forked from harvey1673/pyktrader
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathbktest_turtle.py
159 lines (154 loc) · 7.07 KB
/
bktest_turtle.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import misc
import json
import data_handler as dh
import pandas as pd
import numpy as np
import strategy as strat
import sys
import backtest
def turtle_sim( mdf, config ):
ddf = config['ddf']
marginrate = config['marginrate']
offset = config['offset']
start_equity = config['capital']
tcost = config['trans_cost']
chan = config['chan']
unit = config['unit']
param = config['param']
max_pos = param[1]
max_loss = param[0]
ma_ratio = config['ma_ratio']
use_ma = config['use_ma']
chan_func = config['chan_func']
pos_update = config['pos_update']
ddf['ATR'] =dh.ATR(ddf, n=chan[0]).shift(1)
ddf['OL_1'] = eval(chan_func['high']['func'])(ddf, chan[0]).shift(1)
ddf['OS_1'] = eval(chan_func['low']['func'])(ddf, chan[0]).shift(1)
ddf['CL_1'] = eval(chan_func['low']['func'])(ddf, chan[1]).shift(1)
ddf['CS_1'] = eval(chan_func['high']['func'])(ddf, chan[1]).shift(1)
ddf['MA1'] = dh.MA(ddf, ma_ratio*chan[0]).shift(1)
ddf['MA2'] = dh.MA(ddf, chan[1]).shift(1)
ll = mdf.shape[0]
mdf['pos'] = pd.Series([0]*ll, index = mdf.index)
mdf['cost'] = pd.Series([0]*ll, index = mdf.index)
curr_pos = []
tradeid = 0
closed_trades = []
end_d = mdf.index[-1].date()
curr_atr = 0
for idx, dd in enumerate(mdf.index):
mslice = mdf.ix[dd]
min_id = mslice.min_id
d = dd.date()
dslice = ddf.ix[d]
tot_pos = sum( [trade.pos for trade in curr_pos] )
mdf.ix[dd, 'pos'] = tot_pos
if np.isnan(dslice.ATR):
continue
if (min_id >= config['exit_min']) :
if (tot_pos != 0) and (d == end_d):
for trade_pos in curr_pos:
trade_pos.close(mslice.close - misc.sign(trade_pos.pos) * offset, dd)
tradeid += 1
trade_pos.exit_tradeid = tradeid
closed_trades.append(trade_pos)
mdf.ix[dd, 'cost'] -= abs(trade_pos.pos) * (offset + mslice.close*tcost)
curr_pos = []
tot_pos = 0
else:
if tot_pos == 0:
curr_atr = dslice.ATR
direction = 0
dol = dslice.OL_1
dos = dslice.OS_1
if (mslice.close >= dol) and ((use_ma == False) or (mslice.MA1 >= mslice.MA2)):
direction = 1
elif (mslice.close <= dos) and ((use_ma == False) or (mslice.MA1 <= mslice.MA2)):
direction = -1
pos = direction * unit
if direction != 0:
new_pos = strat.TradePos([mslice.contract], [1], pos, mslice.close, mslice.close)
tradeid += 1
new_pos.entry_tradeid = tradeid
new_pos.open(mslice.close + direction * offset, dd)
mdf.ix[dd, 'cost'] -= abs(pos) * (offset + mslice.close*tcost)
curr_pos.append(new_pos)
else:
direction = curr_pos[0].direction
#exit position out of channel
if (direction == 1 and mslice.close <= dslice.CL_1) or \
(direction == -1 and mslice.close >= dslice.CS_1):
for trade_pos in curr_pos:
trade_pos.close(mslice.close - misc.sign(trade_pos.pos) * offset, dd)
tradeid += 1
trade_pos.exit_tradeid = tradeid
closed_trades.append(trade_pos)
mdf.ix[dd, 'cost'] -= abs(trade_pos.pos) * (offset + mslice.close*tcost)
curr_pos = []
#stop loss position partially
elif curr_pos[-1].check_exit( mslice.close, curr_atr * max_loss ):
for trade_pos in curr_pos:
if trade_pos.check_exit( mslice.close, curr_atr * max_loss ):
trade_pos.close(mslice.close - misc.sign(trade_pos.pos) * offset, dd)
tradeid += 1
trade_pos.exit_tradeid = tradeid
closed_trades.append(trade_pos)
mdf.ix[dd, 'cost'] -= abs(trade_pos.pos) * (offset + mslice.close*tcost)
curr_pos = [trade for trade in curr_pos if not trade.is_closed]
#add positions
elif (len(curr_pos) < max_pos) and (mslice.close - curr_pos[-1].entry_price)*direction > curr_atr/max_pos*max_loss:
for trade_pos in curr_pos:
#trade.exit_target += curr_atr/max_pos*max_loss * direction
trade_pos.set_exit( mslice.close )
new_pos = strat.TradePos([mslice.contract], [1], direction*unit, mslice.close, mslice.close)
tradeid += 1
new_pos.entry_tradeid = tradeid
new_pos.open(mslice.close + direction * offset, dd)
mdf.ix[dd, 'cost'] -= abs(pos) * (offset + mslice.close*tcost)
curr_pos.append(new_pos)
if (len(curr_pos) > 0) and pos_update:
for trade_pos in curr_pos:
trade_pos.update_price(mslice.close)
mdf.ix[dd, 'pos'] = sum( [trade.pos for trade in curr_pos] )
(res_pnl, ts) = backtest.get_pnl_stats( mdf, start_equity, marginrate, 'm')
res_trade = backtest.get_trade_stats( closed_trades )
res = dict( res_pnl.items() + res_trade.items())
return (res, closed_trades, ts)
def gen_config_file(filename):
sim_config = {}
sim_config['sim_func'] = 'bktest_turtle.turtle_sim'
sim_config['scen_keys'] = ['chan', 'param']
sim_config['sim_name'] = 'TurtleTest_'
sim_config['products'] = ['m', 'RM', 'y', 'p', 'l', 'pp', 'a', 'rb', 'SR', 'TA', 'MA', 'i', 'ru', 'j', 'jd', 'jm', 'cu','TF', 'IF', 'cs', 'v', 'ME']
sim_config['start_date'] = '20121101'
sim_config['end_date'] = '20151118'
sim_config['need_daily'] = True
sim_config['chan'] = [(10, 3), (10, 5), (15, 5), (20, 5), (20, 10)]
sim_config['param'] = [(1, 1), (1, 2), (2, 1), (2, 2), (2, 3), (2, 4)]
sim_config['pos_class'] = 'strat.TradePos'
sim_config['offset'] = 1
#chan_func = { 'high': {'func': 'dh.PCT_CHANNEL', 'args':{'pct': 90, 'field': 'high'}},
# 'low': {'func': 'dh.PCT_CHANNEL', 'args':{'pct': 10, 'field': 'low'}}}
chan_func = { 'high': {'func': 'dh.DONCH_H', 'args':{}},
'low': {'func': 'dh.DONCH_L', 'args':{}}}
config = {'capital': 10000,
'trans_cost': 0.0,
'close_daily': False,
'unit': 1,
'pos_args': {},
'chan_func': chan_func,
'ma_ratio': 2,
'use_ma': False,
'pos_update': False,
}
sim_config['config'] = config
with open(filename, 'w') as outfile:
json.dump(sim_config, outfile)
return sim_config
if __name__=="__main__":
args = sys.argv[1:]
if len(args) < 1:
print "need to input a file name for config file"
else:
gen_config_file(args[0])
pass