As an Amazon Associate I earn from qualifying purchases.
In the last post, I showed you how to Monitor your P/L for open trades. Today, let’s expand on this. I will be going through how to report daily trading statistics for your algorithmic trading bot such as:
- Number of trades opened
- Number of trades closed
- Amount of profitable trades
- Amount of losing trades
- W/L Ratio
- Account balance
I suggest sending these statistics to slack but like the last post, you can also use email if you wish to.
Getting your account information
Start by creating a new method and name it get_account_info
with no arguments:
def get_account_info:
Next, fetch your account information using the MT5 API account_info
method and assign it to a variable named res
:
def get_account_info:
res = mt5.account_info()
And finally return res
:
def get_account_info:
res = mt5.account_info()
return res
Getting the trading history
When you are sending the daily stats notification, you will be fetching the last 24 hours of data. This means if you decide to send daily stats at 7 pm, everything from 7 pm yesterday until today will be included. Start by defining a new method named get_daily_trade_data
which takes no arguments:
def get_daily_trade_data():
You may remember from Creating an algotrader/trading bot with Python – Part 1 – I mentioned your local time and broker time can be different. I highly suggest using your broker time when calculating the last 24 hours of data. Start by defining the current time adjusted for your broker time:
def get_daily_trade_data():
now = datetime.now().astimezone(pytz.timezone('Europe/Athens'))
now = datetime(now.year, now.month, now.day, hour=now.hour, minute=now.minute)
From the variable now
, you need to subtract 24 hours using timedelta
and assign this to a variable named yesterday
:
def get_daily_trade_data():
now = datetime.now().astimezone(pytz.timezone('Europe/Athens'))
now = datetime(now.year, now.month, now.day, hour=now.hour, minute=now.minute)
yesterday = now - timedelta(hours=24)
And call get_order_history
passing in both variables and return the result res
:
def get_daily_trade_data():
now = datetime.now().astimezone(pytz.timezone('Europe/Athens'))
now = datetime(now.year, now.month, now.day, hour=now.hour, minute=now.minute)
yesterday = now - timedelta(hours=24)
res = get_order_history(yesterday, now)
return res
Formatting the stats into a message
Now that you have all the information you need, let’s go ahead and start formatting the data into a message that can be sent to slack. Define a new method and name it send_daily_stats
. This method will also take no arguments:
def send_daily_stats():
Get the required account information using get_account_info()
and assign it to a variable named account_info
. Similarly, get the last 24 trade data by using get_daily_trade_data()
and assign it to a variable named trades
:
def send_daily_stats():
account_info = get_account_info()
trades = get_daily_trade_data()
Now, let’s start gathering all the information we care about. Start by getting balance
from account_info
:
def send_daily_stats():
account_info = get_account_info()
trades = get_daily_trade_data()
balance = account_info.balance
Here is a reminder of what we want to send in the daily stats alert:
- Number of trades opened
- Number of trades closed
- Amount of profitable trades
- Amount of losing trades
- W/L Ratio
- Account balance
When we get the deal history, a trade will have 2 entries: one for opening the trade and one for closing. You can differentiate these entries by the type
column. 0 is opened and 1 is closed. To get a count of opened trades and closed trades we will use the shape
method to count the rows in each data frame. (More information on shape
can be found here):
# Calculate open trade count
opened_trades = trades[trades['type'] == 0]
open_trade_count = opened_trades.shape[0]
# Calculate closed trade count
closed_trades = trades[trades['type'] == 1]
closed_trade_count = closed_trades.shape[0]
Calculate the amount of profitable and losing trades. For profitable trades, filter on the profit
column for anything greater than zero. For losing, filter on profit less than zero. Get the counts by using the same shape
method above:
# Calculate profitable trades count
profitable_trades = trades[trades['profit'] > 0]
profitable_trades_count = profitable_trades.shape[0]
# Calculate losing trades count
losing_trades = trades[trades['profit'] < 0]
losing_trades_count = losing_trades.shape[0]
The last thing you need to do is calculate the W/L Ratio. The W/L ratio is defined as
W/L Ratio = Amount of winning trades / Amount of losing trades
The code in our case will look like the following:
winLossRatio = profitable_trades_count / losing_trades_count
Great! Now we have all the information we need to send our stats alert. In the next section, I take you through how to format this information into a message for slack.
Formatting the message for slack
We will be using JSON to send a formatted message to slack. Here is a preview of what our slack message template will look like:


