Backtesting FX Systre avec Python (1)

introduction

Il existe de nombreuses bibliothèques qui testent Systre avec Python, mais il est difficile pour les personnes qui sont entrées depuis MetaTrader de comprendre, alors j'ai écrit le code pour le backtest après avoir pratiqué Python.

Système de backtesting avec Python

Cependant, dans la première version, j'ai écrit que cela fonctionne en premier, donc c'était assez coûteux et la vitesse d'exécution était lente, alors j'ai essayé de l'améliorer un peu cette fois.

Acquisition des données historiques FX

Dans le cas des cours boursiers, il y a beaucoup de choses qui peuvent être téléchargées directement à partir de Yahoo! Etc., mais dans le cas de FX, des données de plusieurs périodes de temps telles que 5 minutes ou 15 minutes peuvent être utilisées, donc 1 minute comme données de base. Je veux des données sur les pieds.

Dans ce cas, les données sont également volumineuses, donc je pense qu'il est plus pratique de lire les données téléchargées à l'avance. Ici, téléchargez-le comme exemple de données à partir du site suivant.

HistData.com

Il existe plusieurs formats pour les mêmes données, mais je vais télécharger les données au format générique ASCII car j'utilise la fonction pandas.

Maintenant, lisons les données téléchargées sur 1 minute EUR / USD pour 2015'DAT_ASCII_EURUSD_M1_2015.csv 'avec la fonction pandas. Cependant, si cela reste tel quel, le prix du marché en début de semaine commencera le dimanche, donc je vais le retarder de 7 heures et commencer à minuit le lundi.

import numpy as np
import pandas as pd

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) #Décalage de 7 heures

Création de données de période arbitraire

