FX Systre Parameter Optimization in Python

Backtesting FX Systre with Python Now that we've written the backtesting code, let's try optimizing the parameters of the systole. Optimizing the trading system does not mean deep learning, which is popular nowadays, but simply changing the parameter values of technical indicators to find the one with the highest evaluation value. This is for practicing Python programming.

Preparation

Backtesting FX Systre with Python As with, prepare the historical data of FX. As before, I will make the data for the 2015 hourly chart of EUR / USD.

import numpy as np
import pandas as pd
import indicators as ind #indicators.Import of py

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

For indicators.py, use the one listed on GitHub.

Backtest and its evaluation

Use the same backtest function as last time. Calculate the trading result and profit / loss by inputting historical data and trading signal.

def Backtest(ohlc, BuyEntry, SellEntry, BuyExit, SellExit, lots=0.1, spread=2):
    Open = ohlc['Open'].values #Open price
    Point = 0.0001 #Value of 1pip
    if(Open[0] > 50): Point = 0.01 #1 pip value of cross circle
    Spread = spread*Point #Spread
    Lots = lots*100000 #Actual trading volume
    N = len(ohlc) #FX data size
    BuyExit[N-2] = SellExit[N-2] = True #Finally forced exit
    BuyPrice = SellPrice = 0.0 #Selling price
    
    LongTrade = np.zeros(N) #Buy trade information
    ShortTrade = np.zeros(N) #Sell trade information
    
    LongPL = np.zeros(N) #Profit and Loss of Buy Position
    ShortPL = np.zeros(N) #Sell position profit or loss

    for i in range(1,N):
        if BuyEntry[i-1] and BuyPrice == 0: #Buy entry signal
            BuyPrice = Open[i]+Spread
            LongTrade[i] = BuyPrice #Buy position open
        elif BuyExit[i-1] and BuyPrice != 0: #Buy exit signal
            ClosePrice = Open[i]
            LongTrade[i] = -ClosePrice #Buy position closed
            LongPL[i] = (ClosePrice-BuyPrice)*Lots #Profit and loss settlement
            BuyPrice = 0

        if SellEntry[i-1] and SellPrice == 0: #Sell entry signal
            SellPrice = Open[i]
            ShortTrade[i] = SellPrice #Sell position open
        elif SellExit[i-1] and SellPrice != 0: #Sell exit signal
            ClosePrice = Open[i]+Spread
            ShortTrade[i] = -ClosePrice #Sell position closed
            ShortPL[i] = (SellPrice-ClosePrice)*Lots #Profit and loss settlement
            SellPrice = 0

    return pd.DataFrame({'Long':LongTrade, 'Short':ShortTrade}, index=ohlc.index),\
            pd.DataFrame({'Long':LongPL, 'Short':ShortPL}, index=ohlc.index)

To evaluate the system, use the following functions to calculate the total profit / loss, the number of transactions, the average profit / loss, the profit factor, the maximum drawdown, and the recovery factor. It is almost the same as the output by MetaTrader optimization.

def BacktestReport(Trade, PL):
    LongPL = PL['Long']
    ShortPL = PL['Short']
    LongTrades = np.count_nonzero(Trade['Long'])//2
    ShortTrades = np.count_nonzero(Trade['Short'])//2
    GrossProfit = LongPL.clip_lower(0).sum()+ShortPL.clip_lower(0).sum()
    GrossLoss = LongPL.clip_upper(0).sum()+ShortPL.clip_upper(0).sum()
    #Total profit and loss
    Profit = GrossProfit+GrossLoss
    #Number of transactions
    Trades = LongTrades+ShortTrades
    #Average profit and loss
    if Trades==0: Average = 0
    else: Average = Profit/Trades
    #Profit factor
    if GrossLoss==0: PF=100
    else: PF = -GrossProfit/GrossLoss
    #Maximum drawdown
    Equity = (LongPL+ShortPL).cumsum()
    MDD = (Equity.cummax()-Equity).max()
    #Recovery factor
    if MDD==0: RF=100
    else: RF = Profit/MDD
    return np.array([Profit, Trades, Average, PF, MDD, RF])

Parameters to be optimized and their range

In the previous backtest, the long-term moving average period was fixed at 30 and the short-term moving average period was fixed at 10, but in this optimization, we will change these two periods.

The period of change is 10 to 50 for the long-term moving average and 5 to 30 for the short-term moving average. Put it in the array as follows.

SlowMAperiod = np.arange(10, 51) #Range of long-term moving average period
FastMAperiod = np.arange(5, 31)  #Range of short-term moving averages

There are 41 and 26 ways for each period, but the combination of the two periods is $ 41 \ times 26 = 1066 $.

optimisation

Optimize by substituting the period range of this parameter. As the number of combination of periods increases, the calculation time cannot be ignored, so it is necessary to eliminate unnecessary calculations as much as possible.

For the time being, calculate the time series of 41 and 26 moving averages in advance. Then, for 1066 combinations, buy / sell signals are generated, backtested, and evaluated, and parameter values and evaluation values are output. An example of the code is as follows.