Start by copying the following JSON template and assign it to a variable named slack_json
in the send_daily_stats
method. (This can also be loaded in as a JSON file using json.loads
):
slack_json = [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Daily report"
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Account Balance:* "
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Trades opened:* "
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Trades closed:* "
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Profitable trades:* "
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Losing trades:* "
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*W/L Ratio:* "
}
}
]
Modify the text field in each entry to include the variables created in the previous section. These variables should be wrapped in the str
method.
E.g "text": "Account Balance: "
becomes "text": "Account Balance: " + str(balance)
"text": "*Trades opened:*"
becomes "text": "*Trades opened:* " + str(open_trade_count)
etc.
After this has been done, your are ready to send your slack message!
send_notification("", slack_json)
Call send_notification
with 2 arguments, leaving the first argument as an empty string and the second as slack_json
:
send_notification("", slack_json)
Okay, so you may have noticed the send_notification
method only takes one argument. We need to add a second optional argument blocks
to send the message as JSON.
Find send_notification
and modify the method signature by adding blocks = None
at the end as an optional argument:
def send_notification(message, blocks = None):
Now, specify if blocks
is None
then call client.chat_postMessage
without the blocks
argument, otherwise call it with this argument:
def send_notification(message, blocks = None):
if(blocks is None):
client.chat_postMessage(channel='#algotrader', text=message)
else:
client.chat_postMessage(channel='#algotrader', text=message, blocks=blocks)
Sending daily stats on a schedule
The final part is to add daily stats to the schedule. Find the live_trading
method and add the following line to the end of all the schedule calls:
schedule.every().day.at("19:00").do(send_daily_stats)
Your live_trading
method should now look like the following:
def live_trading(strategy):
schedule.every().hour.at(":00").do(run_trader, mt5.TIMEFRAME_M15, strategy)
schedule.every().hour.at(":15").do(run_trader, mt5.TIMEFRAME_M15, strategy)
schedule.every().hour.at(":30").do(run_trader, mt5.TIMEFRAME_M15, strategy)
schedule.every().hour.at(":45").do(run_trader, mt5.TIMEFRAME_M15, strategy)
schedule.every(4).hours.at(":00").do(send_stats)
schedule.every().day.at("19:00").do(send_daily_stats)
while True:
schedule.run_pending()
time.sleep(1)
Testing the code
To test the code, I will manually trigger the send_daily_stats method. I expected to see my message in slack formatted as specified above:


As you can see, my message has been posted to my slack channel and includes all the daily stats relevant to my account.
If you are interested in learning more about algo trading and trading systems, I highly recommend reading this book. I have taken some of my own trading ideas and strategies from this book. It also provided me a great insight into effective back testing. Check it out here.
That’s all for how to report daily trading statistics for your Algorithmic trading bot! Check back on Friday to see how to turn your strategy off if you have reached your maximum account drawdown. As always, if you have any questions or comments please feel free to post them below. Additionally, if you run into any issues, please let me know.
Hi Connor,
I’m having some issues trying to deal with this error when I run the code:
Traceback (most recent call last):
File “C:\Python\Python39\lib\site-packages\pandas\core\indexes\base.py”, line 3080, in get_loc
return self._engine.get_loc(casted_key)
File “pandas\_libs\index.pyx”, line 70, in pandas._libs.index.IndexEngine.get_loc
File “pandas\_libs\index.pyx”, line 101, in pandas._libs.index.IndexEngine.get_loc
File “pandas\_libs\hashtable_class_helper.pxi”, line 4554, in pandas._libs.hashtable.PyObjectHashTable.get_item
File “pandas\_libs\hashtable_class_helper.pxi”, line 4562, in pandas._libs.hashtable.PyObjectHashTable.get_item
KeyError: ‘type’
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File “C:\Python\Python39\Scripts\trader.py”, line 368, in
live_trading(current_strategy)
File “C:\Python\Python39\Scripts\trader.py”, line 358, in live_trading
schedule.run_pending()
File “C:\Python\Python39\lib\site-packages\schedule\__init__.py”, line 592, in run_pending
default_scheduler.run_pending()
File “C:\Python\Python39\lib\site-packages\schedule\__init__.py”, line 94, in run_pending
self._run_job(job)
File “C:\Python\Python39\lib\site-packages\schedule\__init__.py”, line 147, in _run_job
ret = job.run()
File “C:\Python\Python39\lib\site-packages\schedule\__init__.py”, line 491, in run
ret = self.job_func()
File “C:\Python\Python39\Scripts\trader.py”, line 346, in run_trader
send_daily_stats()
File “C:\Python\Python39\Scripts\trader.py”, line 235, in send_daily_stats
opened_trades = trades[trades[‘type’] == 0]
File “C:\Python\Python39\lib\site-packages\pandas\core\frame.py”, line 3024, in __getitem__
indexer = self.columns.get_loc(key)
File “C:\Python\Python39\lib\site-packages\pandas\core\indexes\base.py”, line 3082, in get_loc
raise KeyError(key) from err
KeyError: ‘type’
It keeps complaining of KeyError ‘type’ in:
def send_daily_stats():
account_info = get_account_info()
trades = get_daily_trade_data()
balance = account_info.balance
# Calculate open trade count
opened_trades = trades[trades[‘type’] == 0]
open_trade_count = opened_trades.shape[0]
# Calculate closed trade count
closed_trades = trades[trades[‘type’] == 1]
closed_trade_count = closed_trades.shape[0]
# Calculate profitable trades count
profitable_trades = trades[trades[‘profit’] > 0]
profitable_trades_count = profitable_trades.shape[0]
# Calculate losing trades count
losing_trades = trades[trades[‘profit’] < 0]
losing_trades_count = losing_trades.shape[0]
winLossRatio = profitable_trades_count / losing_trades_count
slack_json = [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Daily report"
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Account Balance: " + str(balance)
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Trades opened:* " + str(open_trade_count)
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Trades closed:* " + str(closed_trade_count)
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Profitable trades:* " + str(profitable_trades_count)
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Losing trades:* " + str(losing_trades_count)
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*W/L Ratio:* " + str(winLossRatio)
}
}
]
send_notification("", slack_json)
Not sure what I did wrong.
I’m stuck on this too but I don’t know if they even reply or look at these messages at this point…you message or DM them here directly or on Reddit and you just get ignored…
This code counts on the fact that a trade has been opened. I might need to add a None check for the trades variable! Apologies on not getting back soon, I’ve been extremely busy the last few weeks 🙂
This code counts on the fact that a trade has been opened. I might need to add a None check for the trades variable! Apologies on not getting back soon, I’ve been extremely busy the last few weeks