Ensuite, créez des données pour n'importe quel intervalle de temps à partir des données d'une minute. Encore une fois, vous pouvez facilement le faire en utilisant les fonctions pandas resample (), ʻohlc () `.

#Une fonction qui crée des données à quatre pattes pour une période spécifiée par tf à partir de données df
def TF_ohlc(df, tf):
    x = df.resample(tf).ohlc()
    O = x['Open']['open']
    H = x['High']['high']
    L = x['Low']['low']
    C = x['Close']['close']
    ret = pd.DataFrame({'Open': O, 'High': H, 'Low': L, 'Close': C},
                       columns=['Open','High','Low','Close'])
    return ret.dropna()

ohlc = TF_ohlc(dataM1, 'H') #Création de données horaires

Le mot-clé spécifié pour tf est

Donc, pendant 15 minutes, écrivez simplement `` 15T ''. Avec cela, vous pouvez créer même des minutes de données. Ici, j'ai fait des données d'une heure.

Créer des indicateurs techniques

Les indicateurs techniques utilisés dans les règles de trading de Systre sont créés sur GitHub. Vous pouvez l'utiliser en récupérant uniquement les indicateurs.py. Je vais omettre le code source, mais j'ai évité d'utiliser des pandas pour les itérations internes et utilisé Numba, donc je pense qu'il n'y a plus de fonctions qui prennent du temps. ~~ Cependant, le SAR parabolique prend un certain temps en raison de l'algorithme. ~~

Par exemple, une moyenne mobile de 10 barres et une moyenne mobile de 30 barres peuvent être écrites comme suit:

import indicators as ind #indicators.Importation de py
FastMA = ind.iMA(ohlc, 10) #Moyenne mobile à court terme
SlowMA = ind.iMA(ohlc, 30) #Moyenne mobile à long terme

Affichage des indicateurs techniques

Vous pouvez utiliser la fonction de tracé des pandas pour afficher l'index technique créé sur le graphique, mais comme vous voudrez peut-être l'agrandir ou le réduire, vous le voyez souvent sur les sites liés au marché HighCharts. Utilisons la bibliothèque Python affichée dans /).

Ici, je vais utiliser le facile à installer pandas-highcharts.

L'installation est

pip install pandas-highcharts

Est OK. Le code pour afficher le cours de clôture du FX et les deux moyennes mobiles est le suivant.

from pandas_highcharts.display import display_charts
df = pd.DataFrame({'Close': ohlc['Close'], 'FastMA': FastMA, 'SlowMA': SlowMA})
display_charts(df, chart_type="stock", title="MA cross", figsize=(640,480), grid=True)

Lorsque vous faites cela, vous verrez un graphique comme celui-ci.

chart_y.png

Si vous affichez la valeur d'un an, les moyennes mobiles se chevaucheront à peine, donc si vous effectuez un zoom avant correctement, cela ressemblera à ceci, et vous pourrez voir la différence dans les moyennes mobiles.

chart_m.png

C'est assez pratique.

Règles de négociation pour le système de croisement de moyenne mobile

Comme exemple de système de trading, nous prendrons le système classique de croisement de moyenne mobile. Les règles d'achat et de vente sont

C'est aussi simple que ça. Ce signal est un signal d'entrée, et le signal de sortie pour fermer une position est que la sortie d'achat est la même que l'entrée de vente et la sortie de vente est la même que l'entrée d'achat. C'est ce qu'on appelle le trading de transfert.

#Acheter le signal d'entrée
BuyEntry = ((FastMA > SlowMA) & (FastMA.shift() <= SlowMA.shift())).values
#Vendre un signal d'entrée
SellEntry = ((FastMA < SlowMA) & (FastMA.shift() >= SlowMA.shift())).values
#Acheter un signal de sortie
BuyExit = SellEntry.copy()
#Vendre un signal de sortie
SellExit = BuyEntry.copy()

Ici, chaque signal est un tableau de type booléen de numpy. Si vous y réfléchissez normalement, vous jugeriez probablement le signal en tournant les données de la série chronologique, mais dans le cas de Python, il est plus rapide de les traiter collectivement dans un tableau, alors mettez le signal dans un tableau.

Exécuter un backtest

Nous allons enfin effectuer un backtest. Entrez les données historiques et le signal de négociation ci-dessus, et sortez le prix de négociation réel et le profit / perte sous forme de données de série chronologique.

def Backtest(ohlc, BuyEntry, SellEntry, BuyExit, SellExit, lots=0.1, spread=2):
    Open = ohlc['Open'].values #Prix ouvert
    Point = 0.0001 #Valeur de 1pip
    if(Open[0] > 50): Point = 0.01 #1 valeur pip du cercle croisé
    Spread = spread*Point #Propagé
    Lots = lots*100000 #Volume d'échange réel
    N = len(ohlc) #Taille des données FX
    BuyExit[N-2] = SellExit[N-2] = True #Enfin sortie forcée
    BuyPrice = SellPrice = 0.0 #Prix de vente
    
    LongTrade = np.zeros(N) #Acheter des informations commerciales
    ShortTrade = np.zeros(N) #Vendre des informations commerciales
    
    LongPL = np.zeros(N) #Gain / perte de position d'achat
    ShortPL = np.zeros(N) #Gain / perte de position de vente

    for i in range(1,N):
        if BuyEntry[i-1] and BuyPrice == 0: #Acheter le signal d'entrée
            BuyPrice = Open[i]+Spread
            LongTrade[i] = BuyPrice #Achat de position ouverte
        elif BuyExit[i-1] and BuyPrice != 0: #Acheter un signal de sortie
            ClosePrice = Open[i]
            LongTrade[i] = -ClosePrice #Position d'achat fermée
            LongPL[i] = (ClosePrice-BuyPrice)*Lots #Règlement des profits et pertes
            BuyPrice = 0

        if SellEntry[i-1] and SellPrice == 0: #Vendre un signal d'entrée
            SellPrice = Open[i]
            ShortTrade[i] = SellPrice #Position de vente ouverte
        elif SellExit[i-1] and SellPrice != 0: #Vendre un signal de sortie
            ClosePrice = Open[i]+Spread
            ShortTrade[i] = -ClosePrice #Position de vente fermée
            ShortPL[i] = (SellPrice-ClosePrice)*Lots #Règlement des profits et pertes
            SellPrice = 0

    return pd.DataFrame({'Long':LongTrade, 'Short':ShortTrade}, index=ohlc.index),\
            pd.DataFrame({'Long':LongPL, 'Short':ShortPL}, index=ohlc.index)
        
Trade, PL = Backtest(ohlc, BuyEntry, SellEntry, BuyExit, SellExit)

Dans la version précédente, cette partie était divisée en plusieurs étapes, mais cette fois j'ai essayé de la résumer. À l'origine, ce serait bien si nous ne pouvions négocier qu'avec des signaux, mais il y a des cas où des signaux sont émis même s'il y a des positions, nous avons donc également vérifié l'existence de positions.

Dans la sortie de cette fonction, «Commerce» stocke le prix d'achat / de vente comme une valeur positive et le prix de règlement comme une valeur négative pour chaque achat et vente. Les gains et pertes réalisés au moment du règlement sont stockés dans «PL».

Évaluation du système commercial

Avec les informations sur «Commerce» et «PL», vous pouvez évaluer approximativement le système commercial. Par exemple, cela ressemble à ceci.

def BacktestReport(Trade, PL):
    LongPL = PL['Long']
    LongTrades = np.count_nonzero(Trade['Long'])//2
    LongWinTrades = np.count_nonzero(LongPL.clip_lower(0))
    LongLoseTrades = np.count_nonzero(LongPL.clip_upper(0))
    print('Nombre de transactions d'achat=', LongTrades)
    print('Nombre de trades gagnants=', LongWinTrades)
    print('Échange de gains maximum=', LongPL.max())
    print('Échange de gains moyen=', round(LongPL.clip_lower(0).sum()/LongWinTrades, 2))
    print('Nombre de transactions négatives=', LongLoseTrades)
    print('Échange négatif maximum=', LongPL.min())
    print('Commerce négatif moyen=', round(LongPL.clip_upper(0).sum()/LongLoseTrades, 2))
    print('Taux de réussite=', round(LongWinTrades/LongTrades*100, 2), '%\n')

    ShortPL = PL['Short']
    ShortTrades = np.count_nonzero(Trade['Short'])//2
    ShortWinTrades = np.count_nonzero(ShortPL.clip_lower(0))
    ShortLoseTrades = np.count_nonzero(ShortPL.clip_upper(0))
    print('Nombre de transactions de vente=', ShortTrades)
    print('Nombre de trades gagnants=', ShortWinTrades)
    print('Échange de gains maximum=', ShortPL.max())
    print('Échange de gains moyen=', round(ShortPL.clip_lower(0).sum()/ShortWinTrades, 2))
    print('Nombre de transactions négatives=', ShortLoseTrades)
    print('Échange négatif maximum=', ShortPL.min())
    print('Commerce négatif moyen=', round(ShortPL.clip_upper(0).sum()/ShortLoseTrades, 2))
    print('Taux de réussite=', round(ShortWinTrades/ShortTrades*100, 2), '%\n')

    Trades = LongTrades + ShortTrades
    WinTrades = LongWinTrades+ShortWinTrades
    LoseTrades = LongLoseTrades+ShortLoseTrades
    print('Nombre total de transactions=', Trades)
    print('Nombre de trades gagnants=', WinTrades)
    print('Échange de gains maximum=', max(LongPL.max(), ShortPL.max()))
    print('Échange de gains moyen=', round((LongPL.clip_lower(0).sum()+ShortPL.clip_lower(0).sum())/WinTrades, 2))
    print('Nombre de transactions négatives=', LoseTrades)
    print('Échange négatif maximum=', min(LongPL.min(), ShortPL.min()))
    print('Commerce négatif moyen=', round((LongPL.clip_upper(0).sum()+ShortPL.clip_upper(0).sum())/LoseTrades, 2))
    print('Taux de réussite=', round(WinTrades/Trades*100, 2), '%\n')

    GrossProfit = LongPL.clip_lower(0).sum()+ShortPL.clip_lower(0).sum()
    GrossLoss = LongPL.clip_upper(0).sum()+ShortPL.clip_upper(0).sum()
    Profit = GrossProfit+GrossLoss
    Equity = (LongPL+ShortPL).cumsum()
    MDD = (Equity.cummax()-Equity).max()
    print('Bénéfice total=', round(GrossProfit, 2))
    print('Perte totale=', round(GrossLoss, 2))
    print('Total des profits et pertes=', round(Profit, 2))
    print('Facteur de profit=', round(-GrossProfit/GrossLoss, 2))
    print('Bénéfice / perte moyen=', round(Profit/Trades, 2))
    print('Tirage maximum=', round(MDD, 2))
    print('Facteur de récupération=', round(Profit/MDD, 2))
    return Equity
Equity = BacktestReport(Trade, PL)
Nombre de transactions d'achat= 113
Nombre de trades gagnants= 39
Échange de gains maximum= 440.4
Échange de gains moyen= 82.57
Nombre de transactions négatives= 74
Échange négatif maximum= -169.4
Commerce négatif moyen= -43.89
Taux de réussite= 34.51 %

Nombre de transactions de vente= 113
Nombre de trades gagnants= 49
Échange de gains maximum= 327.6
Échange de gains moyen= 78.71
Nombre de transactions négatives= 64
Échange négatif maximum= -238.5
Commerce négatif moyen= -43.85
Taux de réussite= 43.36 %

Nombre total de transactions= 226
Nombre de trades gagnants= 88
Échange de gains maximum= 440.4
Échange de gains moyen= 80.42
Nombre de transactions négatives= 138
Échange négatif maximum= -238.5
Commerce négatif moyen= -43.87
Taux de réussite= 38.94 %

Bénéfice total= 7077.0
Perte totale= -6054.4
Total des profits et pertes= 1022.6
Facteur de profit= 1.17
Bénéfice / perte moyen= 4.52
Tirage maximum= 1125.4
Facteur de récupération= 0.91

Ici, chaque élément est affiché, mais lors de l'exécution de l'apprentissage automatique ou de l'optimisation, il est uniquement nécessaire de calculer la valeur d'évaluation.

Vous voudrez souvent voir la courbe des actifs comme une évaluation du système. Dans la fonction ci-dessus, la courbe d'actif est sortie en tant que ʻEquity`, donc si vous ajoutez les actifs initiaux et créez un graphique, ce sera comme suit.

