Optimizing Trading Strategies with Metaheuristics

Algorithmic Trading is an automatized way of trade goods with the objective of maximizing profits and reducing the investment risk. These algorithms are based on rules that considers the status of the current and historical price and technical analysis indicators. The later can be consider as parametrical statistical suggestion about the current trend of price, market cycles (overbought or oversell), volatility, etc. By making use of these, a set of rules can be design in order to provide trading signals to buy, sell or close trading positions. The collection and application of these given rules are known as trading strategy.

The reliability of these strategies is tested using historical data (backtesting) evaluating the returns offered by the trading decisions in terms of profits and risk. In this stage of development, the proposed strategy can be optimize by looking for the parameters of the technical indicators and rules thresholds that maximize the aforementioned objective.

As an example, a simple strategy can be formulated using the Moving Average (MA), which calculates the average price over a certain window length \(L\) of ticks, filtering the signal into a smoother representation. A possible rule for this strategy is to buy every time the tick close price cross over the MA line, and sell when it cross under it. This strategy can be coded as follows:

function strategy = movingAverageStrategy(asset, window)
movAvg = movavg(asset.close, 'linear', window);
strategy = asset.close > movAvg;
strategy = [0; strategy(1:end-1)];
strategy(strategy==0) = -1;
strategy(1:window) = 0;

or expressed as:

\[ {MA}_{i}(L) = \frac{\sum_{j=i-L}^{i}{close_j}}{L} \]

\[ {MA}^{Strategy}_{i}(L) = \begin{cases} \;0 & i<L\\ \;1 & {MA}_{i-1}(L)>close_{i-1}\\ -1 & otherwise \end{cases} \]

This code produce a discrete vector that indicates the current positions of the strategy, being a value of 1 when the strategy considers a long position, -1 on shorts ones, or 0 when no open position in either side is made. For the case of this strategy a position is always active due to the simplistic rule considered.

The evaluation of any strategy vector of this kind can be executed as follows:

function cumret = evaluateStrategy(asset, strategy, fees)
orders = [0; diff(strategy)];
tickRet = [0; (asset.close(2:end) - asset.close(1:end-1))./asset.close(1:end-1)];
strategyRet = tickRet .* strategy;
strategyRet = strategyRet - abs(orders) * fees;
cumret = cumprod(1 + strategyRet) - 1;

This function considers the fee of placing orders, whose derivate from the strategy on the second line, and their fees are subtracted to the returns on the corresponded tick in line 5. The strategy recollects the returns of each tick, in the direction of the current position, and the cumulative returns shows the profits progression over the executed time of the algorithm. The final value of this vector indicates the estimated profit offered by the strategy.

Due that only one parameter take part on the strategy calculation, being the window length, the optimization of this strategy can be efficiently made by a range sweep of possible values for it as follows:

bestWindow = 0;
bestReturn = -inf;
fees = 0.002; % Taker Fee
for i=1:1000
strategy = movingAverageStrategy(asset, i);
cumret = evaluateStrategy(asset, strategy, fees);
if(cumret[end] > bestReturn)
bestReturn = cumret[end];
bestWindow = i;

This optimization strategy is known as brute force, and it becomes quickly inefficient when more that one parameter is considered as the total possible combinations grows exponentially. For example using a Double Exponential Moving Average (DEMA) strategy requires at least two parameters, being the window length of both EMAs. This strategy considers a fast (\(EMA(L_{fast})\)) and slow (\(EMA(L_{slow})\)) signal, on which $ L_{slow} > L_{fast} $, and positions are made when these price lines crosses each other. This is strategy can be codified as follows:

function strategy = dualMovingAverageStrategy(asset, slowWindow, fastWindow)
slowMovAvg = movavg(asset.close, 'exponential', slowWindow);
fastMovAvg = movavg(asset.close, 'exponential', fastWindow);
strategy = slowMovAvg > fastMovAvg;
strategy = [0; strategy(1:end-1)];
strategy(strategy==0) = -1;
strategy(1:slowWindow) = 0;

\[ {EMA}^{Strategy}_{i}(L) = \begin{cases} \;0 & i<L_{slow}\\ \;1 & {EMA}_{i-1}(L_{slow}) \geq {EMA}_{i-1}(L_{fast})\\ -1 & otherwise \end{cases} \]

Similarly, a MACD strategy requires at least three parameters, two exponential moving averages window length of the asset price, and one for the difference between the first two EMAs. This strategy open a long position every time that the MACD Line crosses the MACD Signal Line and the value of the MACD Line is positive, and a short one when the opposite happens. Therefore, it does not always has an active position like MA and DEMA. This behavior is intended to avoid to open short positions on a bullish trend as explained in https://tradingsim.com/blog/macd/.

