backtest2104
code
import datetime
import numpy as np, pandas as pd
import matplotlib.pyplot as plt # 个性配置文件: C:\Users\Administrator\.matplotlib\matplotlibrc
from matplotlib.ticker import FuncFormatter, MaxNLocator # 刻度的格式, 刻度的定位
import mplfinance as mpf; _=mpf
from mplfinance.original_flavor import candlestick_ohlc
# from cycler import cycler# 用于定制线条颜色
import util.ttr as ttr
from dqsbt.util2 import util as aid # setcash/comm, adddata, addanalyzer
from qsutil.gspace import g
from qsutil.u import ucheck
from qsutil.db.dboperation import resample_d_to_w
from candleplotter import CandlePlotter
from study.tlog import Tlog
from qsutil import pkl
from qsutil.u import uplot
from qsutil.u import uload
# df_dict = uload.load_data(g.codes_3kj)
def load_1h():
pfile = 'd:\\secData\\5min\\hs300_1h.pkl'
# pkl.pkl_dump((dfh,dfw),pfile)
dfh, dfw = pkl.pkl_load(pfile)
return dfh, dfw
def load_nmoney():
pfile = 'd:\\secData\\nmoney.pkl'
# pkl.pkl_dump((dfh,dfw),pfile)
nmoney= pkl.pkl_load(pfile)
return nmoney
def load_eod():
df_dict=aid.load_data(g.k1l)
return df_dict[g.k1]
# def get_index_eod(code='399296.SZ', start='2018-01-01', end='2019-12-31',):
def get_index_eod(code='399296.SZ', start='1933', end='2033',):
return uload.get_index_df(code, start, end)
#%% basestrategy
class BaseStrategy:
'''EOD日线数据'''
def __init__(self, df=None,
code=None,
start='2020-1-1',
end='2020-12-31',
n_sma=5,
moveup_level=4, #跟踪止损线 上移门槛 4: 4%
n_tsl=3, n_nbar=8,
n_hhv=3, n_llv=3, n_median=3, upslope=3,
n_lr1=21, n_lr2=8,
freq='Daily',
figsize=(12,6), # (12,8)
comm=1/100/100.,
**kwargs):
self.dfd=df # daily df
self.p=dict()
self.p.update(dict(
code=code,
dname=g.cn_dict[code],
start=start, end=end,
n_hhv=n_hhv,
n_llv=n_llv,
n_median=n_median,
upslope=upslope,
n_sma=n_sma, n_lr1=n_lr1, n_lr2=n_lr2,
n_nbar=n_nbar,
moveup_level=moveup_level, # 表示 pnl > 4% 的时候, 就要上移tsl
n_tsl=n_tsl, # 3表示设置sl的时候, 引用最近4期的low的最小值
freq=freq,
figsize=figsize,
comm=comm,
))
self.mparams = 'n_hhv n_llv n_median upslope'.split()
self.p.update(kwargs)
# super().__init__(**kwargs)
def load(self):
if self.dfd is None:
# self.dfh, self.dfw = load_1h()
# self.dfd = load_eod()
self.dfd = get_index_eod(g.ticker)
def ind(self):
start = g.strptime(self.p['start']) - datetime.timedelta(days=450)
end = self.p['end']
df = self.dfd.loc[start:end, ].copy()
df['weekday'] = df.index.isocalendar().day
df['roc1'] = ttr.roc1(df.close)[0] #用作策略当日收益率的引用值
df['close1'] = df.close.shift(1) #用于计算策略卖出日的收益
df['tr'], _, __ = ttr.tr_(df)
df['atr20'] = ttr.atr(df,20).shift(1)
_ = ttr.ema(df.close, 2)
df['ema'] = ttr.ema(_, 2).shift(1)
df['trend'] = ttr.sma(df.close, self.p['n_sma']*4)
high, low = df.high, df.low
df['outa'] = ttr.hhv(df.high, 40) - (ttr.hhv(high,4)-ttr.llv(low,4))
df['hhv'] = ttr.hhv(df.high, 10).shift(1)
df['llv'] = ttr.llv(df.low, 10).shift(1)
_ = (df.hhv + df.llv)/2 - df.atr20/2
df['lline'] = ttr.ema(_, 2) # lifeline 生命线
df['nmoney'] = load_nmoney()
df['nmoney'] = df.nmoney.fillna(0)
df['nmoney_cum'] = df.nmoney.cumsum()
df['nmoney_ma'] = ttr.sma(df.nmoney_cum, self.p['n_sma'])
df['moneyjc'] = ttr.upCross(df.nmoney_cum, df.nmoney_ma)
df['moneysc'] = ttr.downCross(df.nmoney_cum, df.nmoney_ma)
self.df = df.loc[self.p['start']: end].copy()
self.plotter = CandlePlotter(self.df, self.p)
def play(self, cook=False,
which='trueemajc', label='emajc', color='r'):
'''
'''
self._ind()
self._candles()
# self._plot_trend(plot_mm=True)
self._plot_trend(plot_mm=False)
self._get_buysignal()
self._plot_buysignal(which, label, color)
print('\nSignal info: ', which, label, color)
# self._plot_buysignal('trueema2', 'ema2', 'k')
# self._plot_buysignal('truebuy', 'cdft', 'k')
# self._plot_buysignal('truebuy_zsl', 'zsl', 'red')
# self._plot_buysignal('truebuy_mjjc', 'mjjc', 'green')
if cook:
# self.cook_signal_zsxy(label)
self.cook_signal(label)
self.get_perf()
self.print_transac()
# self.print_pnl_desc()
self.plot_tsl()
self.custom_fig() # self._subplots_adj()
'''
# self._plot_box_btn_cross()
'''
def cook_signal(self, sname='emajc',
ini_sl='ema',
sl_ref='ema',
sprice_ref='last_sl',
):
''' 中轴线上拐头 进场, 低点破前低点出场
也就是:
止损位跟踪收盘时/收盘后的k线低点
卖出价 = sl = 锁定前k线的low,
判断: if row.low<sl
ini_sl: 买入位置止损位/初始止损 low mml
sprice_ref: 卖出价格锁定位置 'curr_close' last_sl
sl_ref: 跟踪止损参考线, 'trend' 'mml' low
'''
self.tlog = Tlog(self.p)
pos = self.tlog.pos; df = self.df
signal = getattr(self, sname); signal=signal.fillna(0)
print(self.tlog.log_title)
for i, (dt, row) in enumerate(df.iterrows()):
if not pos:
if signal[dt]: # signal comes into my room
# hqpat = sname
# sl = getattr(row, ini_sl)/1.0 # 设置初始止损
sl = df.ema[i+1]
pos = self.tlog.buy(i, dt, row, df, sl) # set pos = 1
else:
pass
elif pos:
# 取tlog里的数据(昨天的)
# sl = self.tlog.sl
sl = row.ema
# 计算新的指标
pnl = (row.close/self.tlog.bprice - 1) * 100; _=pnl
# if row.close<row.mmh and row.open>row.mmh:
if False: # skit it
pos = self.tlog.sell(i, dt, row, df, sprice_ref) # set pos = 0
# 收盘价向下击穿止损的情况
# elif row.close<sl: # df里的sl: 当前日的止损位; 效果很不好
# elif row.low<sl: # df里的sl: 当前日的止损位; 效果很不好
elif row.close<row.ema: # df里的sl: 当前日的止损位; 效果很不好
# elif self.zzxxgt[dt]: # df里的sl: 当前日的止损位; 效果很不好
# pos = self.tlog.sell(i, dt, row, df, 'curr_close')
pos = self.tlog.sell(i, dt, row, df, 'row_ema')
# 日常盘整: 登记收盘时间戳下的头寸和止损
else:
self.tlog.register(i, dt, row, df, 'row_ema')
self._get_equity()
def _find_event(self, *cond_args, label='dingfx', name='顶分形',):
event = cond_args[0]
for cond in cond_args[1:]:
event = event & cond
# event = all(cond_args) # a & b & c & d & e
if True in event.value_counts().index:
print(f'\n "{name}"信号次数: ', event.value_counts()[True])
self.df[label] = event
def find_event(self, label='dingfx', name='顶分形'):
df=self.df
h,l = df.high, df.low
a = h<h.shift(1)
b = l<l.shift(1)
c = h.shift(1)>h.shift(2)
d = l.shift(1)>l.shift(2)
e = h.shift(1) > df.hhv.shift(1)
args = (a,b,c,d,e)
self._find_event(*args, label=label, name=name)
def find_event_difx(self, ):
df=self.df
h,l = df.high, df.low
a = h > h.shift(1)
b = l > l.shift(1)
c = h.shift(1) < h.shift(2)
d = l.shift(1) < l.shift(2)
e = l.shift(1) < df.llv.shift(1)
args = (a,b,c,d,e)
self._find_event(*args, label='difx', name='底分形')
def find_event_(self, event_label='dfx', event_name='顶分形'):
df=self.df
h,l = df.high, df.low
a = h<h.shift(1)
b = l<l.shift(1)
c = h.shift(1)>h.shift(2)
d = l.shift(1)>l.shift(2)
e = h.shift(1) > df.hhv.shift(1)
event = a & b & c & d & e
if True in event.value_counts().index:
print(f'\n "{event_name}"信号次数: ', event.value_counts()[True])
self.df[event_label] = event
def get_bsig_group(self):
'''仅提取买入信号分组, 需要在get_signal()方法之后才可以调用 '''
try:
out = self.bsig.groupby('sig').get_group(True)
return out
except Exception as e:
print('抛出异常: ', e)
print('你应该先调用self.get_signal(0)方法来提取交易信号')
def get_ssig_group(self):
return self.ssig.groupby('sig').get_group(True)
def get_signal(self, only_evevt=True):
'''
提取特征事件之交易信号
输出:
- self.df里添加了两列: 金叉事件'jc', 死叉事件'sc'
- 可选地, self里添加两个属性: bsig 和 ssig , df类型
周k线的操作信号,
行模式获取买入信号, 比较高效, 但是缺乏细腻
周线数据属于低频数据, 其信噪比非常高,
均线稳定, k线与均线之间的关系也很明确
应该能寻找出比较可靠的交易信号.
上沿: 红线
下沿: 蓝线
趋势线: 绿线
'''
df=self.df
# print('\nbandwidth 简述:\n', self.df_ha.bw1.describe())
# 1. 均线和收盘线都在趋势线之下, 发生金叉: ==>超跌反弹
# 2. 均线和收盘线都在趋势线之上, 发生金叉: ==>主升浪
# 3. 均线在趋势线之下但是收盘线在趋势线之上, 发生金叉: ==>密集金叉 ==> 牛熊转势特征
# 前期为疯熊的话, 大概率为 空头陷阱
# 前期为疯牛的话, 大概率为 多头陷阱
# hdark: high dark 高位的阴线(heikin ashi蜡烛线的黑线/阴线)
# 前期一直是上涨, ha线一直是白色的大实体, 越过了wh(前一个周线的高点),
# 最近两三根ha线变成了小实体+上下影线, 现在是黑色实体,
# 但是收盘价依然在wh之上,
# ==>下跌的前兆
# =============================================================================
# a = df.high > df.hhv
# a1 = a.shift(3)
# b = df.high < df.hhv
# b1 = b.shift(1)
# b2 = b.shift(2)
# cxgyhd3t = a1 & b & b1 & b2
# if True in cxgyhd3t.value_counts().index:
# print('\n cxgyhd3t"创新高以后第三天"信号次数: ', cxgyhd3t.value_counts()[True])
# self.df['cxgyhd3t'] = cxgyhd3t
# =============================================================================
self.df['jc'] = ttr.upCross(df.close, df.lline)
self.df['sc'] = ttr.downCross(df.close, df.lline)
print('\n jc"收盘价金叉生命线"信号次数: ', self.df.jc.value_counts()[True])
print('\n sc"收盘价死叉生命线"信号次数: ', self.df.sc.value_counts()[True])
# cutw: crossUp trend and white block
# 金叉趋势线并且ha线已经由黑体变成了白体
# 前期是一波下跌, 向下击穿了wh,
# ha线一直是黑色实体,
# 在向下试探支撑的过程中, 可能会有四条路线:
# 1. hwhite
# trbt: take a rest at just below wh/top 在上沿下方歇歇脚
#
# 2. 在wt附近或者wh和wl的中点线附近形成中枢横盘,
# 此时ha线表现为小实体+长上下影线
# ==>持观望态度(有股时持股, 应该无操作)
# 3. twhite
# 继续下行跌穿wt来到wl附近或者在wl的上沿处得到支撑
# 最近两三根ha线变成了小实体+上下影线, 现在是白色色实体,
# 但是收盘价依然在wl之上,
# ==>下涨的前兆
# 4. 继续下行击穿wl/下沿
# ==> 上涨趋势已经彻底改变了. 到了不可操作区.
# ==> 继续深跌之后可能有反弹行情. 也只能半仓快进快出了
if not only_evevt:
bsignal = self._get_signal('jc')
ssignal = self._get_signal('sc')
self.bsig, self.ssig = bsignal, ssignal
def get_signal_nmoney(self, only_evevt=True):
''' 提取'北向资金'的开仓/平仓信号, 其类型为df, cols='sig stype'
'''
df=self.df
print('\n moneyjc"北向资金流入金叉"信号次数: ', df.moneyjc.value_counts()[True])
print('\n moneysc"北向资金流入死叉"信号次数: ', df.moneysc.value_counts()[True])
if not only_evevt:
bsignal = self._get_signal('moneyjc')
ssignal = self._get_signal('moneysc')
self.bsig, self.ssig = bsignal, ssignal
def _add_signal_to_df(self, signal, stype='bwhite'):
if stype in self.df:
signal['sig'] = getattr(self.df, stype) | getattr(signal, 'sig')
str_arr = np.where(getattr(self.df, stype), stype, '')
signal['stype'] += str_arr
return signal
def _get_signal(self, stype:str):
''' '''
signal = pd.DataFrame(index=self.df.index, columns='sig stype'.split())
signal.stype = '' #bsignal.stype.astype(str) # change signal_type datatype as str
signal = self._add_signal_to_df(signal, stype)
return signal
def simplest_bt(self, print_log=True, bprice_ref='close', sprice_ref='close'):
pos=0
bp=0; sp=0; sellbar=0; allow_enter_bar=10; _=allow_enter_bar
trans, srec, pos_l, sroc1 = [],[], [], []
zips = zip(self.bsig.index, self.df.close, self.bsig.values,self.ssig.values)
print('\n开始简单回测 --------')
print('sn dt close bsig btype ssig stype pnl')
print('--'*30)
for i, (dt, c0, (bs, bt), (ss, st)) in enumerate(zips):
if not pos:
if bs: # and i>=sellbar+allow_enter_bar :
pos=1; pos_l.append((dt, pos))
bp=c0 if bprice_ref=='close' else self.df['lline'].iloc[i]
t = (dt, 'buy', bp, 0)
trans.append(t)
# _=-self.p['comm']
_ = c0/bp - 1 - self.p['comm']
sroc1.append((dt, _))
if print_log:
print('{:>3d} {} {:.2f} {:8s} {:8s} {:8s} {:8s}'.format(
i, dt, c0, str(bs),bt,str(ss),st))
else:
if ss:
sellbar = i; _=sellbar
pos=0; pos_l.append((dt, pos))
sp=c0 if sprice_ref=='close' else self.df['lline'].iloc[i]
pnl = sp/bp -1
t = (dt, 'sell', sp, pnl)
trans.append(t)
srec_ = (dt, bp, sp, pnl)
srec.append(srec_)
_ = sp/self.df.close[i-1] - 1 - self.p['comm']
sroc1.append((dt, _))
if print_log:
print('{:>3d} {} {:.2f} {:8s} {:8s} {:8s} {:8s} {:.2%}'.format(
i, dt, c0, str(bs),bt,str(ss),st,pnl))
self.pos_l, self.sroc1, self.srec, self.trans = \
pos_l, sroc1, srec, trans
self.get_equity()
def get_equity(self):
pos = pd.DataFrame(self.pos_l, columns='dt pos'.split()).set_index('dt')
self.df['pos'] = pos
self.df.pos=self.df.pos.ffill()
# 计算策略的每日收益率: sroc1
# 考虑到了佣金, 并剔除了进出场时的两个bar的原始roc1
sroc1_bs = pd.DataFrame(self.sroc1, columns='dt sroc1'.split()).set_index('dt')
self.df['sroc1_bs'] = sroc1_bs
sroc1_ = self.df.pos * self.df.roc1
self.df['sroc1'] = np.where(
self.df.sroc1_bs.fillna(False), self.df.sroc1_bs, sroc1_)
self.df['sroc1'] = self.df.sroc1.fillna(0)
self.df['equity'] = self.df.sroc1.add(1).cumprod() * self.df.close[0]
if hasattr(self.plotter, 'ax'):
self.plotter._plot_equity()
# =============================================================================
# self.df['equity'].plot(
# ax=self.plotter.ax, use_index=False,
# color='m', marker='o', markersize=4,
# label='equity({:.2f}) vs bm({:.2f})'.format(
# self.df.equity[-1]/self.df.equity[0],
# self.df.close[-1]/self.df.close[0],
# ),
# legend=True,
# )
# =============================================================================
self.srec_df = pd.DataFrame(self.srec, columns='dt bp sp pnl'.split()).set_index('dt')
self.trans_df = pd.DataFrame(self.trans, columns='dt operation price pnl'.split()).set_index('dt')
self.nav = self.trans_df.pnl.add(1).cumprod()
print('nav:{:.2f}, benchmark nav:{:.2f}'.format(self.nav[-1], self.df.close[-1]/self.df.close[0] ))
# fig,ax=plt.subplots()
# self.df.roc1.add(1).cumprod().plot(ax=ax)
# self.nav.plot(ax=ax, marker='o')
# (self.nav/1.001).plot(ax=ax, marker='^')
def _get_equity(self):
df = self.df_ha.copy()
pos_l = pd.DataFrame(self.tlog.pos_l, columns='dt pos'.split()).set_index('dt').shift(1)
df['pos'] = pos_l # index will be aligned to df.index
df['pos'] = df.pos.fillna(0)
# df['equity'] = ((df.roc1*df.pos) + 1).cumprod()*df.close[0]
sroc1 = pd.DataFrame(self.tlog.sroc1_l, columns='dt sroc1'.split()).set_index('dt')
df['sroc1'] = sroc1
df['sroc1'] = df.sroc1.fillna(0)
df['equity'] = ((df.sroc1*df.pos) + 1).cumprod()*df.close[0]
self.df = df
def show_event(self, bsig=None, ssig=None, annotate=False):
# show week start bar tickmark, show week end bar tickmark
# uplot.vlines (self.plotter.ax, self.df_ha.iswendbar, self.df_ha.wh ,'red')
# uplot.vlines (self.plotter.ax, self.df.cxgyhd3t, self.df.hhv ,'red')
uplot.vlines (self.plotter.ax, getattr(self.df, bsig), self.df.llv, 'green', position='below')
uplot.vlines (self.plotter.ax, getattr(self.df, ssig), self.df.hhv, 'red', position='above')
# uplot.annotate_event(self.plotter.ax, self.df_ha.iswstartbar, self.df_ha.wh, self.df_ha.bw1, 'wbw', arrow_color='red')
if annotate:
uplot.annotate_event(self.plotter.ax, getattr(self.df, bsig), self.df.llv,
self.df.close, 'bs', text_position='below',
text_format='.0f', arrow_color='red')
uplot.annotate_event(self.plotter.ax, getattr(self.df, ssig), self.df.hhv,
self.df.close, 'ss', text_position='above',
arrow_color='purple', text_format='.0f')
def make_subplot(self, length=100, plot_signal=False, plot_equity=False, savefig=False):
for i in range(0, len(self.df), length):
# print(i)
self.subplot=CandlePlotter(self.plotter.df.iloc[i:i+length,], self.plotter.p)
self.subplot._candles()
self.subplot._plot_trend()
if plot_signal:
self.subplot._plot_signal(bsig='jc', ssig='sc')
if plot_equity:
self.subplot._plot_equity()
pass
if savefig:
date_ = self.subplot.df.index[0].date().isoformat()
fname = f'{g.k1[:6]}_{date_}.png'
self.subplot.fig.savefig(fname)
def weekday_effect(self):
fig,ax=plt.subplots()
gb = self.df.roc1.groupby(self.df.weekday)
for x in range(1, 5+1):
lbl = f'wk{x}'
self.df[lbl] = gb.get_group(x).add(1).cumprod()
self.df[lbl] = self.df[lbl].fillna(method='ffill')
self.df[lbl].plot(ax=ax, label=lbl+f'({self.df[lbl][-1]:.2f})', legend=True, use_index=False)
self.df.roc1.add(1).cumprod().plot(ax=ax, use_index=False, label='benchmark', legend=True)
#%% utest
def utest(n_sma=5):
# from study.tlog import Tlog
# from study.trade_chart_hilo import CandlePlotter
class LifelineStrat(BaseStrategy):
def __init__(self, **kwargs):
super().__init__(**kwargs)
g.df = get_index_eod(g.ticker)
start,end= '2020-1-1', '2020-12-31';
self = LifelineStrat(df=g.df, start=start, end=end, n_sma=n_sma)
self.load()
self.ind()
self.plotter._candles()
self.plotter._plot_trend()
self.get_signal(0)
self.show_event(bsig='jc', ssig='sc')
# self.simplest_bt(print_log=False, )
# self.simplest_bt(print_log=False, bprice_ref='lline')
# self.simplest_bt(print_log=False, bprice_ref='lline', sprice_ref='lline')
# self.simplest_bt(print_log=0, bprice_ref='lline', sprice_ref='lline')
self.simplest_bt(print_log=1, bprice_ref='lline', sprice_ref='lline')
print(f"策略参数n_sma={self.p['n_sma']}")
print('策略绩效-------')
ttr.perf_measure(self.df.equity, with_print=True)
print('BnH绩效-------')
ttr.perf_measure(self.df.close, with_print=True)
return self
# Here is the main entry:
# self = utest(5)
# for n in range(3,15, 1): self= utest(n)
# self= utest(cook=False)
duanqs