Fine-Tuning the Algorithmic Strategy Using a Particle Swarm Optimization | by Sergey Malchevskiy | Towards Data Science

Today we talk about a technique that allows searching a good set of parameters for a limited time, also we will consider one trading strategy as a bonus. Have a good read!
Gentle Introduction to Particle Swarm OptimizationTrading Strategy AlgorithmLet’s Code It!ExperimentFurther ImprovementsSummary
Gentle Introduction to Particle Swarm Optimization
Trading Strategy Algorithm
Let’s Code It!
Experiment
Further Improvements
Summary
The main goal of the optimization for our task is to define the sub-optimal parameters of the trading strategy that maximize or minimize the objective function (usually it uses the minimization).
The objective function could be simple like the return of the algorithm that we should maximize or the drawdown that we should minimize. In this experiment, we use a more sophisticated and complex function that tries to compose a few factors. I describe this function in one next part.
A lot of researchers described the PSO approach in their works. My task is to collect in one place the main algorithmic scheme and principles behind the method. Also, I give links to get more information and dive into it depending on your interests.
From Wikipedia:
In computational science, particle swarm optimization (PSO) is a computational method that optimizes a problem by iteratively trying to improve a candidate solution with regard to a given measure of quality. It solves a problem by having a population of candidate solutions, here dubbed particles, and moving these particles around in the search-space according to simple mathematical formulae over the particle’s position and velocity. Each particle’s movement is influenced by its local best known position, but is also guided toward the best known positions in the search-space, which are updated as better positions are found by other particles. This is expected to move the swarm toward the best solutions.
PSO is a metaheuristic as it makes few or no assumptions about the problem being optimized and can search very large spaces of candidate solutions. However, metaheuristics such as PSO do not guarantee an optimal solution is ever found. Also, PSO does not use the gradient of the problem being optimized, which means PSO does not require that the optimization problem be differentiable as is required by classic optimization methods such as gradient descent and quasi-newton methods.
It sounds great, we can use the objective function that could be non-differentiable. This is a very convenient point because our function usually is complex and consists of different parts. Also, the algorithm can work with no assumptions in large spaces. The scheme of the algorithm looks like this
The math description you can get in this source article. The algorithm is iterative that tries to converge step by step, this picture shows the animation of the process.
Generally, the pseudo-code independent of a programming language with basic operations is the next.
The good explanation of the algorithm with hands-on implementation by Tarlan Ahadli you can find here. Also, you can watch this great video
If you want to dive into different modifications of this approach, I would recommend this, this, and this.
The main idea of the strategy is to try to catch the moment when price breakthrough the trend in a reverse direction. After that, we open the position and try to make the money.
The trend line is based on two pivot points. For the upside trend line (green lower line), the next down pivot point is higher than the previous one, and the next up pivot point is lower than the previous one for the downside trend (red upper line).
A down pivot point is the low price of the bar with the lowest price around several bars (2 bars minimum for each side), an up pivot point has a reversal logic.
The enter in the long position occurs when price intersects the downside trend line and closes upper than this line. A reversal logic for the short position.
The exit point of the position is a take profit (green label) or stop loss (red label). In this case, the fixed levels are used for TP and SL. The alternative event for the exit is occurring of the reversal pattern.
After we got the description of the strategy we can start writing the code. As a backtesting system is used Backtrader. This is an open-source feature-rich framework for the backtesting and trading. Yes, in Python.
This project I separated into 5 files, where 4 files consisting of a class, and one file is a script that contains the main experiment and runs the others.
The code of main.py contains the logic of the experiment here.
This script contains three important parts: objective function definition, running the PSO on train dataset, and running the strategy with the best parameters on a few datasets for the evaluation (train, test, full). As a PSO library is used pyswarm.
In this script, we create the object of BacktestTrendBreakerPL class. This class is responsible for data reading, running the strategy, calculating the metrics, and results in visualization. The code of BacktestTrendBreakerPL.py class is
The output_settings object that we passed from the main script is responsible for verbosity.
The interesting part is stability_of_timeseries function. This function is a heart of research that impact on optimization process directly. The main goal is a quality estimation of the trading strategy with the passed set of the parameters. Mathematically, it determines as an R-squared of a linear fit to the cumulative log returns. The sign of the function depending on profitability. When the value is close to 1.0 it means the great result and line is growing up with small drawdowns, the bad result when this value is close to -1.0.
This script contains FinamHLOC class from DataFeedFormat.py, where located the code for the data structure of source CSV-file.
The next interesting thing is an object of TrendBreakerPL class from TrendBreakerPLStrategy.py.
The main functionality of this class is position management. The first one is to enter the position when the signal occurs. The second one is to close the position when the price is reached the Take profit or Stop loss, or the reversal pattern has occurred.
The additional functionality is printing the order status.
This script uses the signals based on indicator data that defined in PivotPointLine class in PivotPointLineIndicator.py. The basic functionality is calculating the pivot points, trend lines and signal when close price intersects and closing upper\lower trend line depending on the direction.
I think this code is not optimal, it takes a lot of time in a general pipeline of the backtesting.
When I was developing this solution I met with problems with the latest version of Pyfolio library that I use for visualization and some metrics. I fixed a few lines of code how it was suggested here.
Also, I changed a few lines of code in the plotting.py of Pyfolio like this because I need a DataFrame object
The experiment is conducted on Sberbank data. Sberbank is the largest bank in Russia, Eastern, and Central Europe.
I downloaded hourly data from Finam. The period starts from 6th of January 2014 to 21th of February 2020. Then this period was divided on
train period (6th of January 2014 — 29th of December 2017);
test period (3th of January 2018 — 21th of February 2020).
The set of optimization parameters includes:
pivot_window_len - window length (the number of bars) by each side for determing the pivot point. Range is [2, 120];history_bars_as_multiple_pwl - history window length as a multiplication factor for pivot_window_len. Range is [10, 100];fixed_tp - value for the fixed level of take profit. Range is [0.01, 0.2];fixed_sl_as_multiple_tp - value for the fixed level of stop loss as a multiplication factor for fixed_tp. Range is [0.1, 1.5].I used a multiplication factor instead of a simple value because it is easier to control and logically suitable in an optimization process. E.g., take profit and stop loss will be closer to each other.
The experiment was launched on 40 iterations with swarm size is 20. The other paramters of swarm optimization are default (omega=0.5, phip=0.5, phig=0.5). The best set of parameters on train data is:
pivot_window_len = 9history_bars_as_multiple_pwl = 49fixed_tp = 0.06034064 (6.034%)fixed_sl_as_multiple_tp = 0.14778282. It  means stop loss is 0.892%As a result the objective function is -0.9712, then stability is 0.9712. This is very good result because it's very close to 1.0.
Let’s look at the capital curve of our experiment
It looks pretty good. On the train period, we have the same level of return with lower drawdown and more stable curve. The algo curve on the test period demonstrates strong positive dynamics, much better than the benchmark (buy & hold). The final curve strongly better than the benchmark. The metrics of the algorithm are
Train:Return: 106.45%Stability: 0.971Maximum Drawdown: 14.37%Test:Return: 63.17%Stability: 0.7934Maximum Drawdown: 11.58%Full Period:Return: 296.3%Stability: 0.9738Maximum Drawdown: 14.37%
The next picture demonstrates more effectively the drawdown periods
The good point is that the drawdown level is stable during the train and test periods. Even on the test period is lower. Let’s look at Top-5 drawdowns
Train:  Net drawdown in %  Peak date Valley date Recovery date Duration1            14.365 2017-06-15  2017-12-27           NaT      NaN2           13.2355 2015-05-20  2015-07-07    2015-11-17      1303           12.8431 2014-10-06  2014-12-02    2014-12-16       514           10.1103 2015-01-27  2015-02-12    2015-03-16       355            8.6618 2014-04-01  2014-04-22    2014-06-16       55Test:  Net drawdown in %  Peak date Valley date Recovery date Duration1           11.5807 2019-02-08  2019-03-18    2019-04-23       522           10.8415 2019-07-23  2019-12-02           NaT      NaN3           9.28844 2018-05-03  2018-06-25    2018-07-27       624           8.26704 2018-10-30  2018-12-03    2019-01-18       595           7.07621 2018-04-03  2018-04-09    2018-04-09        5Full Period:  Net drawdown in %  Peak date Valley date Recovery date Duration1            14.365 2017-06-15  2017-12-27    2018-03-21      2002           13.2355 2015-05-20  2015-07-07    2015-11-17      1303           12.8431 2014-10-06  2014-12-02    2014-12-16       514           12.1112 2019-02-08  2019-03-18    2019-05-03       605           11.5791 2019-07-23  2019-12-02           NaT      NaN
As we see, the highest drawdown was on the train period.
The next picture demonstrates the hourly returns. On the whole, the number of outliers is small.
Let’s look at the quantile plot of the returns for the different timeframes
The shapes are similar, this is a good indication. The monthly returns distribution is next, the mean is strongly higher than 0.0 and negative returns are compacted without outliers.
The final summary of the experiment is that the PSO allows getting the pretty good quality for the limited time, and the sub-optimal parameters are working on the out-of-sample dataset with the same level of profitability and drawdown.
I would recommend researching the next moments to get better results:
Experiment with the PSO parameters. Also, you can try to use another approach for optimization instead of PSO, then you can compare each other.Volatility-based Take profit and Stop loss.Trailing stop instead of Take profit and Stop loss.Limited time in a position.Any pattern trigger to close a position (e.g., intersecting trend line while in a position).Different timeframes (e.g. 15 minutes or 1 day).Apply the Walk-Forward Analysis to research the stability and robustness of the result.
Experiment with the PSO parameters. Also, you can try to use another approach for optimization instead of PSO, then you can compare each other.
Volatility-based Take profit and Stop loss.
Trailing stop instead of Take profit and Stop loss.
Limited time in a position.
Any pattern trigger to close a position (e.g., intersecting trend line while in a position).
Different timeframes (e.g. 15 minutes or 1 day).
Apply the Walk-Forward Analysis to research the stability and robustness of the result.
8. Create a portfolio based on several uncorrelated assets.
9. Add the stress-testing mechanism for strategy robustness verification.
10. Add more metrics for the cumulative Walk-Forward Period and portfolio curve, such as alpha, beta, Sortino ratio, and so on.
These improvements requires a lot of computations, you should think about:
Refactor the PivotPointLineIndicator.py because this is very slow implementation, or prepare the pre-calculated cache of the indicator for particular timeframes and parameters.Try to use GPU where it is possible.Use a multiprocessing mode in portfolio generating.Check other implementation of the PSO approach. Some libraries can do the computations on GPU and CPU multiprocessing mode.
Refactor the PivotPointLineIndicator.py because this is very slow implementation, or prepare the pre-calculated cache of the indicator for particular timeframes and parameters.
Try to use GPU where it is possible.
Use a multiprocessing mode in portfolio generating.
Check other implementation of the PSO approach. Some libraries can do the computations on GPU and CPU multiprocessing mode.
Described the quick introduction to particle swarm optimization approach and its application to algorithmic trading.Formalized the patterns and logic of the trading strategy.Demonstrated the code with a description that you can get on GitHub. Also, pay the attention to packages_env_list.txt for installing the right versions of packages.Trained and tested the approach, and evaluated the experiment.Suggested further improvements.
Described the quick introduction to particle swarm optimization approach and its application to algorithmic trading.
Formalized the patterns and logic of the trading strategy.
Demonstrated the code with a description that you can get on GitHub. Also, pay the attention to packages_env_list.txt for installing the right versions of packages.
Trained and tested the approach, and evaluated the experiment.
Suggested further improvements.
In this case, the suggested approach presented a great result with a good performance and moderate drawdowns for this kind of strategy.
All steps and the experiment are reproducible, you can get the code and play with your data. As a data source, you can try this service by Antoine Vulcain for free.
Have a good investing!
Best regards,
Sergey
Note from Towards Data Science’s editors: While we allow independent authors to publish articles in accordance with our rules and guidelines, we do not endorse each author’s contribution. You should not rely on an author’s works without seeking professional advice. See our Reader Terms for details.