Initial = 10000 #Actifs initiaux
display_charts(pd.DataFrame({'Equity':Equity+Initial}), chart_type="stock", title="Courbe des actifs", figsize=(640,480), grid=True)

equity.png

Affichage du graphique des échanges

Enfin, montrons où vous avez acheté et vendu sur le graphique. Il est normal d'afficher uniquement les points que vous avez achetés et vendus, mais je ne sais pas comment le faire dans HighCharts, alors je vais essayer de relier les points qui se sont ouverts et les points qui ont fermé la position avec une ligne.

Convertissons les informations Trade en une ligne avec le code suivant.

def PositionLine(trade):
    PosPeriod = 0 #Période de poste
    Position = False #Présence ou absence de poste
    Line = trade.copy()
    for i in range(len(Line)):
        if trade[i] > 0: Position = True 
        elif Position: PosPeriod += 1 #Compter la durée d'un poste
        if trade[i] < 0:
            if PosPeriod > 0:
                Line[i] = -trade[i]
                diff = (Line[i]-Line[i-PosPeriod])/PosPeriod
                for j in range(i-1, i-PosPeriod, -1):
                    Line[j] = Line[j+1]-diff #Interpoler la durée du poste
                PosPeriod = 0
                Position = False
        if trade[i] == 0 and not Position: Line[i] = 'NaN'
    return Line

