Optimization of FX systole parameters in Python So, I tried all the combinations of parameters of the trading system to find the optimum solution.
If the number of combinations is small, brute force is fine, but if the number of combinations is large, it becomes quite difficult. The optimization algorithm for that is [Metaheuristics](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BF%E3%83%92%E3%83%A5% It is known as E3% 83% BC% E3% 83% AA% E3% 82% B9% E3% 83% 86% E3% 82% A3% E3% 82% AF% E3% 82% B9). I wanted to implement a well-known genetic algorithm, but before that, I wrote a simpler random search in Python.
Put the function to convert 1-minute data to another time frame and the technical indicator function in indicators.py, and the function to perform the previous backtest and its evaluation in backtest.py. The source can be found on GitHub. The following code will create 1 hour data in ʻohlc`.
import numpy as np
import pandas as pd
import indicators as ind #indicators.Import of py
from backtest import Backtest,BacktestReport
dataM1 = pd.read_csv('DAT_ASCII_EURUSD_M1_2015.csv', sep=';',
names=('Time','Open','High','Low','Close', ''),
index_col='Time', parse_dates=True)
dataM1.index += pd.offsets.Hour(7) #7 hour offset
ohlc = ind.TF_ohlc(dataM1, 'H') #Creation of 1-hour data
In the previous optimization, we optimized by changing two parameters within a certain range as follows.
SlowMAperiod = np.arange(10, 61) #Range of long-term moving average period
FastMAperiod = np.arange(5, 31) #Range of short-term moving averages
At this rate, there are only 1066 ways, but I will change the number of combinations later.
In a simple random search, parameter values should be randomly generated in sequence and the one with the highest backtest evaluation should be adopted, but here, one time so that it can be applied to genetic algorithms I tried about 20 parameter combinations in each generation and tried to repeat it for several generations.
def Optimize(ohlc, SlowMAperiod, FastMAperiod):
def shift(x, n=1): return np.concatenate((np.zeros(n), x[:-n])) #Shift function
SlowMA = np.empty([len(SlowMAperiod), len(ohlc)]) #Long-term moving average
for i in range(len(SlowMAperiod)):
SlowMA[i] = ind.iMA(ohlc, SlowMAperiod[i])
FastMA = np.empty([len(FastMAperiod), len(ohlc)]) #Short-term moving average
for i in range(len(FastMAperiod)):
FastMA[i] = ind.iMA(ohlc, FastMAperiod[i])
M = 20 #Population
Eval = np.zeros([M, 6]) #Evaluation item
Param = np.zeros([M, 2], dtype=int) #Parameters
RandomSearch(Param, Eval[:,0], SlowMAperiod, FastMAperiod) #Parameter initialization
gens = 0 #Number of generations
while gens < 100:
for k in range(M):
i = Param[k,0]
j = Param[k,1]
#Buy entry signal
BuyEntry = (FastMA[j] > SlowMA[i]) & (shift(FastMA[j]) <= shift(SlowMA[i]))
#Sell entry signal
SellEntry = (FastMA[j] < SlowMA[i]) & (shift(FastMA[j]) >= shift(SlowMA[i]))
#Buy exit signal
BuyExit = SellEntry.copy()
#Sell exit signal
SellExit = BuyEntry.copy()
#Backtest
Trade, PL = Backtest(ohlc, BuyEntry, SellEntry, BuyExit, SellExit)
Eval[k] = BacktestReport(Trade, PL)
#Alternation of generations
Param = RandomSearch(Param, Eval[:,0], SlowMAperiod, FastMAperiod)
gens += 1
Slow = SlowMAperiod[Param[:,0]]
Fast = FastMAperiod[Param[:,1]]
return pd.DataFrame({'Slow':Slow, 'Fast':Fast, 'Profit': Eval[:,0], 'Trades':Eval[:,1],
'Average':Eval[:,2],'PF':Eval[:,3], 'MDD':Eval[:,4], 'RF':Eval[:,5]},
columns=['Slow','Fast','Profit','Trades','Average','PF','MDD','RF'])
The difference from the last time is that we prepared an array Param
to store the combination of parameters in each generation. Then, the set of parameters with the highest evaluation value (total profit / loss in this case) is saved (elite save) for each generation, and the remaining parameters are randomly generated repeatedly.
A function that randomly generates parameters with elite storage can be written as:
#Random search with elite save
def RandomSearch(Param, Eval, SlowMAperiod, FastMAperiod):
Param = Param[np.argsort(Eval)[::-1]] #sort
NewParam = np.vstack((np.random.randint(len(SlowMAperiod), size=len(Eval)),
np.random.randint(len(FastMAperiod), size=len(Eval)))).T
NewParam[0] = Param[0] #Elite save
return NewParam
The method is to sort the evaluation values and set the 0th parameter again. When you do this, you get:
result = Optimize(ohlc, SlowMAperiod, FastMAperiod)
result.sort_values('Profit', ascending=False)
Slow | Fast | Profit | Trades | Average | PF | MDD | RF | |
---|---|---|---|---|---|---|---|---|
0 | 27 | 8 | 2507.1 | 264.0 | 9.496591 | 1.423497 | 485.1 | 5.168213 |
15 | 18 | 5 | 944.1 | 428.0 | 2.205841 | 1.110187 | 693.1 | 1.362141 |
19 | 48 | 14 | 825.4 | 238.0 | 3.468067 | 1.131883 | 927.7 | 0.889727 |
16 | 42 | 29 | 720.4 | 308.0 | 2.338961 | 1.094974 | 1011.8 | 0.711998 |
8 | 44 | 25 | 589.3 | 246.0 | 2.395528 | 1.089205 | 1141.1 | 0.516432 |
6 | 60 | 25 | 588.8 | 120.0 | 4.906667 | 1.126946 | 1025.0 | 0.574439 |
4 | 22 | 5 | 493.5 | 124.0 | 3.979839 | 1.105354 | 1106.3 | 0.446082 |
11 | 26 | 7 | 391.2 | 206.0 | 1.899029 | 1.069326 | 1099.8 | 0.355701 |
17 | 13 | 7 | 343.2 | 152.0 | 2.257895 | 1.069697 | 990.9 | 0.346352 |
18 | 45 | 20 | 174.5 | 160.0 | 1.090625 | 1.032210 | 1118.4 | 0.156026 |
12 | 38 | 20 | 169.8 | 170.0 | 0.998824 | 1.029754 | 939.8 | 0.180677 |
3 | 21 | 11 | 148.6 | 460.0 | 0.323043 | 1.016830 | 1203.1 | 0.123514 |
5 | 34 | 13 | -342.0 | 170.0 | -2.011765 | 0.939258 | 1160.8 | -0.294624 |
7 | 42 | 22 | -374.4 | 156.0 | -2.400000 | 0.933744 | 1189.6 | -0.314728 |
1 | 51 | 20 | -441.4 | 160.0 | -2.758750 | 0.922788 | 1089.9 | -0.404991 |
9 | 58 | 27 | -1485.5 | 222.0 | -6.691441 | 0.787506 | 2394.0 | -0.620510 |
10 | 19 | 25 | -1620.0 | 234.0 | -6.923077 | 0.775828 | 1845.7 | -0.877716 |
13 | 33 | 16 | -1625.0 | 400.0 | -4.062500 | 0.827957 | 2307.7 | -0.704164 |
2 | 44 | 24 | -1885.7 | 508.0 | -3.712008 | 0.828287 | 2652.2 | -0.710995 |
14 | 55 | 12 | -2048.3 | 398.0 | -5.146482 | 0.776908 | 2813.6 | -0.728000 |
In this way, the optimal solution has been sought, but it is natural because we have tried 20 individuals for 100 generations, that is, nearly 2000 times.
Since there are only two parameters this time, the number of combinations does not increase so much,
SlowMAperiod = np.arange(10, 161) #Range of long-term moving average period
FastMAperiod = np.arange(5, 131) #Range of short-term moving averages
Let's increase it to 19,026 ways like this. Result is
Slow | Fast | Profit | Trades | Average | PF | MDD | RF | |
---|---|---|---|---|---|---|---|---|
0 | 52 | 48 | 2003.4 | 190.0 | 10.544211 | 1.426773 | 1106.4 | 1.810738 |
5 | 125 | 26 | 1312.6 | 58.0 | 22.631034 | 1.513939 | 896.4 | 1.464302 |
16 | 49 | 47 | 1203.9 | 62.0 | 19.417742 | 1.448046 | 773.0 | 1.557439 |
11 | 56 | 55 | 989.6 | 116.0 | 8.531034 | 1.245266 | 922.0 | 1.073319 |
14 | 52 | 34 | 704.0 | 62.0 | 11.354839 | 1.261632 | 527.2 | 1.335357 |
13 | 152 | 36 | 545.5 | 75.0 | 7.273333 | 1.133198 | 936.5 | 0.582488 |
2 | 140 | 58 | 457.4 | 196.0 | 2.333673 | 1.085960 | 867.1 | 0.527505 |
18 | 40 | 109 | 308.1 | 88.0 | 3.501136 | 1.073977 | 1435.0 | 0.214704 |
6 | 75 | 107 | 255.1 | 78.0 | 3.270513 | 1.066815 | 1145.1 | 0.222775 |
3 | 158 | 85 | 172.7 | 122.0 | 1.415574 | 1.036899 | 1530.6 | 0.112832 |
12 | 68 | 32 | -622.3 | 90.0 | -6.914444 | 0.836838 | 1629.9 | -0.381803 |
10 | 120 | 111 | -638.0 | 37.0 | -17.243243 | 0.787057 | 1233.4 | -0.517269 |
8 | 153 | 30 | -667.3 | 35.0 | -19.065714 | 0.776284 | 1558.8 | -0.428086 |
7 | 43 | 74 | -749.0 | 39.0 | -19.205128 | 0.770245 | 1766.8 | -0.423930 |
15 | 63 | 104 | -774.3 | 92.0 | -8.416304 | 0.835339 | 1692.6 | -0.457462 |
9 | 154 | 56 | -1296.9 | 58.0 | -22.360345 | 0.675945 | 1733.3 | -0.748226 |
1 | 152 | 24 | -1315.2 | 74.0 | -17.772973 | 0.664627 | 2130.3 | -0.617378 |
19 | 155 | 27 | -1363.8 | 60.0 | -22.730000 | 0.672006 | 1474.4 | -0.924986 |
17 | 109 | 40 | -1478.5 | 62.0 | -23.846774 | 0.651379 | 1784.5 | -0.828523 |
4 | 148 | 122 | -1957.4 | 100.0 | -19.574000 | 0.626400 | 2171.4 | -0.901446 |
Recommended Posts