Using argparse to change strategy parameters

Python contains a lot of native and 3rd party modules that can save us a lot of time and effort when developing a strategy. If you are unfamiliar with Python modules, a module is a just another python script that contains useful functions and classes that have already been coded for you. Argparse is a good example of this. It is a module which allows us to specify command line arguments when running our scripts that can be used to change some parameters or logic in our code.

What is a command-line argument?

Since this tutorial is aimed at the beginner, it is worth briefly discussing what command line arguments actually are. When we run a python script, we can run it directly from an IDE (Integrated development environment, such as IDLE, the native development environment bundled with Python) or we can run the script from the command line. A command line argument is a program option that is written following the command/program name. For example:

The commands above are all simple examples of calling a program and providing it a single argument.

CommandArgument
cd/home/
ls-l
python3myStrategy.py

Python’s argparse module allows you to provide arguments to your script in the same way we can provide arguments to other system programs.

Argparse

Before we begin, Python has some great documentation on argparse with simple usage examples. The official “how to” docs can be found here:

https://docs.python.org/3/howto/argparse.html

My intention with this post is to try and add some value to the vast amount of tutorials in the wild by linking it to backtesting and showing some practical examples of how we can use it in our Backtrader scripts.

Common Use Cases

Argparse for me becomes useful after I have generated the “bare bones” of my strategy logic and I am happy with the initial backtest results. I then take a look at my code and decide which parameters it would be useful to change at run time. The same applies to the logic in the code. The reason I personally choose to do a bit later in the process is because I often find that after implementing a simple ‘proof of concept’ / bare bones script, the concept failed to be proved! So with that in mind, I try not to spend too much time on the polishing the table before it has legs.

Some good candidates for run-time options include:

  • Instrument ticker / symbol
  • Backtest date ranges
  • Strategy parameters (e.g, risk, lookback period or whatever you have defined)
  • Operation mode: E.g Backtest or Live Trading

Any parameter or logical decision “could” be a candidate for a run time option. However, I would caution against doing this for every parameter in the script at first. It will add a lot of lines to your code, increase complexity and is prone to fat finger errors.

The Code

For this tutorial, I will adapt the code written in the Backtrader: First Script post. The simple nature of the code will allow us to focus on playing with argparse module.

Code Commentary

One of the great things about the argparse module is that it does more than just let you enter program parameters and options. It also automatically generates some simple user documentation and help prompts. Once setup, you can run your program with the –help option and argparse will display a help file based off the arguments you have added to the code.

Running the above command will output:

Displaying argparse help text

If you have used shell programs in Linux or Mac OS, the output will look very familiar to you. In addition to this, if you enter an incorrect option or miss a required parameter, argparse will tell the you where the issue lies.

Now lets dig into the code. I choose to setup my script arguments inside the dedicated function parse_args(). Apart from being nicely compartmentalized, there are some good reasons for doing this such as being able to us the same script as a module without attempting to parse non-existent arguments. However that is a little out of scope of this post.

Digging into parse_args()

This function contains everything you need to setup and return arguments from the command-line. I have tried to provide a few different types of argument that are useful. As a result, I hope it is easy to copy and adapt to your own scripts. The parse_args() functions contains the following examples:

  1. Positional argument examples
  2. Optional Argument examples
  3. Examples which return strings (this is the default argument type)
  4. An example which returns a number using the type keyword
  5. An example that provides choices

Each argument added in parse_args() has a help keyword. It is from this, that the help text is automatically generated. If you never plan to have someone else use your script, you might decide not to include it. My personal preference is to always write a short help string describing the parameter. It just comes in useful for when you come back to a program after a few months and forgotten what each parameter does. There is no need to return the code in order to figure out how to run it!

Positional vs Optional Arguments

Positional arguments are ones which you must write in a fixed order following the command and do not use a flag/switch/keyword (.eg. “-p“, “–period“). Any argument that you provide a flag or keyword for will be classed as an optional argument. This can be slightly confusing as even if you set an argument’s keyword parameter “required=True“, it will still be classed as an optional argument in the help text.

Argument application in backtesting

So now we get to the bit which matters. How do we make use of these options in a relevant way? I imagine from just reading over each argument’s help strings, you will already have a clear picture of how we can use arguments in a meaningful way. Using arguments to apply the same strategy in different markets is a classic case. In backtesting, changing the date range we are focusing on is another. Some more advanced use cases might include turning logging on/off, putting the script into optimization mode (Optimize Strategies in Backtrader) or switching between live trading, practice accounts and backtesting modes.

Accessing arguments

Once we have called parse_args() in the script we can easily access an argument value through the returned args object. Calling args.start returns the positional argument for the start time. Similarly, calling args.period returns the optional period parameter.

Getting the right data types

By default, argparse returns strings unless we specify otherwise. If we need something other than a string, we should specify what we want using the type keyword. For example, this allows us to specify that a particular argument should be an integer. We do this in the code for the period argument.

As a bonus, argparse will also check the argument provided is the correct type and raise an error if it is not. This saves us from having to code a check in the script. As an example, if we provide a string to the –period argument, argparse will raise the following error:

Next, we have something more interesting. Unfortunately, not all ‘types’ are supported. This is especially true of types that are part of another module. A datetime object is a good example of this. The Yahoo data feed in Backtrader requires datetime objects to specify the start and end date of a backtest. As a result, we must handle the conversion in the script.

In datetime’s strptime() function, the string ‘%Y-%m-%d‘ is defining the expected format of the string. This string means that strptime is expecting a YYYY-MM-DD format string to be provided to it. If the string is not in this format, a ValueError will be raised when you try to run the program.

For more information regarding the various formatting options see:

https://docs.python.org/3.5/library/datetime.html#strftime-and-strptime-behavior

The last line that I think is worth commenting on is:

You may wonder why I went to the effort of extending the Backtrader: First Script example to include strategy params and then load the arguments into cerebro when the strategy is initialized. After all I could have just written:

The reasoning is portability. I can now move this strategy class to any script without having to bring the baggage of my arguments with me. I may not want to have the same arguments in another script.

Final Note

During testing, I noticed that if you have a combination of setting the RSI to use a simple moving average with a period of 7, a ZeroDivisionError is raised. I am not convinced there is an issue with the code because longer periods work and a period of 7 also works with the exponential moving average.  However if I do find an issue with the code, I will update this article accordingly.