df = pd.DataFrame({'Open': ohlc['Open'],
                   'Long': PositionLine(Trade['Long'].values),
                   'Short': PositionLine(Trade['Short'].values)})
display_charts(df, chart_type="stock", title="Graphique du commerce", figsize=(640,480), grid=True)

Il sera affiché dans les graphiques élevés de la même manière, essayez donc de zoomer correctement.

trade.png

Puisqu'il s'agit d'un système de transfert, vous pouvez voir que la position longue et la position courte apparaissent alternativement.

Résumé

J'ai écrit le code en Python pour backtester le système afin que les règles de trading ne puissent être décrites que par des indicateurs techniques. La bibliothèque qui peut afficher des graphiques de Python à HighCharts était assez pratique. Il y a encore place à l'amélioration, alors j'aimerais continuer à écrire quand j'en ai envie.

La suite est ici. Backtesting FX System avec Python (2)

Le code publié dans cet article a été téléchargé ci-dessous. MT5IndicatorsPy/EA_sample.ipynb

Recommended Posts

Backtesting FX Systre avec Python (1)
Backtesting FX Systre avec Python (2)
Optimisation des paramètres de systole FX en Python
Quadtree en Python --2
CURL en Python
Métaprogrammation avec Python
Python 3.3 avec Anaconda
Géocodage en python
SendKeys en Python
Méta-analyse en Python
Unittest en Python
[FX] Hit oanda-API avec Python en utilisant Docker
Discord en Python
DCI en Python
tri rapide en python
nCr en python
N-Gram en Python
Programmation avec Python
Plink en Python
Constante en Python
Sqlite en Python
Étape AIC en Python
LINE-Bot [0] en Python
CSV en Python
Assemblage inversé avec Python
Réflexion en Python
Constante en Python
nCr en Python.
format en python
Scons en Python 3
Puyopuyo en python
python dans virtualenv
PPAP en Python
Réflexion en Python
Chimie avec Python
Hashable en Python
DirectLiNGAM en Python
LiNGAM en Python
Aplatir en Python
Aplatir en python
Afficher la bougie de données FX (forex) en Python
Liste triée en Python
AtCoder # 36 quotidien avec Python
Texte de cluster en Python
AtCoder # 2 tous les jours avec Python
Daily AtCoder # 32 en Python
Daily AtCoder # 6 en Python
Modifier les polices en Python
Motif singleton en Python
Opérations sur les fichiers en Python
Lire DXF avec python
Daily AtCoder # 53 en Python
Séquence de touches en Python
Utilisez config.ini avec Python
Daily AtCoder # 33 en Python
Résoudre ABC168D en Python
Distribution logistique en Python
AtCoder # 7 tous les jours avec Python