def Optimize(ohlc, SlowMAperiod, FastMAperiod):
    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])
    
    N = len(SlowMAperiod)*len(FastMAperiod)
    Eval = np.empty([N, 6]) #Evaluation item
    Slow = np.empty(N) #Long-term moving average period
    Fast = np.empty(N) #Short-term moving average period
    def shift(x, n=1): return np.concatenate((np.zeros(n), x[:-n])) #Shift function
    k = 0
    for i in range(len(SlowMAperiod)):
        for j in range(len(FastMAperiod)):
            #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)
            Slow[k] = SlowMAperiod[i]
            Fast[k] = FastMAperiod[j]
            k += 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'])
            
result = Optimize(ohlc, SlowMAperiod, FastMAperiod)

I was worried about the calculation time, but it took about 12 seconds with a Core i5-3337U 1.8GHz CPU. I tried optimizing the same conditions with MetaTrader 5, but it took nearly 50 seconds, so I think it was a fairly practical speed for Python.

The result of optimization

You can find the optimum parameter value by sorting the optimization results by the item you like. For example, if you sort by total profit / loss, it will be as follows.

result.sort_values('Profit', ascending=False).head(20)
        Slow  Fast  Profit  Trades   Average        PF     MDD        RF
   445  27.0   8.0  2507.1   264.0  9.496591  1.423497   485.1  5.168213
   470  28.0   7.0  2486.0   260.0  9.561538  1.419642   481.2  5.166251
   446  27.0   9.0  2263.3   252.0  8.981349  1.376432   624.7  3.623019
   444  27.0   7.0  2171.4   272.0  7.983088  1.341276   504.7  4.302358
   471  28.0   8.0  2102.3   250.0  8.409200  1.359030   540.3  3.890986
   497  29.0   8.0  2093.3   242.0  8.650000  1.365208   603.8  3.466876
   495  29.0   6.0  2063.5   256.0  8.060547  1.342172   620.6  3.325008
   498  29.0   9.0  2053.5   238.0  8.628151  1.362451   686.5  2.991260
   546  31.0   5.0  1959.4   254.0  7.714173  1.344256   529.7  3.699075
   520  30.0   5.0  1940.3   276.0  7.030072  1.313538   681.7  2.846267
   496  29.0   7.0  1931.5   248.0  7.788306  1.322891   611.3  3.159660
   422  26.0  11.0  1903.4   248.0  7.675000  1.309702   708.7  2.685763
   523  30.0   8.0  1903.0   232.0  8.202586  1.327680   823.9  2.309746
   524  30.0   9.0  1875.8   234.0  8.016239  1.328598   908.6  2.064495
   573  32.0   6.0  1820.8   242.0  7.523967  1.320688   639.8  2.845889
   420  26.0   9.0  1819.1   258.0  7.050775  1.282035   667.0  2.727286
   572  32.0   5.0  1808.2   256.0  7.063281  1.313564   522.9  3.458023
   598  33.0   5.0  1799.6   248.0  7.256452  1.317183   613.2  2.934768
   419  26.0   8.0  1777.4   274.0  6.486861  1.273817   552.7  3.215849
   434  26.0  23.0  1739.6   368.0  4.727174  1.241049  1235.5  1.408013

From this, the values of the parameters that maximize the total profit and loss are 27 for the long-term moving average period and 8 for the short-term moving average period.

As a test, the asset curve backtested during this period looks like this: chart.png

Sounds good. However, it is natural that such a result can be obtained by optimizing the parameters, and it is not very pleasing. You just have to backtest for another period and be disappointed.

This time, it's okay to get faster results than MetaTrader's backtest. MetaTrader can also backtest in tick units, but I think it will take a long time to do it in Python. There is still a long way to go.

Recommended Posts

FX Systre Parameter Optimization in Python
Backtesting FX Systre in Python (1)
Python in optimization
Use parameter store in Python
Backtesting FX Systre with Python (2)
Solve optimization problems in Python
Transfer parameter values in Python
HMM parameter estimation implementation in python
Bayesian optimization package GPyOpt in Python
[FX] Hit oanda-API in Python using Docker
I tried using Bayesian Optimization in Python
Quadtree in Python --2
CURL in python
Geocoding in python
SendKeys in Python
Meta-analysis in Python
Unittest in python
Epoch in Python
Discord in Python
Sudoku in Python
DCI in Python
quicksort in python
nCr in python
N-Gram in Python
Programming in python
Plink in Python
Constant in python
Lifegame in Python.
FizzBuzz in Python
Sqlite in python
StepAIC in Python
N-gram in python
LINE-Bot [0] in Python
Csv in python
Disassemble in Python
Reflection in Python
Constant in python
nCr in Python.
format in python
Scons in Python3
Puyo Puyo in python
python in virtualenv
PPAP in Python
Quad-tree in Python
Reflection in Python
Chemistry in Python
Hashable in python
DirectLiNGAM in Python
LiNGAM in Python
Flatten in python
flatten in python
Display candlesticks for FX (forex) data in Python
Sorted list in Python
Daily AtCoder # 36 in Python
Clustering text in Python
Daily AtCoder # 2 in Python
Daily AtCoder # 32 in Python
Daily AtCoder # 6 in Python
Daily AtCoder # 18 in Python
Edit fonts in Python
Singleton pattern in Python