5 Python Tips for Quant Trading Systems
It’s been a while since quantitative trading has become instrumental among fund managers in adding uncorrelated returns to their portfolios.
The quantitative methodology has the ultimate goal of translate a set of tasks taken by an investment analyst into a programmable, rule-based system capable of running large-scale and more efficiently.
In today’s post I share a few tips/insights I personally use in my trading systems written in Python language. Target audience is therefore Python developers with either basic or advanced knowledge of trading and risk management systems. If you don’t fit this group but have any questions, feel free to DM me on social media.
Each of these tips has helped me, directly or indirectly, to solve something I was once stuck by.
Libraries
Just so you know, make sure you import the following libraries before trying out the 5 tips.
import datetime as dt
from datetime import timedelta
import pandas as pd
import pytz
import math
#1 Get previous/next timestamp based on trading interval
Both previous and next timestamps can be achievable by passing the same parameters: the trading interval string itvl and the current datetime object curr_dt.
An example case of the get_prev_tstamp function would be to test whether last timestamp was the last trading bar for a given day to prevent new updates hence spend less computing power, while the get_next_tstamp function I personally use to set as the time my system will resume activities after a sleep time and look again for new signals.
Trading intervals are expected to be provided in the “m” (1m, 5m, 15m…), “h” (1h, 4h, 8h…) or “d” (1d, 2d…) format, so be careful when pasting the above into your code without make proper formatting adjustments when needed. Output is returned as a datetime object.
Example 1 — get previous timestamp
Input:
get_prev_tstamp("1h", dt.datetime(2021, 7, 19, 12, 0, 0))Output:
2021-07-19 11:00:00
Example 2 — get next timestamp
Input:
get_next_tstamp("1m", dt.datetime(2021, 7, 19, 12, 34, 0))Output:
2021-07-19 12:35:00
#2 Concatenating rows/columns along existing DataFrame
I assume you are familiar with pandas read_csv and to_csv functions when it comes to store historical databases in CSV format. Unless in a few circumstances such as when stocks pay dividend (and past values must be adjusted), re-download all historical data every time you look for new signals is time-consuming and will take longer to execute, hence should be avoided. So, the goal is to simply concatenate the new row to the existing database rather than updating it all.
Similarly, pandas.concat also allows for columns concatenation. This is mostly useful when plotting indicators such as moving averages. As one can notice, column concatenation has the axis parameter set to 1 instead of the default 0 (for rows). Both functions return a DataFrame object.
#3 Adjusting timezone
Working with datetime objects is far from being my favorite part when writing code, particularly when I need to deal with different timezones.
Candlestick data from Binance, for example, are returned in UTC time only. Living in Sao Paulo (GMT-3), I of course prefer to store them in local time. The code below simply converts the object parameters into the desired timezone parameters. Output returns a timezone-naive, which I personally find easier to work with.
Input:
adjust_dt(dt.datetime(2021, 7, 19, 16, 0, 0), "UTC", "America/Sao_Paulo")Output:
2021-07-19 13:00:00
Note: pytz.all_timezones attribute will give you a list of all timezone strings available.
#4 Setting time range for simulations
Besides good backtest results, trading your money live will also require sufficient statistical evidence that your strategy will delivery as expected within acceptable degree of confidence. A well-known simulation in the trading space is the Monte Carlo simulation.
First of all, note that in line 3 we use the get_next_tstamp function from tip #1 discussed above.
While writing this piece of code, I assumed today is Friday 23/Jul/21 15:30 and your system is trading a random Brazilian stock. Last timestamp of our database should be Friday 23/Jul/21 15:00 because we are tracking hourly data, so starting timestamp of our range will be Friday 23/Jul/21 16:00.
Market is open only between 10:15 and 16:45 in weekdays only excluding holidays. You want to run a simulation and your x axis must be the future tradable timestamps. Few things you must consider excluding:
- Saturday and Sundays;
- Holidays (shall be included as elements in holidays empty list as date objects);
- Timestamps when market is closed (manually adjusted depending on the exchange).
Input:
get_date_range()Output:
DatetimeIndex(['2021-07-23 16:00:00', '2021-07-26 11:00:00',
'2021-07-26 12:00:00', '2021-07-26 13:00:00',
'2021-07-26 14:00:00', '2021-07-26 15:00:00',
'2021-07-26 16:00:00', '2021-07-27 11:00:00',
'2021-07-27 12:00:00', '2021-07-27 13:00:00',
'2021-07-27 14:00:00', '2021-07-27 15:00:00',
'2021-07-27 16:00:00'],
dtype='datetime64[ns]', freq=None)
Note: date labels will not change the values of the simulation, so this is more a formatting enhancement than anything else.
#5 Avoiding issues with price/volume when sending orders
Depending on your strategy, you may end up with price and volume values with too many decimal points. However, exchanges/brokers have specific tick and lot sizes for the issues traded, which means you shall give the inputs following these rules (i.e. right number of decimals), otherwise your order will fail to execute and your system will screw up.
Depending on the exchange/broker, they may also impose minimum and maximum volume, so make sure you capture these scenarios using min() and max() built-in functions when dealing with the variable volume.
Example 1 — format price
Input:
format_price(29.2178)Output:
29.21
Example 2 — format volume
Input:
format_volume(255.343241)Output:
200.0
That’s all, folks! Hope you enjoyed the tips and were able to derive some value from it. I’m open to discuss any questions on social media, just follow me and let’s get in touch. :-)