function strategy = MACDStrategy(asset, slowMA, fastMA, ma)
emaSlow = movavg(asset.close,'exponential', fastMA);
emaFast = movavg(asset.close,'exponential', slowMA);
MACDLine = emaFast - emaSlow;
MACDSignalLine = movavg(MACDLine,'exponential', ma);
MACDbars = MACDLine - MACDSignalLine;
strategy = zeros(size(asset.close));
strategy(MACDbars > 0 & MACDLine > 0) = 1;
strategy(MACDbars < 0 & MACDLine < 0) = -1;
strategy = [0; strategy(1:end-1)];

Although these three strategies can be optimized individually with a good computer and a couple hours, when a more sophisticated mixture of strategies is intended to be implemented execution time of brute force is unpractical. One approach could be to use the individual best parameters of these strategies and then optimize the mixture weights between them. Splitting the complexity overall optimization, but resulting in sub-optimal solutions, due to different parameters might result optimal when a combination of strategies is intended as opposite of their individual strategy behavior. Although, several ways of mixing these strategies can be formulated, a simple weighted sum can be implemented as follows:

newStr = mixStrategies([MA_Str * 0.5; DEMA_Str * 0.5]) % Example
function newStrat = mixStrategies(strats)
sumStrat = sum(strats, 2);
newStrat = zeros(size(strats, 1), 1);
newStrat(sumStrat >= 1) = 1;
newStrat(sumStrat <= -1) = -1;

This function considers that when the sum of the strategies positions is higher than 1, then the long positions is confirmed, or lower than -1 indicates a short one. Also, when the sum results is between the range \([-1,1]\), no consensus is reached, therefore no positions is advised.

Given the higher number of parameters in a mixture of strategies, other optimization methods can be applied to find efficient solutions quickly. Previous works have make use of Metaheuristics Algorithms to find sub-optimal parameters for single strategies, such as the moving average in (Lee et al. 2005), or a mixture of them in (S.Tawfik, Badr, and Abdel-Rahman 2013) (Contreras, Hidalgo, and Núñez-Letamendia 2013) (Stasinakis et al. 2016) (Hu et al. 2015). Metaheuristics Algorithms are designed to operate as black boxes, in such way that their application results relatively easy. In this case, the open source AISearch toolbox(Reyna-Orta 2019) provides the Metaheuristics implementation. For the case of the application of trading strategy optimization, these can be applied by selecting the dimensionality of the problem as the number of parameters to be adjusted, a function that transform each dimensionality between the desired boundaries and a fitness function that evaluates the solutions offers by the algorithm. If the MA, DEMA, MACD and RSI strategies are considered, then there is 2, 3, 4, and 4 parameters to be found respectively, considering the weight of the strategy as well. Therefore, the dimensionality of the problem becomes 13, and each dimension is manipulated to belong between the boundaries of each strategy. The following function shows an implementation of the designed fitness function, which calculates the strategy of each indicator and their weighted mixture. This mix strategy is evaluated against the historical data, and the final return is used as fitness value.

function y = fitnessFunction(x)
bounds = [1000, 1000, 1000, 500, 500, 50, 50, 50, 500, 1, 1, 1, 1];
MA_Strategy = movingAverageStrategy(asset, ceil(xT(1)));
DMA_Strategy = dualMovingAverageStrategy(asset, ceil(xT(2)), ceil(xT(3)));
MACD_Strategy = MACDStrategy(asset, ceil(xT(4)), ceil(xT(5)), ceil(xT(6)));
RSI_Strategy = rsiStrategy(asset, ceil(xT(7)), 100 - ceil(xT(8)), ceil(xT(9)));
Mix_Strategy = mixStrategies2(xT(end-3:end).*[MA_Strategy, DMA_Strategy, MACD_Strategy, RSI_Strategy]);
Mix_cumret = this.evaluateStrategy(asset, Mix_Strategy, fees);
y = assetR.Mix_cumret(end);

Finally the execution of the algorithm is started by the following code block.

aisearch = PSO(@fitnessFunction, 13);
aisearch.sizePopulation = 100;
aisearch.maxNoIterations = 100;

The code here showed is included as an example application of the Metaheuristics Framework in my GitHub repository.


In this section the results of the optimization process for each individual strategy and the Metaheuristics algorithm mixture are presented. The individual TA strategies are maximized optimally enumerating all the possibilities between the range of values considered. On the other hand, the Metaheuristics solution has to be considered as sub-optimal, as no warranty is offer by the algorithm on finding the global optima. The experiments were realized using the hourly bitcoin historical data provided from the Binance exchange from the dates from 10/05/2017 to 10/06/2019. The Table 1 shows the optimal parameters found for each strategy.

