Pythonでシストレのバックテスト

移動平均交差システムを、以下の日足データを使ってバックテストしてみます。

In [1]:
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# dfのデータからtfで指定するタイムフレームの4本足データを作成する関数
def TF_ohlc(df, tf):
    x = df.resample(tf).ohlc()
    ret = pd.DataFrame({'Open': x['Open']['open'],
                       'High': x['High']['high'],
                       'Low': x['Low']['low'],
                       'Close': x['Close']['close']},
                       columns=['Open','High','Low','Close'])
    return ret.dropna()
    
dataM1 = pd.read_csv('C:\Python\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時間のオフセット

ohlcD1 = TF_ohlc(dataM1, 'D') #日足データの作成
ohlcD1
Out[1]:
Open High Low Close
Time
2015-01-01 1.20965 1.21041 1.20959 1.21002
2015-01-02 1.21037 1.21072 1.20002 1.20009
2015-01-05 1.19427 1.19763 1.18664 1.19327
2015-01-06 1.19329 1.19687 1.18841 1.18893
2015-01-07 1.18894 1.18965 1.18021 1.18392
2015-01-08 1.18377 1.18474 1.17541 1.17916
2015-01-09 1.17930 1.18459 1.17626 1.18407
2015-01-12 1.18499 1.18709 1.17862 1.18334
2015-01-13 1.18331 1.18595 1.17534 1.17722
2015-01-14 1.17722 1.18462 1.17272 1.17887
2015-01-15 1.17888 1.17924 1.15678 1.16299
2015-01-16 1.16304 1.16487 1.14600 1.15620
2015-01-19 1.15516 1.16389 1.15513 1.16019
2015-01-20 1.16068 1.16148 1.15405 1.15485
2015-01-21 1.15483 1.16802 1.15414 1.16092
2015-01-22 1.16095 1.16509 1.13159 1.13647
2015-01-23 1.13649 1.13738 1.11144 1.12024
2015-01-26 1.11511 1.12953 1.10978 1.12386
2015-01-27 1.12377 1.14225 1.12236 1.13805
2015-01-28 1.13761 1.13829 1.12760 1.12863
2015-01-29 1.12862 1.13678 1.12617 1.13192
2015-01-30 1.13183 1.13636 1.12786 1.12839
2015-02-02 1.13105 1.13620 1.12922 1.13411
2015-02-03 1.13407 1.15340 1.13123 1.14785
2015-02-04 1.14788 1.14847 1.13155 1.13433
2015-02-05 1.13433 1.14987 1.13038 1.14768
2015-02-06 1.14763 1.14856 1.13117 1.13154
2015-02-09 1.12964 1.13592 1.12702 1.13231
2015-02-10 1.13229 1.13454 1.12730 1.13202
2015-02-11 1.13200 1.13472 1.12799 1.13354
... ... ... ... ...
2015-11-19 1.06572 1.07630 1.06552 1.07346
2015-11-20 1.07323 1.07384 1.06397 1.06436
2015-11-23 1.06377 1.06568 1.05927 1.06356
2015-11-24 1.06354 1.06731 1.06195 1.06428
2015-11-25 1.06419 1.06892 1.05660 1.06236
2015-11-26 1.06237 1.06273 1.05998 1.06093
2015-11-27 1.06095 1.06380 1.05685 1.05900
2015-11-30 1.05881 1.05949 1.05578 1.05631
2015-12-01 1.05629 1.06370 1.05623 1.06313
2015-12-02 1.06312 1.06356 1.05508 1.06127
2015-12-03 1.06129 1.09811 1.05223 1.09376
2015-12-04 1.09376 1.09562 1.08359 1.08832
2015-12-07 1.08725 1.08876 1.07960 1.08369
2015-12-08 1.08362 1.09021 1.08300 1.08917
2015-12-09 1.08909 1.10425 1.08791 1.10240
2015-12-10 1.10226 1.10245 1.09251 1.09401
2015-12-11 1.09392 1.10308 1.09266 1.09886
2015-12-14 1.09823 1.10486 1.09455 1.09909
2015-12-15 1.09905 1.10594 1.09045 1.09280
2015-12-16 1.09284 1.10110 1.08879 1.09115
2015-12-17 1.09105 1.09138 1.08027 1.08251
2015-12-18 1.08238 1.08744 1.08050 1.08652
2015-12-21 1.08531 1.09389 1.08470 1.09145
2015-12-22 1.09137 1.09841 1.09021 1.09555
2015-12-23 1.09550 1.09565 1.08701 1.09093
2015-12-24 1.09093 1.09676 1.09028 1.09630
2015-12-28 1.09676 1.09924 1.09547 1.09652
2015-12-29 1.09650 1.09915 1.08992 1.09193
2015-12-30 1.09172 1.09436 1.09018 1.09312
2015-12-31 1.09309 1.09372 1.08527 1.08545

260 rows × 4 columns

移動平均交差システムで使うテクニカル指標は、言うまでもなく移動平均です。ここでは、MT4のように色々な移動平均が作れるように、iMA()という関数を作っておきます。関数の引数もMT4のiMA()と似たようなものにしています。また前回の記事では省略したLWMAも実装してみました。

In [2]:
# iMA()関数
def iMA(df, ma_period, ma_shift=0, ma_method='SMA', applied_price='Close'):
    if ma_method == 'SMA':    
        return df[applied_price].rolling(ma_period).mean().shift(ma_shift)
    elif ma_method == 'EMA':
        return df[applied_price].ewm(span=ma_period).mean().shift(ma_shift)
    elif ma_method == 'SMMA':
        return df[applied_price].ewm(alpha=1/ma_period).mean().shift(ma_shift)
    elif ma_method == 'LWMA':
        y = pd.Series(0.0, index=df.index)
        for i in range(len(y)):
            if i<ma_period-1: y[i] = 'NaN'
            else:
                y[i] = 0
                for j in range(ma_period):
                    y[i] += df[applied_price][i-j]*(ma_period-j)
                y[i] /= ma_period*(ma_period+1)/2
        return y.shift(ma_shift)
    else: return df[applied_price].copy().shift(ma_shift)

この関数で最低限必要な引数は、DataFrame型の4本値データと、移動平均の期間です。終値のSMAを取る場合、ほかの引数は省略できます。

まずは、期間の異なる2本の移動平均を作っておきます。ここでは短期移動平均FastMAを期間25のSMA、長期移動平均SlowMAを期間30のSMAとします。

In [3]:
FastMA = iMA(ohlcD1, 25)
SlowMA = iMA(ohlcD1, 30)
pd.DataFrame({'FastMA':FastMA, 'SlowMA':SlowMA})
Out[3]:
FastMA SlowMA
Time
2015-01-01 NaN NaN
2015-01-02 NaN NaN
2015-01-05 NaN NaN
2015-01-06 NaN NaN
2015-01-07 NaN NaN
2015-01-08 NaN NaN
2015-01-09 NaN NaN
2015-01-12 NaN NaN
2015-01-13 NaN NaN
2015-01-14 NaN NaN
2015-01-15 NaN NaN
2015-01-16 NaN NaN
2015-01-19 NaN NaN
2015-01-20 NaN NaN
2015-01-21 NaN NaN
2015-01-22 NaN NaN
2015-01-23 NaN NaN
2015-01-26 NaN NaN
2015-01-27 NaN NaN
2015-01-28 NaN NaN
2015-01-29 NaN NaN
2015-01-30 NaN NaN
2015-02-02 NaN NaN
2015-02-03 NaN NaN
2015-02-04 1.159916 NaN
2015-02-05 1.157422 NaN
2015-02-06 1.154680 NaN
2015-02-09 1.152242 NaN
2015-02-10 1.149965 NaN
2015-02-11 1.147950 1.155833
... ... ...
2015-11-19 1.093822 1.101340
2015-11-20 1.091013 1.098967
2015-11-23 1.088254 1.096563
2015-11-24 1.085454 1.094113
2015-11-25 1.082600 1.091281
2015-11-26 1.080607 1.088702
2015-11-27 1.078899 1.086182
2015-11-30 1.076927 1.083641
2015-12-01 1.075258 1.081269
2015-12-02 1.074025 1.078855
2015-12-03 1.073873 1.078288
2015-12-04 1.073389 1.077842
2015-12-07 1.072678 1.077112
2015-12-08 1.072396 1.076589
2015-12-09 1.073040 1.076932
2015-12-10 1.073268 1.076814
2015-12-11 1.074276 1.076762
2015-12-14 1.075239 1.076683
2015-12-15 1.076056 1.076569
2015-12-16 1.076739 1.076730
2015-12-17 1.076794 1.076537
2015-12-18 1.077172 1.076966
2015-12-21 1.078089 1.077514
2015-12-22 1.079349 1.078286
2015-12-23 1.080355 1.078848
2015-12-24 1.081268 1.079354
2015-12-28 1.082555 1.080002
2015-12-29 1.083690 1.080782
2015-12-30 1.084843 1.081751
2015-12-31 1.085767 1.082406

260 rows × 2 columns

2本の移動平均がちゃんとできたかどうか、チャートに表示させてみます。終値と合わせて表示させると次のようになります。ちょっとギザギザしているのは、チャートの横軸に週末の相場データのない期間が含まれているからです。

In [4]:
Close = ohlcD1['Close']
pd.DataFrame({'Close': Close, 'FastMA': FastMA, 'SlowMA': SlowMA},
            columns=['Close', 'FastMA', 'SlowMA']).plot(figsize=(8,6))
Out[4]:
<matplotlib.axes._subplots.AxesSubplot at 0x28118978cc0>

次に売買ルールですが、短期移動平均が長期移動平均を上抜いたときが買いで、下抜いたときが売りとします。最新のバーにおける移動平均と1本前のバーにおける移動平均の上下関係が逆転した条件が売買シグナルとなります。ただ、最新のバーといっても確定した最新のバーなので、実際には1本前のバーの終値でシグナルが発生して、最新のバーの始値で売買することになります。

売買シグナルは、BuyEntry()SellEntry()という関数として定義しておきます。戻り値は条件文なので、条件が成り立てばTrueが、成り立たなければ、Falseが返ります。またポジションを決済する関数も用意しておきます。今回は途転売買なので、買いポジションの決済は売りシグナル、売りポジションの決済は買いシグナルと同じものです。

In [5]:
def BuyEntry(i): # 買いシグナル
    return FastMA[i] > SlowMA[i] and FastMA[i-1] <= SlowMA[i-1]

def SellEntry(i): # 売りシグナル
    return FastMA[i] < SlowMA[i] and FastMA[i-1] >= SlowMA[i-1]

def BuyExit(i): # 買いポジション決済シグナル
    return SellEntry(i)

def SellExit(i): # 売りポジション決済シグナル
    return BuyEntry(i)

バックテストの部分は、読み込んだ日足データを1日ずつ進めて、その都度、売買シグナルのチェックを行い、仮想売買を行います。ただし、ここではポジションの有無を記録するだけで、損益の計算は行いません。LongPosShortPosという時系列データに、それぞれ買いポジションと売りポジョションの売買ロット数を保存します。

買いポジションがない場合に買いシグナルが出れば、次のバーの始値で買うという意味で、次のバーの買いポジションにロット数を代入します。決済シグナルが出れば、買いポジションを0にします。シグナルが出ない場合は、前のバーのポジション情報を継続します。これを売りポジションの場合も同様にします。

In [6]:
LongPos = pd.Series(0.0, index=ohlcD1.index) # 買いポジション情報
ShortPos = LongPos.copy() # 売りポジション情報
Lots = 0.1 # 売買ロット数
for i in range(1,len(ohlcD1)-2):
    if BuyEntry(i) and LongPos[i] == 0: LongPos[i+1] = Lots # 買いシグナル
    elif BuyExit(i) and LongPos[i] != 0: LongPos[i+1] = 0 # 買いポジション決済
    else: LongPos[i+1] = LongPos[i] # 買いポジション継続  

    if SellEntry(i) and ShortPos[i] == 0: ShortPos[i+1] = Lots # 売りシグナル
    elif SellExit(i) and ShortPos[i] != 0: ShortPos[i+1] = 0 # 売りポジション決済
    else: ShortPos[i+1] = ShortPos[i] # 売りポジション継続

このコードを実行後のLongPosShortPosのデータの一部は以下のようになっています。途転売買なので、買いポジションと売りポジションが交互に表示されています。

In [7]:
pd.DataFrame({'Long':LongPos, 'Short':ShortPos}).tail(50)
Out[7]:
Long Short
Time
2015-10-22 0.0 0.1
2015-10-23 0.0 0.1
2015-10-26 0.0 0.1
2015-10-27 0.0 0.1
2015-10-28 0.0 0.1
2015-10-29 0.0 0.1
2015-10-30 0.1 0.0
2015-11-02 0.1 0.0
2015-11-03 0.1 0.0
2015-11-04 0.0 0.1
2015-11-05 0.0 0.1
2015-11-06 0.0 0.1
2015-11-09 0.0 0.1
2015-11-10 0.0 0.1
2015-11-11 0.0 0.1
2015-11-12 0.0 0.1
2015-11-13 0.0 0.1
2015-11-16 0.0 0.1
2015-11-17 0.0 0.1
2015-11-18 0.0 0.1
2015-11-19 0.0 0.1
2015-11-20 0.0 0.1
2015-11-23 0.0 0.1
2015-11-24 0.0 0.1
2015-11-25 0.0 0.1
2015-11-26 0.0 0.1
2015-11-27 0.0 0.1
2015-11-30 0.0 0.1
2015-12-01 0.0 0.1
2015-12-02 0.0 0.1
2015-12-03 0.0 0.1
2015-12-04 0.0 0.1
2015-12-07 0.0 0.1
2015-12-08 0.0 0.1
2015-12-09 0.0 0.1
2015-12-10 0.0 0.1
2015-12-11 0.0 0.1
2015-12-14 0.0 0.1
2015-12-15 0.0 0.1
2015-12-16 0.0 0.1
2015-12-17 0.1 0.0
2015-12-18 0.1 0.0
2015-12-21 0.1 0.0
2015-12-22 0.1 0.0
2015-12-23 0.1 0.0
2015-12-24 0.1 0.0
2015-12-28 0.1 0.0
2015-12-29 0.1 0.0
2015-12-30 0.1 0.0
2015-12-31 0.0 0.0

次にポジション情報から各トレードにおける損益を計算していきます。今回は簡単のため、実現損益のみを求めます。そのために必要なのは、ポジションを建てた価格と決済した価格ですが、その前に先ほどのポジション情報の時系列データで、1日前との差を取ってみます。それぞれ、BuyPointSellPointという時系列データとします。

In [8]:
BuyPoint = LongPos.diff(1)
SellPoint = ShortPos.diff(1)
pd.DataFrame({'BuyPoint':BuyPoint, 'SellPoint':SellPoint}).tail(50)
Out[8]:
BuyPoint SellPoint
Time
2015-10-22 0.0 0.0
2015-10-23 0.0 0.0
2015-10-26 0.0 0.0
2015-10-27 0.0 0.0
2015-10-28 0.0 0.0
2015-10-29 0.0 0.0
2015-10-30 0.1 -0.1
2015-11-02 0.0 0.0
2015-11-03 0.0 0.0
2015-11-04 -0.1 0.1
2015-11-05 0.0 0.0
2015-11-06 0.0 0.0
2015-11-09 0.0 0.0
2015-11-10 0.0 0.0
2015-11-11 0.0 0.0
2015-11-12 0.0 0.0
2015-11-13 0.0 0.0
2015-11-16 0.0 0.0
2015-11-17 0.0 0.0
2015-11-18 0.0 0.0
2015-11-19 0.0 0.0
2015-11-20 0.0 0.0
2015-11-23 0.0 0.0
2015-11-24 0.0 0.0
2015-11-25 0.0 0.0
2015-11-26 0.0 0.0
2015-11-27 0.0 0.0
2015-11-30 0.0 0.0
2015-12-01 0.0 0.0
2015-12-02 0.0 0.0
2015-12-03 0.0 0.0
2015-12-04 0.0 0.0
2015-12-07 0.0 0.0
2015-12-08 0.0 0.0
2015-12-09 0.0 0.0
2015-12-10 0.0 0.0
2015-12-11 0.0 0.0
2015-12-14 0.0 0.0
2015-12-15 0.0 0.0
2015-12-16 0.0 0.0
2015-12-17 0.1 -0.1
2015-12-18 0.0 0.0
2015-12-21 0.0 0.0
2015-12-22 0.0 0.0
2015-12-23 0.0 0.0
2015-12-24 0.0 0.0
2015-12-28 0.0 0.0
2015-12-29 0.0 0.0
2015-12-30 0.0 0.0
2015-12-31 -0.1 0.0

これを見ると、ポジションを建てた日に0.1が、ポジションを決済した日に-0.1が入っていることがわかります。これを使って、実現損益が発生した日にLongPLShortPLという時系列データに損益を記録します。

買いポジションの場合、BuyPointが0.1の日の始値を買値とし、BuyPointが-0.1の日の始値との差を損益としてLongPLに代入します。売りポジションの場合も同様にして、損益をShortPLに代入します。

In [9]:
LongPL = pd.Series(0.0, index=ohlcD1.index) # 買いポジションの損益
ShortPL = LongPL.copy() # 売りポジションの損益
Spread = 0.0002 # スプレッド
Unit = 100000 # 1ロットの通貨単位
BuyPrice = SellPrice = 0.0 # 売買価格
BuyLots = SellLots = 0.0 # 売買ロット数
Open = ohlcD1['Open'] # 始値

for i in range(1,len(ohlcD1)):
    if BuyPoint[i] > 0:
        BuyPrice = Open[i]+Spread
        BuyLots = LongPos[i]*Unit
        BuyPoint[i] = BuyPrice
    elif BuyPoint[i] < 0:
        ClosePrice = Open[i]
        LongPL[i] = (ClosePrice-BuyPrice)*BuyLots
        BuyPoint[i] = ClosePrice
    else: BuyPoint[i] = 'NaN'

    if SellPoint[i] > 0:
        SellPrice = Open[i]
        SellLots = ShortPos[i]*Unit
        SellPoint[i] = SellPrice
    elif SellPoint[i] < 0:
        ClosePrice = Open[i]+Spread
        ShortPL[i] = (SellPrice-ClosePrice)*SellLots
        SellPoint[i] = ClosePrice
    else: SellPoint[i] = 'NaN'

なお、損益の計算はLongPLShortPLの処理だけでいいのですが、後で売買ポイントをチャートに表示させるときに利用するために、BuyPointSellPointに売買した価格と決済した価格を入れておきます。これらの時系列データは、以下のようになっています。

In [10]:
pd.DataFrame({'BuyPoint':BuyPoint, 'SellPoint':SellPoint, 'LongPL':LongPL, 'ShortPL':ShortPL}).tail(50)
Out[10]:
BuyPoint LongPL SellPoint ShortPL
Time
2015-10-22 NaN 0.0 NaN 0.0
2015-10-23 NaN 0.0 NaN 0.0
2015-10-26 NaN 0.0 NaN 0.0
2015-10-27 NaN 0.0 NaN 0.0
2015-10-28 NaN 0.0 NaN 0.0
2015-10-29 NaN 0.0 NaN 0.0
2015-10-30 1.09780 0.0 1.09780 365.4
2015-11-02 NaN 0.0 NaN 0.0
2015-11-03 NaN 0.0 NaN 0.0
2015-11-04 1.09619 -16.1 1.09619 0.0
2015-11-05 NaN 0.0 NaN 0.0
2015-11-06 NaN 0.0 NaN 0.0
2015-11-09 NaN 0.0 NaN 0.0
2015-11-10 NaN 0.0 NaN 0.0
2015-11-11 NaN 0.0 NaN 0.0
2015-11-12 NaN 0.0 NaN 0.0
2015-11-13 NaN 0.0 NaN 0.0
2015-11-16 NaN 0.0 NaN 0.0
2015-11-17 NaN 0.0 NaN 0.0
2015-11-18 NaN 0.0 NaN 0.0
2015-11-19 NaN 0.0 NaN 0.0
2015-11-20 NaN 0.0 NaN 0.0
2015-11-23 NaN 0.0 NaN 0.0
2015-11-24 NaN 0.0 NaN 0.0
2015-11-25 NaN 0.0 NaN 0.0
2015-11-26 NaN 0.0 NaN 0.0
2015-11-27 NaN 0.0 NaN 0.0
2015-11-30 NaN 0.0 NaN 0.0
2015-12-01 NaN 0.0 NaN 0.0
2015-12-02 NaN 0.0 NaN 0.0
2015-12-03 NaN 0.0 NaN 0.0
2015-12-04 NaN 0.0 NaN 0.0
2015-12-07 NaN 0.0 NaN 0.0
2015-12-08 NaN 0.0 NaN 0.0
2015-12-09 NaN 0.0 NaN 0.0
2015-12-10 NaN 0.0 NaN 0.0
2015-12-11 NaN 0.0 NaN 0.0
2015-12-14 NaN 0.0 NaN 0.0
2015-12-15 NaN 0.0 NaN 0.0
2015-12-16 NaN 0.0 NaN 0.0
2015-12-17 1.09125 0.0 1.09125 49.4
2015-12-18 NaN 0.0 NaN 0.0
2015-12-21 NaN 0.0 NaN 0.0
2015-12-22 NaN 0.0 NaN 0.0
2015-12-23 NaN 0.0 NaN 0.0
2015-12-24 NaN 0.0 NaN 0.0
2015-12-28 NaN 0.0 NaN 0.0
2015-12-29 NaN 0.0 NaN 0.0
2015-12-30 NaN 0.0 NaN 0.0
2015-12-31 1.09309 18.4 NaN 0.0

LongPLShortPLのデータから次のように資産曲線が作成できます。cumsum()という累積データを算出するメソッド関数を使うと簡単です。

In [11]:
Initial = 10000 # 初期資産
Equity = (LongPL+ShortPL).cumsum()+Initial
Equity.plot(figsize=(8,6))
Out[11]:
<matplotlib.axes._subplots.AxesSubplot at 0x2811c059470>

さらに、以下のようにMT4のバックテストのレポートと同じようなデータが算出されます。

In [12]:
LongTrades = BuyPoint.count()//2
LongWinTrades = np.nonzero(LongPL.clip_lower(0).values)[0].size
LongLoseTrades = np.nonzero(LongPL.clip_upper(0).values)[0].size
print('買いトレード数 =', LongTrades)
print('勝トレード数 =', LongWinTrades)
print('最大勝トレード =', LongPL.max())
print('平均勝トレード =', round(LongPL.clip_lower(0).sum()/LongWinTrades, 2))
print('負トレード数 =', LongLoseTrades)
print('最大負トレード =', LongPL.min())
print('平均負トレード =', round(LongPL.clip_upper(0).sum()/LongLoseTrades, 2))
print('勝率 =', round(LongWinTrades/LongTrades*100, 2), '%\n')

ShortTrades = SellPoint.count()//2
ShortWinTrades = np.nonzero(ShortPL.clip_lower(0).values)[0].size
ShortLoseTrades = np.nonzero(ShortPL.clip_upper(0).values)[0].size
print('売りトレード数 =', ShortTrades)
print('勝トレード数 =', ShortWinTrades)
print('最大勝トレード =', ShortPL.max())
print('平均勝トレード =', round(ShortPL.clip_lower(0).sum()/ShortWinTrades, 2))
print('負トレード数 =', ShortLoseTrades)
print('最大負トレード =', ShortPL.min())
print('平均負トレード =', round(ShortPL.clip_upper(0).sum()/ShortLoseTrades, 2))
print('勝率 =', round(ShortWinTrades/ShortTrades*100, 2), '%\n')

Trades = LongTrades + ShortTrades
WinTrades = LongWinTrades+ShortWinTrades
LoseTrades = LongLoseTrades+ShortLoseTrades
print('総トレード数 =', Trades)
print('勝トレード数 =', WinTrades)
print('最大勝トレード =', max(LongPL.max(), ShortPL.max()))
print('平均勝トレード =', round((LongPL.clip_lower(0).sum()+ShortPL.clip_lower(0).sum())/WinTrades, 2))
print('負トレード数 =', LoseTrades)
print('最大負トレード =', min(LongPL.min(), ShortPL.min()))
print('平均負トレード =', round((LongPL.clip_upper(0).sum()+ShortPL.clip_upper(0).sum())/LoseTrades, 2))
print('勝率 =', round(WinTrades/Trades*100, 2), '%\n')

Profit = LongPL.clip_lower(0).sum()+ShortPL.clip_lower(0).sum()
Loss = -(LongPL.clip_upper(0).sum()+ShortPL.clip_upper(0).sum())
PL = Profit-Loss
MDD = (Equity.cummax()-Equity).max()
print('総利益 =', round(Profit, 2))
print('総損失 =', round(Loss, 2))
print('総損益 =', round(PL, 2))
print('プロフィットファクター =', round(Profit/Loss, 2))
print('平均損益 =', round(PL/Trades, 2))
print('最大ドローダウン =', round(MDD, 2))
print('リカバリーファクター =', round(PL/MDD, 2))
買いトレード数 = 8
勝トレード数 = 4
最大勝トレード = 217.6
平均勝トレード = 75.75
負トレード数 = 4
最大負トレード = -252.3
平均負トレード = -84.65
勝率 = 50.0 %

売りトレード数 = 7
勝トレード数 = 4
最大勝トレード = 365.4
平均勝トレード = 188.85
負トレード数 = 3
最大負トレード = -368.9
平均負トレード = -214.43
勝率 = 57.14 %

総トレード数 = 15
勝トレード数 = 8
最大勝トレード = 365.4
平均勝トレード = 132.3
負トレード数 = 7
最大負トレード = -368.9
平均負トレード = -140.27
勝率 = 53.33 %

総利益 = 1058.4
総損失 = 981.9
総損益 = 76.5
プロフィットファクター = 1.08
平均損益 = 5.1
最大ドローダウン = 436.7
リカバリーファクター = 0.18

最後に売買ポイントをチャートに表示させてみます。pandasのプロット関数では、値が'NaN'のところは表示されません。それを利用して、BuyPointSellPointでポジションがない期間は'NaN'のままにし、ポジションがある期間は買値と決済価格の間を直線で補間します。

In [13]:
LongPeriod = ShortPeriod = 0 # 各ポジションの期間
for i in range(len(ohlcD1)):
    if LongPos[i] != 0: LongPeriod += 1 # 買いポジションの期間をカウント
    elif LongPeriod > 0:
        diff = (BuyPoint[i]-BuyPoint[i-LongPeriod])/LongPeriod
        for j in range(i-1, i-LongPeriod, -1):
            BuyPoint[j] = BuyPoint[j+1]-diff # 買いポジションの期間を補間
        LongPeriod = 0

    if ShortPos[i] != 0: ShortPeriod += 1 # 売りポジションの期間をカウント
    elif ShortPeriod > 0:
        diff = (SellPoint[i]-SellPoint[i-ShortPeriod])/ShortPeriod
        for j in range(i-1, i-ShortPeriod, -1):
            SellPoint[j] = SellPoint[j+1]-diff # 売りポジションの期間を補間
        ShortPeriod = 0

この処理の結果、BuyPointSellPointは以下のようになります。

In [14]:
pd.DataFrame({'BuyPoint':BuyPoint, 'SellPoint':SellPoint}).tail(50)
Out[14]:
BuyPoint SellPoint
Time
2015-10-22 NaN 1.129120
2015-10-23 NaN 1.123900
2015-10-26 NaN 1.118680
2015-10-27 NaN 1.113460
2015-10-28 NaN 1.108240
2015-10-29 NaN 1.103020
2015-10-30 1.097800 1.097800
2015-11-02 1.097263 NaN
2015-11-03 1.096727 NaN
2015-11-04 1.096190 1.096190
2015-11-05 NaN 1.096031
2015-11-06 NaN 1.095871
2015-11-09 NaN 1.095712
2015-11-10 NaN 1.095553
2015-11-11 NaN 1.095393
2015-11-12 NaN 1.095234
2015-11-13 NaN 1.095075
2015-11-16 NaN 1.094915
2015-11-17 NaN 1.094756
2015-11-18 NaN 1.094596
2015-11-19 NaN 1.094437
2015-11-20 NaN 1.094278
2015-11-23 NaN 1.094118
2015-11-24 NaN 1.093959
2015-11-25 NaN 1.093800
2015-11-26 NaN 1.093640
2015-11-27 NaN 1.093481
2015-11-30 NaN 1.093322
2015-12-01 NaN 1.093162
2015-12-02 NaN 1.093003
2015-12-03 NaN 1.092844
2015-12-04 NaN 1.092684
2015-12-07 NaN 1.092525
2015-12-08 NaN 1.092365
2015-12-09 NaN 1.092206
2015-12-10 NaN 1.092047
2015-12-11 NaN 1.091887
2015-12-14 NaN 1.091728
2015-12-15 NaN 1.091569
2015-12-16 NaN 1.091409
2015-12-17 1.091250 1.091250
2015-12-18 1.091454 NaN
2015-12-21 1.091659 NaN
2015-12-22 1.091863 NaN
2015-12-23 1.092068 NaN
2015-12-24 1.092272 NaN
2015-12-28 1.092477 NaN
2015-12-29 1.092681 NaN
2015-12-30 1.092886 NaN
2015-12-31 1.093090 NaN

これを表示させると、買いポジションか売りポジションがある期間で、売買ポイントと決済ポイントをラインで結んだチャートになります。終値と合わせて表示させてみたのが下のチャートです。

In [15]:
pd.DataFrame({'Close': Close, 'Long': BuyPoint, 'Short': SellPoint},
            columns=['Close', 'Long', 'Short']).plot(figsize=(8,6), style=[':','-','-'])
Out[15]:
<matplotlib.axes._subplots.AxesSubplot at 0x2811c0caef0>

なんとなく、MT4のバックテストの結果もどきのチャートができました。

今回は、売買と損益計算を別々に行いましたが、売買ルールによっては、売買シグナルに損益が必要になることもあるでしょう。そのあたりはまたの機会に。