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

この1分足を使ってみます

In [2]:
hst_path = 'USDJPY1.hst'
with open(hst_path, 'rb') as f:
    dtype = [('Time','u8'), ('Open','f8'), ('High','f8'), ('Low','f8'),
             ('Close','f8'), ('Volume','i8'), ('s','i4'), ('r','i8')]
    df = pd.DataFrame(np.frombuffer(f.read()[148:], dtype=dtype).astype(dtype[:-3]))
    df = df.set_index(pd.to_datetime(df['Time'], unit='s')).drop('Time', axis=1)

print(len(df))
df['Close'].resample('W').last().dropna().plot(figsize=(15,5))
3779171
Out[2]:
<matplotlib.axes._subplots.AxesSubplot at 0xac0b7b8>

サンプルdllのソースはこんな感じ(´・ω・`)

これを MinGW g++ でコンパイルした(64bitのやつ)
こんな感じ
g++ -shared -o simple_backtest.dll simple_backtest.cpp -Wl,-k -static -O2

※この例だとポジションの価格に使う始値しか渡してないけど
高値安値、利食い、損切り、トレール幅、等を渡すようにすれば
それらのチェックができるように作り変えられると思います

In [3]:
"""
#include <stdint.h>
#define EXPORT extern "C" __declspec(dllexport)
#define NONE   0
#define BUY    1
#define SELL  -1

EXPORT void backtest(uint64_t *tradetime,
                     double   *tradeprice,
                     bool     *openbuy,
                     bool     *opensell,
                     bool     *closebuy,
                     bool     *closesell,
                     int      *postype,
                     uint64_t *opentime,
                     double   *openprice,
                     uint64_t *closetime,
                     double   *closeprice,
                     double   *profit,
                     long     n)
{
    int pos = NONE;
    int posindex = 0;
    
    for(int i=0; i<n-1; i++){
        if((pos==BUY && closebuy[i]) || (pos==SELL && closesell[i])){
            closetime [posindex] = tradetime[i+1];
            closeprice[posindex] = tradeprice[i+1];
            profit    [posindex] = (closeprice[posindex]-openprice[posindex]) * (pos==BUY? 1: -1);
            pos = NONE;
            posindex++;
        }
        if(pos==NONE){
            if(openbuy[i] ) pos=BUY;
            if(opensell[i]) pos=SELL;
            if(pos!=NONE){
                postype  [posindex] = pos;
                opentime [posindex] = tradetime[i+1];
                openprice[posindex] = tradeprice[i+1];
            }
        }
    }
    
    if(pos!=NONE){
        closetime [posindex] = tradetime[n-1];
        closeprice[posindex] = tradeprice[n-1];
        profit    [posindex] = (closeprice[posindex]-openprice[posindex]) * (pos==BUY? 1: -1);
    }
}

""";

インジの算出は豊嶋先生のindicators.pyを使います

https://github.com/toyolab/MT5IndicatorsPy/blob/master/indicators.py

※このipynbファイルと同じディレクトリにindicators.pyがあるものとしてimportしています

In [4]:
import indicators as ind
plt.figure(figsize=(15,7))
d1close = df[['Close']].resample('D').last().dropna()
d1close.plot(c='k', ax=plt.subplot2grid((10,1), (0,0), 7, 1))
ind.iMA(d1close, 100).plot(c='g')
ind.iMA(d1close, 200).plot(c='r')
plt.grid()
ind.iRSI(d1close, 14).plot(c='dodgerblue', ax=plt.subplot2grid((10,1), (7,0), 3, 1))
plt.grid()
In [5]:
class CTester(object):
    
    def __init__(self, df, dllpath):
        self.df = df if df['Open'].flags.c_contiguous else df.copy()
        self.Index = self.df.index.values
        self.tradetime = self.Index.astype(np.uint64)
        self.tradeprice = self.df['Open'].values
        [setattr(self, c, self.df[c].values) for c in self.df.columns]
        
        lib = ctypes.windll.LoadLibrary(dllpath)
        type_uint64_t = np.ctypeslib.ndpointer(dtype=np.uint64)
        type_double   = np.ctypeslib.ndpointer(dtype=np.float64)
        type_int      = np.ctypeslib.ndpointer(dtype=int)
        type_bool     = np.ctypeslib.ndpointer(dtype=np.bool)
        lib.backtest.argtypes = [type_uint64_t, # tradetime
                                 type_double,   # tradeprice
                                 type_bool,     # ob
                                 type_bool,     # os
                                 type_bool,     # cb
                                 type_bool,     # cs
                                 type_int,      # postype
                                 type_uint64_t, # opentime
                                 type_double,   # openprice
                                 type_uint64_t, # closetime
                                 type_double,   # closeprice
                                 type_double,   # profit
                                 ctypes.c_long] # n
        lib.backtest.restype = None
        self.backtest = lib.backtest
        self.lib_handle = lib._handle
        
    def __del__(self):
        ctypes.windll.kernel32.FreeLibrary(self.lib_handle)
    
    def run(self):
        n          = len(self.tradetime)
        postype    = np.zeros(n, dtype=int)
        opentime   = np.zeros(n, dtype=np.uint64)
        openprice  = np.zeros(n, dtype=np.float64)
        closetime  = np.zeros(n, dtype=np.uint64)
        closeprice = np.zeros(n, dtype=np.float64)
        profit     = np.zeros(n, dtype=np.float64)
        
        self.backtest(self.tradetime,
                      self.tradeprice,
                      self.ob,
                      self.os,
                      self.cb,
                      self.cs,
                      postype,
                      opentime,
                      openprice,
                      closetime,
                      closeprice,
                      profit,
                      n)
        
        mask = (postype!=0)
        return DataFrame({'pos': postype[mask],
                          'ot' : pd.to_datetime(opentime[mask], unit='ns'),
                          'op' : openprice[mask],
                          'ct' : pd.to_datetime(closetime[mask], unit='ns'),
                          'cp' : closeprice[mask],
                          'pl' : profit[mask]})['pos ot op ct cp pl'.split()]

class MA(CTester):
    def __init__(self, df, dllpath):
        super(MA, self).__init__(df, dllpath)
        self.set_signal()
    
    def set_signal(self, f_period=500, s_period=1000):
        maf = ind.iMA(self.df, f_period).values
        mas = ind.iMA(self.df, s_period).values
        self.ob = maf > mas
        self.os = maf < mas
        self.cb = self.os
        self.cs = self.ob

dllpath = 'simple_backtest.dll'
%time bt = MA(df, dllpath)
%time res = bt.run()
print('df len:{}'.format(len(df)))
print('trades:{}'.format(len(res)))
res.tail(10)
Wall time: 120 ms
Wall time: 17 ms
df len:3779171
trades:4078
Out[5]:
pos ot op ct cp pl
4068 -1 2017-02-22 04:30:00 113.575 2017-02-23 06:59:00 113.313 0.262
4069 1 2017-02-23 06:59:00 113.313 2017-02-23 15:41:00 113.233 -0.080
4070 -1 2017-02-23 15:41:00 113.233 2017-02-24 13:42:00 112.746 0.487
4071 1 2017-02-24 13:42:00 112.746 2017-02-24 19:31:00 112.358 -0.388
4072 -1 2017-02-24 19:31:00 112.358 2017-02-27 17:45:00 112.292 0.066
4073 1 2017-02-27 17:45:00 112.292 2017-02-28 17:26:00 112.444 0.152
4074 -1 2017-02-28 17:26:00 112.444 2017-03-01 08:31:00 112.929 -0.485
4075 1 2017-03-01 08:31:00 112.929 2017-03-03 11:59:00 114.242 1.313
4076 -1 2017-03-03 11:59:00 114.242 2017-03-03 22:32:00 114.355 -0.113
4077 1 2017-03-03 22:32:00 114.355 2017-03-04 06:59:00 113.976 -0.379
In [6]:
%time bt.set_signal(1000, 2000)
%time res = bt.run()
print('df len:{}'.format(len(df)))
print('trades:{}'.format(len(res)))
res.tail(10)
Wall time: 91 ms
Wall time: 17 ms
df len:3779171
trades:2024
Out[6]:
pos ot op ct cp pl
2014 1 2017-02-09 19:47:00 112.278 2017-02-14 14:03:00 113.421 1.143
2015 -1 2017-02-14 14:03:00 113.421 2017-02-15 06:04:00 114.230 -0.809
2016 1 2017-02-15 06:04:00 114.230 2017-02-16 10:36:00 114.022 -0.208
2017 -1 2017-02-16 10:36:00 114.022 2017-02-20 21:04:00 113.116 0.906
2018 1 2017-02-20 21:04:00 113.116 2017-02-22 16:25:00 113.419 0.303
2019 -1 2017-02-22 16:25:00 113.419 2017-02-28 03:36:00 112.655 0.764
2020 1 2017-02-28 03:36:00 112.655 2017-03-01 01:16:00 111.966 -0.689
2021 -1 2017-03-01 01:16:00 111.966 2017-03-01 13:59:00 113.587 -1.621
2022 1 2017-03-01 13:59:00 113.587 2017-03-03 22:18:00 114.295 0.708
2023 -1 2017-03-03 22:18:00 114.295 2017-03-04 06:59:00 113.976 0.319

ちょっとはまった点(´・ω・`)

In [7]:
print(df.Close.flags)
print(df.Close.values.strides)
  C_CONTIGUOUS : True
  F_CONTIGUOUS : True
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False
(8,)
In [8]:
import pandas_datareader.data as web
n225 = web.get_data_yahoo('^N225')
print(n225.Close.flags)
print(n225.Close.values.strides)
  C_CONTIGUOUS : False
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False
(-8,)

↑ この違いでpandas_datareaderで取得したデータとMT4のデータで挙動が違って困った

(価格データが逆向きになってる感じの結果になって)

↓ copy()をするとmt4から読み込んだものと同じになったのでこれで対処した

メモリのデータの配置の違い?(´・ω・`)

In [9]:
print(n225.copy().Close.flags)
print(n225.copy().Close.values.strides)
  C_CONTIGUOUS : True
  F_CONTIGUOUS : True
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False
(8,)