Table 1: Optimization Results
Indicator Parameters Best Return Method
SMA \(L:551\)
1.8457 BruteForce
DEMA \(L_{slow}:967\)
4.4233 BruteForce
MACD \(L_{slow}:468\)
3.9951 BruteForce
RSI \(High:52\)
5.9747 BruteForce
MixOptimal \(SMA_{w}:0\)
? Metaheuristics
Proposal \(SMA_{w}:0.3822\)
10.2581 Metaheuristics

As the results shows, the mix strategy with the parameters jointly optimized achieves a much higher return than the others, even when the algorithm offers a sub-optimal solution. The Fig. 1 shows that, in the proposal, the chosen parameters for the indicators offers a significantly lower return by itself in comparison with their individual optimal. However, the conjunction of these strategies is beneficed by utilizing sub-optimal individual configurations that complement each other. This chart shows that the individual indicators MA, DEMA and MACD work well when the trend is well stablish, but these had great losses otherwise by themselves, while the RSI returns increase only in bullish scenarios, but keeps control of the capital otherwise. The combination of these allows a strategy that returns profits along the trends with lower risk.

Figure 1: Cumulative Returns obtained by the proposal, and its corresponded individual returns for the included strategies.

The Fig. 2 shows the trades proposed by the mixture strategy, and the indicators considered in the strategy. As the Table 1 indicates, the \(RSI_w\) have the higher impact on the making a position decision, and given the parameters offered by the optimization process, the RSI only advice on the buy side. On the other hand, DEMA, MACD and SMA requires of all three to be coordinated to advise a position on any direction.

Figure 2: Candle Chart of BTCUSD with trades and indicators.

The Fig. 3 shows the current bitcoin All Time High (ATH) and the reversal trend, in which MACD is the first to call the close of the Long Position, but it is executed until RSI confirms it.

Figure 3: Close up of Candle Chart of BTCUSD with trades and indicators, major trend change.


Metaheuristics algorithms are efficient optimization algorithm that offers sub-optimal solution to complex problems. In this experiment, it is show that a more complex mixture of TA tools can be used to design a more profitable trading strategy with a sub-optimal parameter configuration, than the individual optima of each one. Nonetheless, algorithmic trading requires more consideration than formulating a strategy. However, the proposed strategy can be used as a statistical suggestion for humans traders to use.


Contreras, Iván, J. Ignacio Hidalgo, and Laura Núñez-Letamendia. 2013. “Combining Technical Analysis and Grammatical Evolution in a Trading System.” In Applications of Evolutionary Computation, 244–53. Springer Berlin Heidelberg. https://doi.org/10.1007/978-3-642-37192-9_25.

Hu, Yong, Kang Liu, Xiangzhou Zhang, Lijun Su, E.W.T. Ngai, and Mei Liu. 2015. “Application of Evolutionary Computation for Rule Discovery in Stock Algorithmic Trading: A Literature Review.” Applied Soft Computing 36 (November). Elsevier BV: 534–51. https://doi.org/10.1016/j.asoc.2015.07.008.

Lee, Ju-sang, Sangook Lee, Seokcheol Chang, and Byung-Ha Ahn. 2005. “A Comparison of GA and PSO for Excess Return Evaluation in Stock Markets.” In Artificial Intelligence and Knowledge Engineering Applications: A Bioinspired Approach, 221–30. Springer Berlin Heidelberg. https://doi.org/10.1007/11499305_23.

Reyna-Orta, Adolfo. 2019. “Aeroreyna/Aisearchmatlab: Alpha Working.” Zenodo. https://doi.org/10.5281/zenodo.3247876.

Stasinakis, Charalampos, Georgios Sermpinis, Ioannis Psaradellis, and Thanos Verousis. 2016. “Krill-Herd Support Vector Regression and Heterogeneous Autoregressive Leverage: Evidence from Forecasting and Trading Commodities.” Quantitative Finance 16 (12). Informa UK Limited: 1901–15. https://doi.org/10.1080/14697688.2016.1211800.

S.Tawfik, Ahmed, Amr A. Badr, and Ibrahim F. Abdel-Rahman. 2013. “One Rank Cuckoo Search Algorithm with Application to Algorithmic Trading Systems Optimization.” International Journal of Computer Applications 64 (6). Foundation of Computer Science: 30–37. https://doi.org/10.5120/10641-5394.