BackTrader 中文文档(二十二)
MACD 设置
原文:
www.backtrader.com/blog/posts/2016-07-30-macd-settings/macd-settings/
在Reddit的Algotrading网站上发现了一个关于优化 MACD 设置的帖子。
由于《走向财务自由的交易》- 亚马逊链接,我开始了backtrader的探索,我别无选择,只能发布答案并制作一个示例。
该策略的方法在某种程度上基于该书中提出的一些观点。没有新鲜事。参数已经快速设置。没有过度拟合,没有优化,什么都没有。大致:
-
如果
macd
线向上穿过signal
线,并且控制Simple Moving Average
在最后的 x 个周期内具有净负方向(当前SMA值低于 x 个周期前的值),则进入市场 -
设置
stop
价格,即比close
价格远N x ATR
倍 -
如果
close
价格低于stop
价格,则退出市场 -
如果仍然在市场上,则仅在
stop
价格高于实际价格时更新
下注由以下方式完成:
- 通过
Sizer
分配可用现金的百分比给操作。策略赢得越多,押注就越大... 而策略输得越多,押注就越小。
包含了佣金。因为测试将使用股票进行,所以选择了百分比佣金,其值为0.0033
(即每往返交易的0.33%
)。
为了付诸实施,将进行 3 次运行,每次运行 3 个数据(共 9 次运行)
-
手动选择的标准参数
-
将现金分配百分比从
0.20
增加到0.50
-
将 ATR 距离从
3.0
增加到4.0
,以尝试避免被鞭打
为策略手动选择的参数:
params = (
# Standard MACD Parameters
('macd1', 12),
('macd2', 26),
('macdsig', 9),
('atrperiod', 14), # ATR Period (standard)
('atrdist', 3.0), # ATR distance for stop price
('smaperiod', 30), # SMA Period (pretty standard)
('dirperiod', 10), # Lookback period to consider SMA trend direction
)
并且系统范围内:
parser.add_argument('--cash', required=False, action='store',
type=float, default=50000,
help=('Cash to start with'))
parser.add_argument('--cashalloc', required=False, action='store',
type=float, default=0.20,
help=('Perc (abs) of cash to allocate for ops'))
parser.add_argument('--commperc', required=False, action='store',
type=float, default=0.0033,
help=('Perc (abs) commision in each operation. '
'0.001 -> 0.1%%, 0.01 -> 1%%'))
日期范围将从2005-01-01
到2014-12-31
,共 10 年。
分析器
为了获得一些客观数据,将向系统添加 3 个分析器:
-
两个用于整个时期的
TimeReturn
-
对于策略本身
-
用作基准操作的数据
将策略与资产进行有效的基准比较
-
-
一个用于测量年度回报率的
TimeReturn
-
一个
SharpeRatio
用于查看策略相对于无风险资产的表现如何该值设置为
1%
,可通过示例选项更改 -
一个用于分析交易质量的
SQN
(系统质量数),使用 Van K. Tharp 定义的性能指标
此外,将添加一个DrawDown
观察器到混合中。
Run 1:标准参数
YHOO
$ ./macdsystem.py --plot --dataset yhoo
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.07118518868
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.316736183525
===============================================================================
TimeReturn:
- 2005-12-31: 0.02323119024
- 2006-12-31: -0.0813678018166
- 2007-12-31: -0.0144802141955
- 2008-12-31: -0.142301023804
- 2009-12-31: 0.0612152927491
- 2010-12-31: 0.00898269987778
- 2011-12-31: -0.00845048588578
- 2012-12-31: 0.0541362123146
- 2013-12-31: 0.0158705967774
- 2014-12-31: 0.0281978956007
===============================================================================
SharpeRatio:
- sharperatio: -0.261214264357
===============================================================================
SQN:
- sqn: -0.784558216044
- trades: 45
ORCL
$ ./macdsystem.py --plot --dataset orcl
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.24890384718
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 2.23991354467
===============================================================================
TimeReturn:
- 2005-12-31: -0.02372911952
- 2006-12-31: 0.0692563226579
- 2007-12-31: 0.0551086853554
- 2008-12-31: -0.026707886256
- 2009-12-31: 0.0786118091383
- 2010-12-31: 0.037571919146
- 2011-12-31: 0.00846519206845
- 2012-12-31: 0.0402937469005
- 2013-12-31: -0.0147124502187
- 2014-12-31: 0.00710131291379
===============================================================================
SharpeRatio:
- sharperatio: 0.359712552054
===============================================================================
SQN:
- sqn: 1.76240859868
- trades: 37
NVDA
$ ./macdsystem.py --plot --dataset nvda
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.0178507058999
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.177604931253
===============================================================================
TimeReturn:
- 2005-12-31: 0.0773031957141
- 2006-12-31: 0.105007457325
- 2007-12-31: 0.015286423657
- 2008-12-31: -0.109130552525
- 2009-12-31: 0.14716076542
- 2010-12-31: -0.0891638005423
- 2011-12-31: -0.0788216550171
- 2012-12-31: -0.0498231953066
- 2013-12-31: -0.0119166712361
- 2014-12-31: 0.00940493597076
===============================================================================
SharpeRatio:
- sharperatio: -0.102967601564
===============================================================================
SQN:
- sqn: -0.0700412395071
- trades: 38
Run 1 的分析
-
YHOO
第 1^(st)和第 2^(nd)个
TimeReturn
分析器(策略和资产本身)表明,策略损失了7.11%
,而所涉及的资产则升值了31.67%
。甚至都不值得去看其他的分析器
-
ORCL
策略为
24.89%
,但被资产本身的223.99%
回报所淡化。SharpeRatio 为
0.35
,仍然远离通常的最低目标1
。SQN 返回了
1.76
,至少在 Van K. Tharp 尺度上获得了评分:1.6 - 1.9 低于平均水平
。 -
NVDA
在这种情况下,策略为
-1.78%
,资产为-17.76%
。 SharpeRatio 为-0.10
,表明即使策略表现优于资产,选择1%
的银行账户也更好。SQN 显然甚至没有达到尺度的底部。
第 1 次运行的结论
- 1 次巨大的损失,一次平局和一次表现不佳的胜利。并不成功。
第 2 次运行:现金分配为 0.50
YHOO
$ ./macdsystem.py --plot --dataset yhoo --cashalloc 0.50
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.20560369198
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.316736183525
===============================================================================
TimeReturn:
- 2005-12-31: 0.05517338686
- 2006-12-31: -0.195123836162
- 2007-12-31: -0.0441556438731
- 2008-12-31: -0.32426212721
- 2009-12-31: 0.153876836394
- 2010-12-31: 0.0167157437151
- 2011-12-31: -0.0202891373759
- 2012-12-31: 0.13289763017
- 2013-12-31: 0.0408192946307
- 2014-12-31: 0.0685527133815
===============================================================================
SharpeRatio:
- sharperatio: -0.154427699146
===============================================================================
SQN:
- sqn: -0.97846453428
- trades: 45
ORCL
$ ./macdsystem.py --plot --dataset orcl --cashalloc 0.50
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.69016747856
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 2.23991354467
===============================================================================
TimeReturn:
- 2005-12-31: -0.0597533502
- 2006-12-31: 0.176988400688
- 2007-12-31: 0.140268851352
- 2008-12-31: -0.0685193675128
- 2009-12-31: 0.195760054561
- 2010-12-31: 0.0956386594392
- 2011-12-31: 0.018709882089
- 2012-12-31: 0.100122407053
- 2013-12-31: -0.0375741196261
- 2014-12-31: 0.017570390931
===============================================================================
SharpeRatio:
- sharperatio: 0.518921692742
===============================================================================
SQN:
- sqn: 1.68844251174
- trades: 37
NVDA
$ ./macdsystem.py --plot --dataset nvda --cashalloc 0.50
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.128845648113
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.177604931253
===============================================================================
TimeReturn:
- 2005-12-31: 0.200593209479
- 2006-12-31: 0.219254906522
- 2007-12-31: 0.0407793562989
- 2008-12-31: -0.259567975403
- 2009-12-31: 0.380971100974
- 2010-12-31: -0.208860409742
- 2011-12-31: -0.189068154062
- 2012-12-31: -0.122095056225
- 2013-12-31: -0.0296495770432
- 2014-12-31: 0.0232050942344
===============================================================================
SharpeRatio:
- sharperatio: -0.0222780544339
===============================================================================
SQN:
- sqn: -0.190661428812
- trades: 38
第 2 次运行的结论
-
将每次操作中现金分配的百分比从
20%
增加到50%
,已增加了先前结果的影响-
在 YHOO 和 NVDA 上的策略比以前损失更多
-
而在 ORCL 上的策略赢得了比以前更多,但仍然不接近资产的超过 220%。
-
第 3 次运行:ATR 距离为 4.0
仍然保持先前将现金分配增加到 50%
。 这个想法是避免过早离开市场。
YHOO
$ ./macdsystem.py --plot --dataset yhoo --cashalloc 0.50 --atrdist 4.0
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.01196310622
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.316736183525
===============================================================================
TimeReturn:
- 2005-12-31: 0.06476232676
- 2006-12-31: -0.220219327475
- 2007-12-31: -0.0525484648039
- 2008-12-31: -0.314772526784
- 2009-12-31: 0.179631995594
- 2010-12-31: 0.0579495723922
- 2011-12-31: -0.0248948026947
- 2012-12-31: 0.10922621981
- 2013-12-31: 0.406711050602
- 2014-12-31: -0.0113108751022
===============================================================================
SharpeRatio:
- sharperatio: 0.0495181271704
===============================================================================
SQN:
- sqn: -0.211652416441
- trades: 33
ORCL
$ ./macdsystem.py --plot --dataset orcl --cashalloc 0.50 --atrdist 4.0
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.21907748452
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 2.23991354467
===============================================================================
TimeReturn:
- 2005-12-31: -0.06660102614
- 2006-12-31: 0.169334910265
- 2007-12-31: 0.10620478758
- 2008-12-31: -0.167615289704
- 2009-12-31: 0.17616784045
- 2010-12-31: 0.0591200431984
- 2011-12-31: -0.100238247103
- 2012-12-31: 0.135096589522
- 2013-12-31: -0.0630483842399
- 2014-12-31: 0.0175914485158
===============================================================================
SharpeRatio:
- sharperatio: 0.144210776122
===============================================================================
SQN:
- sqn: 0.646519270815
- trades: 30
NVDA
$ ./macdsystem.py --plot --dataset nvda --cashalloc 0.50 --atrdist 4.0
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.48840287049
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.177604931253
===============================================================================
TimeReturn:
- 2005-12-31: 0.246510998277
- 2006-12-31: 0.194958106054
- 2007-12-31: -0.123140650516
- 2008-12-31: -0.246174938322
- 2009-12-31: 0.33121185861
- 2010-12-31: -0.0442212647256
- 2011-12-31: 0.0368388717861
- 2012-12-31: -0.048473112136
- 2013-12-31: 0.10657587649
- 2014-12-31: 0.0883112536534
===============================================================================
SharpeRatio:
- sharperatio: 0.264551262551
===============================================================================
SQN:
- sqn: 0.564151633588
- trades: 29
第 3 次运行的结论
-
鸡,鸡,赢家的晚餐!!
该策略在这 3 个资产上赚了钱
-
YHOO:
1.19%
对比资产本身的31.67%
回报 -
ORCL:
21.90%
对比资产的223.99%
在这种情况下,增加
ATRDist
参数已经降低了 第 2 次运行 的先前收益,即69.01%
- NVDA:
48.84%
对比资产的-17.76%
。
这里令人惊讶的是 SharpeRatio 和 SQN 表明
-
样本的使用
$ ./macdsystem.py --help
usage: macdsystem.py [-h] (--data DATA | --dataset {yhoo,orcl,nvda})
[--fromdate FROMDATE] [--todate TODATE] [--cash CASH]
[--cashalloc CASHALLOC] [--commperc COMMPERC]
[--macd1 MACD1] [--macd2 MACD2] [--macdsig MACDSIG]
[--atrperiod ATRPERIOD] [--atrdist ATRDIST]
[--smaperiod SMAPERIOD] [--dirperiod DIRPERIOD]
[--riskfreerate RISKFREERATE] [--plot [kwargs]]
Sample for Tharp example with MACD
optional arguments:
-h, --help show this help message and exit
--data DATA Specific data to be read in (default: None)
--dataset {yhoo,orcl,nvda}
Choose one of the predefined data sets (default: None)
--fromdate FROMDATE Starting date in YYYY-MM-DD format (default:
2005-01-01)
--todate TODATE Ending date in YYYY-MM-DD format (default: None)
--cash CASH Cash to start with (default: 50000)
--cashalloc CASHALLOC
Perc (abs) of cash to allocate for ops (default: 0.2)
--commperc COMMPERC Perc (abs) commision in each operation. 0.001 -> 0.1%,
0.01 -> 1% (default: 0.0033)
--macd1 MACD1 MACD Period 1 value (default: 12)
--macd2 MACD2 MACD Period 2 value (default: 26)
--macdsig MACDSIG MACD Signal Period value (default: 9)
--atrperiod ATRPERIOD
ATR Period To Consider (default: 14)
--atrdist ATRDIST ATR Factor for stop price calculation (default: 3.0)
--smaperiod SMAPERIOD
Period for the moving average (default: 30)
--dirperiod DIRPERIOD
Period for SMA direction calculation (default: 10)
--riskfreerate RISKFREERATE
Risk free rate in Perc (abs) of the asset for the
Sharpe Ratio (default: 0.01)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example: --plot style="candle" (to plot candles)
(default: None)
还有代码本身
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import random
import backtrader as bt
BTVERSION = tuple(int(x) for x in bt.__version__.split('.'))
class FixedPerc(bt.Sizer):
'''This sizer simply returns a fixed size for any operation
Params:
- ``perc`` (default: ``0.20``) Perc of cash to allocate for operation
'''
params = (
('perc', 0.20), # perc of cash to use for operation
)
def _getsizing(self, comminfo, cash, data, isbuy):
cashtouse = self.p.perc * cash
if BTVERSION > (1, 7, 1, 93):
size = comminfo.getsize(data.close[0], cashtouse)
else:
size = cashtouse // data.close[0]
return size
class TheStrategy(bt.Strategy):
'''
This strategy is loosely based on some of the examples from the Van
K. Tharp book: *Trade Your Way To Financial Freedom*. The logic:
- Enter the market if:
- The MACD.macd line crosses the MACD.signal line to the upside
- The Simple Moving Average has a negative direction in the last x
periods (actual value below value x periods ago)
- Set a stop price x times the ATR value away from the close
- If in the market:
- Check if the current close has gone below the stop price. If yes,
exit.
- If not, update the stop price if the new stop price would be higher
than the current
'''
params = (
# Standard MACD Parameters
('macd1', 12),
('macd2', 26),
('macdsig', 9),
('atrperiod', 14), # ATR Period (standard)
('atrdist', 3.0), # ATR distance for stop price
('smaperiod', 30), # SMA Period (pretty standard)
('dirperiod', 10), # Lookback period to consider SMA trend direction
)
def notify_order(self, order):
if order.status == order.Completed:
pass
if not order.alive():
self.order = None # indicate no order is pending
def __init__(self):
self.macd = bt.indicators.MACD(self.data,
period_me1=self.p.macd1,
period_me2=self.p.macd2,
period_signal=self.p.macdsig)
# Cross of macd.macd and macd.signal
self.mcross = bt.indicators.CrossOver(self.macd.macd, self.macd.signal)
# To set the stop price
self.atr = bt.indicators.ATR(self.data, period=self.p.atrperiod)
# Control market trend
self.sma = bt.indicators.SMA(self.data, period=self.p.smaperiod)
self.smadir = self.sma - self.sma(-self.p.dirperiod)
def start(self):
self.order = None # sentinel to avoid operrations on pending order
def next(self):
if self.order:
return # pending order execution
if not self.position: # not in the market
if self.mcross[0] > 0.0 and self.smadir < 0.0:
self.order = self.buy()
pdist = self.atr[0] * self.p.atrdist
self.pstop = self.data.close[0] - pdist
else: # in the market
pclose = self.data.close[0]
pstop = self.pstop
if pclose < pstop:
self.close() # stop met - get out
else:
pdist = self.atr[0] * self.p.atrdist
# Update only if greater than
self.pstop = max(pstop, pclose - pdist)
DATASETS = {
'yhoo': '../../datas/yhoo-1996-2014.txt',
'orcl': '../../datas/orcl-1995-2014.txt',
'nvda': '../../datas/nvda-1999-2014.txt',
}
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
cerebro.broker.set_cash(args.cash)
comminfo = bt.commissions.CommInfo_Stocks_Perc(commission=args.commperc,
percabs=True)
cerebro.broker.addcommissioninfo(comminfo)
dkwargs = dict()
if args.fromdate is not None:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dkwargs['fromdate'] = fromdate
if args.todate is not None:
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dkwargs['todate'] = todate
# if dataset is None, args.data has been given
dataname = DATASETS.get(args.dataset, args.data)
data0 = bt.feeds.YahooFinanceCSVData(dataname=dataname, **dkwargs)
cerebro.adddata(data0)
cerebro.addstrategy(TheStrategy,
macd1=args.macd1, macd2=args.macd2,
macdsig=args.macdsig,
atrperiod=args.atrperiod,
atrdist=args.atrdist,
smaperiod=args.smaperiod,
dirperiod=args.dirperiod)
cerebro.addsizer(FixedPerc, perc=args.cashalloc)
# Add TimeReturn Analyzers for self and the benchmark data
cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='alltime_roi',
timeframe=bt.TimeFrame.NoTimeFrame)
cerebro.addanalyzer(bt.analyzers.TimeReturn, data=data0, _name='benchmark',
timeframe=bt.TimeFrame.NoTimeFrame)
# Add TimeReturn Analyzers fot the annuyl returns
cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years)
# Add a SharpeRatio
cerebro.addanalyzer(bt.analyzers.SharpeRatio, timeframe=bt.TimeFrame.Years,
riskfreerate=args.riskfreerate)
# Add SQN to qualify the trades
cerebro.addanalyzer(bt.analyzers.SQN)
cerebro.addobserver(bt.observers.DrawDown) # visualize the drawdown evol
results = cerebro.run()
st0 = results[0]
for alyzer in st0.analyzers:
alyzer.print()
if args.plot:
pkwargs = dict(style='bar')
if args.plot is not True: # evals to True but is not True
npkwargs = eval('dict(' + args.plot + ')') # args were passed
pkwargs.update(npkwargs)
cerebro.plot(**pkwargs)
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for Tharp example with MACD')
group1 = parser.add_mutually_exclusive_group(required=True)
group1.add_argument('--data', required=False, default=None,
help='Specific data to be read in')
group1.add_argument('--dataset', required=False, action='store',
default=None, choices=DATASETS.keys(),
help='Choose one of the predefined data sets')
parser.add_argument('--fromdate', required=False,
default='2005-01-01',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', required=False,
default=None,
help='Ending date in YYYY-MM-DD format')
parser.add_argument('--cash', required=False, action='store',
type=float, default=50000,
help=('Cash to start with'))
parser.add_argument('--cashalloc', required=False, action='store',
type=float, default=0.20,
help=('Perc (abs) of cash to allocate for ops'))
parser.add_argument('--commperc', required=False, action='store',
type=float, default=0.0033,
help=('Perc (abs) commision in each operation. '
'0.001 -> 0.1%%, 0.01 -> 1%%'))
parser.add_argument('--macd1', required=False, action='store',
type=int, default=12,
help=('MACD Period 1 value'))
parser.add_argument('--macd2', required=False, action='store',
type=int, default=26,
help=('MACD Period 2 value'))
parser.add_argument('--macdsig', required=False, action='store',
type=int, default=9,
help=('MACD Signal Period value'))
parser.add_argument('--atrperiod', required=False, action='store',
type=int, default=14,
help=('ATR Period To Consider'))
parser.add_argument('--atrdist', required=False, action='store',
type=float, default=3.0,
help=('ATR Factor for stop price calculation'))
parser.add_argument('--smaperiod', required=False, action='store',
type=int, default=30,
help=('Period for the moving average'))
parser.add_argument('--dirperiod', required=False, action='store',
type=int, default=10,
help=('Period for SMA direction calculation'))
parser.add_argument('--riskfreerate', required=False, action='store',
type=float, default=0.01,
help=('Risk free rate in Perc (abs) of the asset for '
'the Sharpe Ratio'))
# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example:\n'
'\n'
' --plot style="candle" (to plot candles)\n'))
if pargs is not None:
return parser.parse_args(pargs)
return parser.parse_args()
if __name__ == '__main__':
runstrat()
Pinkfish 挑战
原文:
www.backtrader.com/blog/posts/2016-07-29-pinkfish-challenge/pinkfish-challenge/
(样本和更改添加到版本 1.7.1.93)
在发展过程中,backtrader 已经变得更加成熟,新增了功能,当然也变得更加复杂。许多新功能是在用户的请求、评论和问题之后引入的。一些小挑战证明了大多数设计决策至少不是那么错误,即使有些事情可能有很多其他方式来完成,有时可能更好。
因此,似乎这些小挑战是为了测试平台对新的未计划和意外情况的灵活性和适应性,pinkfish挑战是另一个例子。pinkfish是另一个 Python 回测框架(在README
中列出),可以在以下网址找到:pinkfish。该网站包含了需要解决的挑战:
- ‘买入收盘价’在‘新的 20 日高点设定’的当天是不允许的
其中一个特点提供了平台如何为这样的壮举运作的提示:
- 使用每日数据(而不是分钟或 tick 数据)进行日内交易
作者对当时现有的回测库的复杂性感到厌烦。当时的情况是否适用于backtrader(当时还处于初期阶段)是由pinkfish的作者自己回答的问题。
无修改解决方案
backtrader 支持数据源的过滤器,其中一个允许
breaking a *daily bar* in 2 parts to let people buy after having seen only the
opening price. The 2nd part of the day (high, low, close) is evaluated in a
2nd tick. This effectively achieves the *uses daily data (vs minute or tick
data) for intraday trading*.
这个筛选器试图进行完整的重播操作,而不涉及内置的重播器。
这个筛选器的明显演变将每日柱破解为两根柱,第一根是(开盘价,最高价,最低价),然后是第二根完整的柱(开盘价,最高价,最低价,收盘价)。
买入收盘价是通过使用backtrader.Order.Close
作为执行类型来实现的。
这在可用的样本中使用-no-replay
。一个执行:
$ ./pinkfish-challenge.py --no-replay
输出的一部分:
...
0955,0478,0478,2006-11-22T00:00:00,27.51,28.56,27.29,28.49,16027900.00,0.00
High 28.56 > Highest 28.56
LAST 19 highs: array('d', [25.33, 25.6, 26.4, 26.7, 26.62, 26.6, 26.7, 26.7, 27.15, 27.25, 27.65, 27.5, 27.62, 27.5, 27.5, 27.33, 27.05, 27.04, 27.34])
-- BUY on date: 2006-11-22
-- BUY Completed on: 2006-11-22
-- BUY Price: 28.49
0956,0478,0478,2006-11-22T23:59:59.999989,27.51,28.56,27.29,28.49,32055800.00,0.00
...
它有效…
-
在看到当天第一部分(行:
0955
)之后 -
如果有一个新的 20 日高点,就会发出一个
Close
订单 -
订单是通过当天第二部分的收盘价格执行的(行:
0956
)收盘价为
28.49
,这是在策略中notify_order
中看到的买入价格。
输出包含相当冗长的部分,仅用于识别最后的20
个高点。样本也非常快速出售,以便多次测试行为。但持有期可以通过--sellafter N
进行更改,其中N
是取消之前持有的柱数(请参见末尾的用法)
no mod
解决方案的问题
这实际上不是一个重播解决方案,如果将订单的执行类型从Close
更改为Market
,就会看到这一点。一个新的执行:
$ ./pinkfish-challenge.py --no-replay --market
现在与上述相同期间的输出:
...
0955,0478,0478,2006-11-22T00:00:00,27.51,28.56,27.29,28.49,16027900.00,0.00
High 28.56 > Highest 28.56
LAST 19 highs: array('d', [25.33, 25.6, 26.4, 26.7, 26.62, 26.6, 26.7, 26.7, 27.15, 27.25, 27.65, 27.5, 27.62, 27.5, 27.5, 27.33, 27.05, 27.04, 27.34])
-- BUY on date: 2006-11-22
-- BUY Completed on: 2006-11-22
-- BUY Price: 27.51
0956,0478,0478,2006-11-22T23:59:59.999989,27.51,28.56,27.29,28.49,32055800.00,0.00
...
问题很容易被识别出来
-
订单执行时,与收盘价相反,因为市价订单取第二根柱中可用的第一价,即
27.51
,而这恰好是当天的开盘价,不再可用。这是因为过滤器实际上并非真正回放,而是将柱子分成两部分,并进行软性回放。
正确的“mod”解决方案
同样获取Market
订单以选择收盘价。
这包括:
-
一个将柱子分成两部分的过滤器
-
并且与backtrader中可用的标准回放功能兼容
在这种情况下,第二根柱仅由
close
价格组成,即使显示显示完整的柱,内部机制也只会将订单与tick匹配
backtrader中的链接过滤器已经是可能的,但这种用法尚未考虑:
-
将单个数据“心跳”拆分为 2 个数据“心跳”
在此挑战之前,主要是将柱子合并为较大的柱子。
对核心机制加载柱进行了小扩展,允许过滤器将柱的第二部分添加到内部存储中以进行重新处理,然后再考虑新数据心跳。而且因为它是一个扩展而不是修改,所以没有影响。
此挑战还提供了机会:
-
再次查看backtrader最初编写的早期代码以获取
Close
订单。在这里,一些代码行和
if
条件已经重新设计,以使匹配Close
订单更加合乎逻辑,并且如果可能的话,将其立即交付给系统(即使匹配到正确的柱上,交付也通常会延迟 1 根柱)
在这些变化之后的一个好处是:
- 过滤器中的逻辑更加简单,因为没有微妙的回放尝试。 回放由回放过滤器完成。
分解柱子第一部分的过滤器解剖:
-
复制传入数据柱
-
将其复制为OHL柱(无 Close)
-
将时间更改为日期 + sessionstart时间
-
移除部分体积(由参数closevol指定给过滤器)
-
使
OpenInterest
失效(在当天结束时可用) -
移除
close
价格并用OHL的平均值替换它 -
将柱子添加到内部栈以供下一个过滤器或策略立即处理(回放过滤器将接管)
分解柱子第二部分的解剖:
-
复制传入数据柱
-
将 OHL 价格替换为
Close
价格 -
将时间更改为日期 + sessionend时间
-
移除体积的其他部分(由参数closevol指定给过滤器)
-
设置
OpenInterest
-
将柱子添加到内部stash以延迟处理为下一个数据心跳,而不是从数据中获取价格
代码:
# Make a copy of current data for ohlbar
ohlbar = [data.lines[i][0] for i in range(data.size())]
closebar = ohlbar[:] # Make a copy for the close
# replace close price with o-h-l average
ohlprice = ohlbar[data.Open] + ohlbar[data.High] + ohlbar[data.Low]
ohlbar[data.Close] = ohlprice / 3.0
vol = ohlbar[data.Volume] # adjust volume
ohlbar[data.Volume] = vohl = int(vol * (1.0 - self.p.closevol))
oi = ohlbar[data.OpenInterest] # adjust open interst
ohlbar[data.OpenInterest] = 0
# Adjust times
dt = datetime.datetime.combine(datadt, data.p.sessionstart)
ohlbar[data.DateTime] = data.date2num(dt)
# Adjust closebar to generate a single tick -> close price
closebar[data.Open] = cprice = closebar[data.Close]
closebar[data.High] = cprice
closebar[data.Low] = cprice
closebar[data.Volume] = vol - vohl
ohlbar[data.OpenInterest] = oi
# Adjust times
dt = datetime.datetime.combine(datadt, data.p.sessionend)
closebar[data.DateTime] = data.date2num(dt)
# Update stream
data.backwards(force=True) # remove the copied bar from stream
data._add2stack(ohlbar) # add ohlbar to stack
# Add 2nd part to stash to delay processing to next round
data._add2stack(closebar, stash=True)
return False # the length of the stream was not changed
在不禁用回放和Close
的情况下执行(让我们添加绘图):
$ ./pinkfish-challenge.py --plot
同一时期的输出:
...
0955,0478,0478,2006-11-22T00:00:00,27.51,28.56,27.29,27.79,16027900.00,0.00
High 28.56 > Highest 28.56
LAST 19 highs: array('d', [25.33, 25.6, 26.4, 26.7, 26.62, 26.6, 26.7, 26.7, 27.15, 27.25, 27.65, 27.5, 27.62, 27.5, 27.5, 27.33, 27.05, 27.04, 27.34])
-- BUY on date: 2006-11-22
-- BUY Completed on: 2006-11-22
-- BUY Price: 28.49
0956,0478,0478,2006-11-22T23:59:59.999989,27.51,28.56,27.29,28.49,32055800.00,0.00
...
一切正常,已记录收盘价为28.49
。
以及图表。
最后但同样重要的是检查修改是否有意义:
$ ./pinkfish-challenge.py --market
相同时期的输出:
...
0955,0478,0478,2006-11-22T00:00:00,27.51,28.56,27.29,27.79,16027900.00,0.00
High 28.56 > Highest 28.56
LAST 19 highs: array('d', [25.33, 25.6, 26.4, 26.7, 26.62, 26.6, 26.7, 26.7, 27.15, 27.25, 27.65, 27.5, 27.62, 27.5, 27.5, 27.33, 27.05, 27.04, 27.34])
-- BUY on date: 2006-11-22
-- BUY Completed on: 2006-11-22
-- BUY Price: 28.49
0956,0478,0478,2006-11-22T23:59:59.999989,27.51,28.56,27.29,28.49,32055800.00,0.00
..
现在Market
订单正在以与Close
订单相同的价格28.49
拾取,这在这个特定的用例中是预期的,因为重播正在发生,而破碎的日线的第二部分有一个单一的标记:28.49
,这是收盘价
示例的用法
$ ./pinkfish-challenge.py --help
usage: pinkfish-challenge.py [-h] [--data DATA] [--fromdate FROMDATE]
[--todate TODATE] [--cash CASH]
[--sellafter SELLAFTER] [--highperiod HIGHPERIOD]
[--no-replay] [--market] [--oldbuysell]
[--plot [kwargs]]
Sample for pinkfish challenge
optional arguments:
-h, --help show this help message and exit
--data DATA Data to be read in (default:
../../datas/yhoo-1996-2015.txt)
--fromdate FROMDATE Starting date in YYYY-MM-DD format (default:
2005-01-01)
--todate TODATE Ending date in YYYY-MM-DD format (default: 2006-12-31)
--cash CASH Cash to start with (default: 50000)
--sellafter SELLAFTER
Sell after so many bars in market (default: 2)
--highperiod HIGHPERIOD
Period to look for the highest (default: 20)
--no-replay Use Replay + replay filter (default: False)
--market Use Market exec instead of Close (default: False)
--oldbuysell Old buysell plot behavior - ON THE PRICE (default:
False)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example (escape the quotes if needed): --plot
style="candle" (to plot candles) (default: None)
并且代码本身
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
import backtrader.indicators as btind
class DayStepsCloseFilter(bt.with_metaclass(bt.MetaParams, object)):
'''
Replays a bar in 2 steps:
- In the 1st step the "Open-High-Low" could be evaluated to decide if to
act on the close (the close is still there ... should not be evaluated)
- If a "Close" order has been executed
In this 1st fragment the "Close" is replaced through the "open" althoug
other alternatives would be possible like high - low average, or an
algorithm based on where the "close" ac
and
- Open-High-Low-Close
'''
params = (
('cvol', 0.5), # 0 -> 1 amount of volume to keep for close
)
def __init__(self, data):
self.pendingbar = None
def __call__(self, data):
# Make a copy of the new bar and remove it from stream
closebar = [data.lines[i][0] for i in range(data.size())]
datadt = data.datetime.date() # keep the date
ohlbar = closebar[:] # Make an open-high-low bar
# Adjust volume
ohlbar[data.Volume] = int(closebar[data.Volume] * (1.0 - self.p.cvol))
dt = datetime.datetime.combine(datadt, data.p.sessionstart)
ohlbar[data.DateTime] = data.date2num(dt)
dt = datetime.datetime.combine(datadt, data.p.sessionend)
closebar[data.DateTime] = data.date2num(dt)
# Update stream
data.backwards() # remove the copied bar from stream
# Overwrite the new data bar with our pending data - except start point
if self.pendingbar is not None:
data._updatebar(self.pendingbar)
self.pendingbar = closebar # update the pending bar to the new bar
data._add2stack(ohlbar) # Add the openbar to the stack for processing
return False # the length of the stream was not changed
def last(self, data):
'''Called when the data is no longer producing bars
Can be called multiple times. It has the chance to (for example)
produce extra bars'''
if self.pendingbar is not None:
data.backwards() # remove delivered open bar
data._add2stack(self.pendingbar) # add remaining
self.pendingbar = None # No further action
return True # something delivered
return False # nothing delivered here
class DayStepsReplayFilter(bt.with_metaclass(bt.MetaParams, object)):
'''
Replays a bar in 2 steps:
- In the 1st step the "Open-High-Low" could be evaluated to decide if to
act on the close (the close is still there ... should not be evaluated)
- If a "Close" order has been executed
In this 1st fragment the "Close" is replaced through the "open" althoug
other alternatives would be possible like high - low average, or an
algorithm based on where the "close" ac
and
- Open-High-Low-Close
'''
params = (
('closevol', 0.5), # 0 -> 1 amount of volume to keep for close
)
# replaying = True
def __init__(self, data):
self.lastdt = None
pass
def __call__(self, data):
# Make a copy of the new bar and remove it from stream
datadt = data.datetime.date() # keep the date
if self.lastdt == datadt:
return False # skip bars that come again in the filter
self.lastdt = datadt # keep ref to last seen bar
# Make a copy of current data for ohlbar
ohlbar = [data.lines[i][0] for i in range(data.size())]
closebar = ohlbar[:] # Make a copy for the close
# replace close price with o-h-l average
ohlprice = ohlbar[data.Open] + ohlbar[data.High] + ohlbar[data.Low]
ohlbar[data.Close] = ohlprice / 3.0
vol = ohlbar[data.Volume] # adjust volume
ohlbar[data.Volume] = vohl = int(vol * (1.0 - self.p.closevol))
oi = ohlbar[data.OpenInterest] # adjust open interst
ohlbar[data.OpenInterest] = 0
# Adjust times
dt = datetime.datetime.combine(datadt, data.p.sessionstart)
ohlbar[data.DateTime] = data.date2num(dt)
# Adjust closebar to generate a single tick -> close price
closebar[data.Open] = cprice = closebar[data.Close]
closebar[data.High] = cprice
closebar[data.Low] = cprice
closebar[data.Volume] = vol - vohl
ohlbar[data.OpenInterest] = oi
# Adjust times
dt = datetime.datetime.combine(datadt, data.p.sessionend)
closebar[data.DateTime] = data.date2num(dt)
# Update stream
data.backwards(force=True) # remove the copied bar from stream
data._add2stack(ohlbar) # add ohlbar to stack
# Add 2nd part to stash to delay processing to next round
data._add2stack(closebar, stash=True)
return False # the length of the stream was not changed
class St(bt.Strategy):
params = (
('highperiod', 20),
('sellafter', 2),
('market', False),
)
def __init__(self):
pass
def start(self):
self.callcounter = 0
txtfields = list()
txtfields.append('Calls')
txtfields.append('Len Strat')
txtfields.append('Len Data')
txtfields.append('Datetime')
txtfields.append('Open')
txtfields.append('High')
txtfields.append('Low')
txtfields.append('Close')
txtfields.append('Volume')
txtfields.append('OpenInterest')
print(','.join(txtfields))
self.lcontrol = 0 # control if 1st or 2nd call
self.inmarket = 0
# Get the highest but delayed 1 ... to avoid "today"
self.highest = btind.Highest(self.data.high,
period=self.p.highperiod,
subplot=False)
def notify_order(self, order):
if order.isbuy() and order.status == order.Completed:
print('-- BUY Completed on:',
self.data.num2date(order.executed.dt).strftime('%Y-%m-%d'))
print('-- BUY Price:', order.executed.price)
def next(self):
self.callcounter += 1
txtfields = list()
txtfields.append('%04d' % self.callcounter)
txtfields.append('%04d' % len(self))
txtfields.append('%04d' % len(self.data0))
txtfields.append(self.data.datetime.datetime(0).isoformat())
txtfields.append('%.2f' % self.data0.open[0])
txtfields.append('%.2f' % self.data0.high[0])
txtfields.append('%.2f' % self.data0.low[0])
txtfields.append('%.2f' % self.data0.close[0])
txtfields.append('%.2f' % self.data0.volume[0])
txtfields.append('%.2f' % self.data0.openinterest[0])
print(','.join(txtfields))
if not self.position:
if len(self.data) > self.lcontrol:
if self.data.high == self.highest: # today is highest!!!
print('High %.2f > Highest %.2f' %
(self.data.high[0], self.highest[0]))
print('LAST 19 highs:',
self.data.high.get(size=19, ago=-1))
print('-- BUY on date:',
self.data.datetime.date().strftime('%Y-%m-%d'))
ex = bt.Order.Market if self.p.market else bt.Order.Close
self.buy(exectype=ex)
self.inmarket = len(self) # reset period in market
else: # in the market
if (len(self) - self.inmarket) >= self.p.sellafter:
self.sell()
self.lcontrol = len(self.data)
def runstrat():
args = parse_args()
cerebro = bt.Cerebro()
cerebro.broker.set_cash(args.cash)
cerebro.broker.set_eosbar(True)
dkwargs = dict()
if args.fromdate:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dkwargs['fromdate'] = fromdate
if args.todate:
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dkwargs['todate'] = todate
if args.no_replay:
data = bt.feeds.YahooFinanceCSVData(dataname=args.data,
timeframe=bt.TimeFrame.Days,
compression=1,
**dkwargs)
data.addfilter(DayStepsCloseFilter)
cerebro.adddata(data)
else:
data = bt.feeds.YahooFinanceCSVData(dataname=args.data,
timeframe=bt.TimeFrame.Minutes,
compression=1,
**dkwargs)
data.addfilter(DayStepsReplayFilter)
cerebro.replaydata(data, timeframe=bt.TimeFrame.Days, compression=1)
cerebro.addstrategy(St,
sellafter=args.sellafter,
highperiod=args.highperiod,
market=args.market)
cerebro.run(runonce=False, preload=False, oldbuysell=args.oldbuysell)
if args.plot:
pkwargs = dict(style='bar')
if args.plot is not True: # evals to True but is not True
npkwargs = eval('dict(' + args.plot + ')') # args were passed
pkwargs.update(npkwargs)
cerebro.plot(**pkwargs)
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for pinkfish challenge')
parser.add_argument('--data', required=False,
default='../../datas/yhoo-1996-2015.txt',
help='Data to be read in')
parser.add_argument('--fromdate', required=False,
default='2005-01-01',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', required=False,
default='2006-12-31',
help='Ending date in YYYY-MM-DD format')
parser.add_argument('--cash', required=False, action='store',
type=float, default=50000,
help=('Cash to start with'))
parser.add_argument('--sellafter', required=False, action='store',
type=int, default=2,
help=('Sell after so many bars in market'))
parser.add_argument('--highperiod', required=False, action='store',
type=int, default=20,
help=('Period to look for the highest'))
parser.add_argument('--no-replay', required=False, action='store_true',
help=('Use Replay + replay filter'))
parser.add_argument('--market', required=False, action='store_true',
help=('Use Market exec instead of Close'))
parser.add_argument('--oldbuysell', required=False, action='store_true',
help=('Old buysell plot behavior - ON THE PRICE'))
# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example (escape the quotes if needed):\n'
'\n'
' --plot style="candle" (to plot candles)\n'))
if pargs is not None:
return parser.parse_args(pargs)
return parser.parse_args()
if __name__ == '__main__':
runstrat()
TA-Lib
原文:
www.backtrader.com/blog/posts/2016-07-26-talib-integration/talib-integration/
即使backtrader提供了大量内置指标,并且开发指标主要是定义输入、输出并以自然方式编写公式,一些人还是想使用TA-LIB。一些原因:
-
指标X在库中而不在backtrader中(作者将很乐意接受请求)
-
TA-LIB的行为是众所周知的,人们信任老牌东西
为了满足每个口味,TA-LIB集成是提供的。
要求
-
它需要的任何依赖项(例如numpy)
安装详情在GitHub存储库中
使用ta-lib
就像使用backtrader中已经内置的任何指标一样容易。简单移动平均的示例。首先是backtrader的:
import backtrader as bt
class MyStrategy(bt.Strategy):
params = (('period', 20),)
def __init__(self):
self.sma = bt.indicators.SMA(self.data, period=self.p.period)
...
...
现在是ta-lib的示例:
import backtrader as bt
class MyStrategy(bt.Strategy):
params = (('period', 20),)
def __init__(self):
self.sma = bt.talib.SMA(self.data, timeperiod=self.p.period)
...
...
哦,就这样!当然,ta-lib指标的params由库本身定义,而不是由backtrader定义。在这种情况下,ta-lib中的SMA需要一个名为timeperiod
的参数来定义操作窗口的大小。
对于需要多个输入的指标,例如随机指标:
import backtrader as bt
class MyStrategy(bt.Strategy):
params = (('period', 20),)
def __init__(self):
self.stoc = bt.talib.STOCH(self.data.high, self.data.low, self.data.close,
fastk_period=14, slowk_period=3, slowd_period=3)
...
...
注意high
、low
和close
已经被单独传递。人们总是可以传递open
而不是low
(或任何其他数据系列)进行实验。
ta-lib指标文档会自动解析并添加到backtrader文档中。您还可以查看ta-lib源代码/文档。或者额外执行:
print(bt.talib.SMA.__doc__)
在这种情况下输出:
SMA([input_arrays], [timeperiod=30])
Simple Moving Average (Overlap Studies)
Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
real
提供一些信息:
-
应该期望哪个输入(DISREGARD
ndarray
评论,因为 backtrader 在后台管理转换) -
哪些参数和默认值
-
哪个线提供了指标的输出
移动平均线和 MA_Type
对于像bt.talib.STOCH
这样的指标选择特定的移动平均线,标准ta-lib MA_Type
可以通过bactrader.talib.MA_Type
来访问。例如:
import backtrader as bt
print('SMA:', bt.talib.MA_Type.SMA)
print('T3:', bt.talib.MA_Type.T3)
绘制 ta-lib 指标
就像常规使用一样,对于绘制ta-lib指标没有什么特别的要做。
注意
输出CANDLE的指标(所有寻找蜡烛图形式的指标)提供二进制输出:要么是 0,要么是 100。为了避免将subplot
添加到图表中,有一个自动绘图转换来在识别模式的时间点上在data上绘制它们。
示例和比较
以下是一些ta-lib指标输出与backtrader中等效内置指标输出的图表比较。要考虑的事项:
-
ta-lib指标在图表上加了一个
TA_
前缀。这是为了帮助用户区分哪个是哪个 -
移动平均线(如果两者产生相同的结果)将绘制在其他现有移动平均线的顶部。这两个指标不能分开看,如果是这样,测试就通过了。
-
所有示例都包括
CDLDOJI
指标作为参考
KAMA(Kaufman 移动平均)
这是第 1 个示例,因为它是唯一一个(与示例直接进行比较的所有指标中)有差异的示例:
-
样本的初始值不相同
-
在某个时间点,值会收敛,两个KAMA实现都会有相同的行为。
分析了ta-lib源代码之后:
-
ta-lib中的实现对KAMA的第 1 个值做出了非行业标准的选择。
选择可以从源代码中看到(引用源代码):这里使用昨天的价格作为前一天的 KAMA。
backtrader 做了与Stockcharts相同的常规选择:
-
由于我们需要一个初始值来开始计算,第一个 KAMA 只是一个简单的移动平均线
因此有所不同。此外:
- ta-lib的
KAMA
实现不允许指定快速
和慢速
周期来调整Kaufman定义的可缩放常数。
示例执行:
$ ./talibtest.py --plot --ind kama
输出
SMA
$ ./talibtest.py --plot --ind sma
输出
EMA
$ ./talibtest.py --plot --ind ema
输出
随机指标
$ ./talibtest.py --plot --ind stoc
输出
RSI
$ ./talibtest.py --plot --ind rsi
输出
MACD
$ ./talibtest.py --plot --ind macd
输出
布林带
$ ./talibtest.py --plot --ind bollinger
输出
AROON
请注意,ta-lib选择将下行线放在前面,当与backtrader内置指标进行比较时,颜色会反转。
$ ./talibtest.py --plot --ind aroon
输出
终极波动率
$ ./talibtest.py --plot --ind ultimate
输出
Trix
$ ./talibtest.py --plot --ind trix
输出
ADXR
在这里,backtrader 提供了ADX
和ADXR
线。
$ ./talibtest.py --plot --ind adxr
输出
DEMA
$ ./talibtest.py --plot --ind dema
输出
TEMA
$ ./talibtest.py --plot --ind tema
输出
PPO
在这里,backtrader不仅提供了ppo
线,还提供了更传统的macd
方法。
$ ./talibtest.py --plot --ind ppo
输出
WilliamsR
$ ./talibtest.py --plot --ind williamsr
输出
ROC
所有指标都具有完全相同的形状,但如何跟踪动量或变化率有几种定义。
$ ./talibtest.py --plot --ind roc
输出
示例用法
$ ./talibtest.py --help
usage: talibtest.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE]
[--ind {sma,ema,stoc,rsi,macd,bollinger,aroon,ultimate,trix,kama,adxr,dema,tema,ppo,williamsr,roc}]
[--no-doji] [--use-next] [--plot [kwargs]]
Sample for sizer
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to be read in (default:
../../datas/yhoo-1996-2015.txt)
--fromdate FROMDATE Starting date in YYYY-MM-DD format (default:
2005-01-01)
--todate TODATE Ending date in YYYY-MM-DD format (default: 2006-12-31)
--ind {sma,ema,stoc,rsi,macd,bollinger,aroon,ultimate,trix,kama,adxr,dema,tema,ppo,williamsr,roc}
Which indicator pair to show together (default: sma)
--no-doji Remove Doji CandleStick pattern checker (default:
False)
--use-next Use next (step by step) instead of once (batch)
(default: False)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example (escape the quotes if needed): --plot
style="candle" (to plot candles) (default: None)
示例代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class TALibStrategy(bt.Strategy):
params = (('ind', 'sma'), ('doji', True),)
INDS = ['sma', 'ema', 'stoc', 'rsi', 'macd', 'bollinger', 'aroon',
'ultimate', 'trix', 'kama', 'adxr', 'dema', 'ppo', 'tema',
'roc', 'williamsr']
def __init__(self):
if self.p.doji:
bt.talib.CDLDOJI(self.data.open, self.data.high,
self.data.low, self.data.close)
if self.p.ind == 'sma':
bt.talib.SMA(self.data.close, timeperiod=25, plotname='TA_SMA')
bt.indicators.SMA(self.data, period=25)
elif self.p.ind == 'ema':
bt.talib.EMA(timeperiod=25, plotname='TA_SMA')
bt.indicators.EMA(period=25)
elif self.p.ind == 'stoc':
bt.talib.STOCH(self.data.high, self.data.low, self.data.close,
fastk_period=14, slowk_period=3, slowd_period=3,
plotname='TA_STOCH')
bt.indicators.Stochastic(self.data)
elif self.p.ind == 'macd':
bt.talib.MACD(self.data, plotname='TA_MACD')
bt.indicators.MACD(self.data)
bt.indicators.MACDHisto(self.data)
elif self.p.ind == 'bollinger':
bt.talib.BBANDS(self.data, timeperiod=25,
plotname='TA_BBANDS')
bt.indicators.BollingerBands(self.data, period=25)
elif self.p.ind == 'rsi':
bt.talib.RSI(self.data, plotname='TA_RSI')
bt.indicators.RSI(self.data)
elif self.p.ind == 'aroon':
bt.talib.AROON(self.data.high, self.data.low, plotname='TA_AROON')
bt.indicators.AroonIndicator(self.data)
elif self.p.ind == 'ultimate':
bt.talib.ULTOSC(self.data.high, self.data.low, self.data.close,
plotname='TA_ULTOSC')
bt.indicators.UltimateOscillator(self.data)
elif self.p.ind == 'trix':
bt.talib.TRIX(self.data, timeperiod=25, plotname='TA_TRIX')
bt.indicators.Trix(self.data, period=25)
elif self.p.ind == 'adxr':
bt.talib.ADXR(self.data.high, self.data.low, self.data.close,
plotname='TA_ADXR')
bt.indicators.ADXR(self.data)
elif self.p.ind == 'kama':
bt.talib.KAMA(self.data, timeperiod=25, plotname='TA_KAMA')
bt.indicators.KAMA(self.data, period=25)
elif self.p.ind == 'dema':
bt.talib.DEMA(self.data, timeperiod=25, plotname='TA_DEMA')
bt.indicators.DEMA(self.data, period=25)
elif self.p.ind == 'ppo':
bt.talib.PPO(self.data, plotname='TA_PPO')
bt.indicators.PPO(self.data, _movav=bt.indicators.SMA)
elif self.p.ind == 'tema':
bt.talib.TEMA(self.data, timeperiod=25, plotname='TA_TEMA')
bt.indicators.TEMA(self.data, period=25)
elif self.p.ind == 'roc':
bt.talib.ROC(self.data, timeperiod=12, plotname='TA_ROC')
bt.talib.ROCP(self.data, timeperiod=12, plotname='TA_ROCP')
bt.talib.ROCR(self.data, timeperiod=12, plotname='TA_ROCR')
bt.talib.ROCR100(self.data, timeperiod=12, plotname='TA_ROCR100')
bt.indicators.ROC(self.data, period=12)
bt.indicators.Momentum(self.data, period=12)
bt.indicators.MomentumOscillator(self.data, period=12)
elif self.p.ind == 'williamsr':
bt.talib.WILLR(self.data.high, self.data.low, self.data.close,
plotname='TA_WILLR')
bt.indicators.WilliamsR(self.data)
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
dkwargs = dict()
if args.fromdate:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dkwargs['fromdate'] = fromdate
if args.todate:
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dkwargs['todate'] = todate
data0 = bt.feeds.YahooFinanceCSVData(dataname=args.data0, **dkwargs)
cerebro.adddata(data0)
cerebro.addstrategy(TALibStrategy, ind=args.ind, doji=not args.no_doji)
cerebro.run(runcone=not args.use_next, stdstats=False)
if args.plot:
pkwargs = dict(style='candle')
if args.plot is not True: # evals to True but is not True
npkwargs = eval('dict(' + args.plot + ')') # args were passed
pkwargs.update(npkwargs)
cerebro.plot(**pkwargs)
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for sizer')
parser.add_argument('--data0', required=False,
default='../../datas/yhoo-1996-2015.txt',
help='Data to be read in')
parser.add_argument('--fromdate', required=False,
default='2005-01-01',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', required=False,
default='2006-12-31',
help='Ending date in YYYY-MM-DD format')
parser.add_argument('--ind', required=False, action='store',
default=TALibStrategy.INDS[0],
choices=TALibStrategy.INDS,
help=('Which indicator pair to show together'))
parser.add_argument('--no-doji', required=False, action='store_true',
help=('Remove Doji CandleStick pattern checker'))
parser.add_argument('--use-next', required=False, action='store_true',
help=('Use next (step by step) '
'instead of once (batch)'))
# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example (escape the quotes if needed):\n'
'\n'
' --plot style="candle" (to plot candles)\n'))
if pargs is not None:
return parser.parse_args(pargs)
return parser.parse_args()
if __name__ == '__main__':
runstrat()
Sizers 智能定额
原文:
www.backtrader.com/blog/posts/2016-07-23-sizers-smart-staking/sizers-smart-staking/
发行版 1.6.4.93 标志着 backtrader 的一个重要里程碑,即使版本编号的更改只是一个小改变。
Position Sizing 是阅读 Van K. Tharp 的 Trade Your Way To Financial Freedom 后,实际上为这个项目奠定了基础的事情之一。
这不是 Van K. Tharp 详细介绍 Position Sizing 方法的书,但该主题在书中被介绍和讨论。关于这一点的一个例子有这样的设置。
-
如果市场上没有,抛硬币决定是否进入
-
如果已经在市场上,则通过一个 2 倍的 ATR 控制仓位,并且如果价格有利于持有的仓位,则更新该止损
关于此的重要部分:
-
进入市场是随机的
-
这种方法经过不同的 Sizing 方案测试,并且有一个动态止损,这使得系统具有盈利能力。
并遵循设置自己的“系统”(无论是手动/自动化/计算机化,技术/基本,...)的原则,backtrader 有一天将测试这种情景。
这可以通过任何现有的平台进行测试,但沿途不会有乐趣,也不会解决许多挑战,这些挑战在启动 backtrader 时甚至没有考虑过。
Sizers 从一开始就在平台上,但被隐藏了,因为许多其他事情,包括 实时交易 开始阻碍。但这现在已经结束,Van K. Tharp 的情景将被测试。比以往更早。
同时进行了一些 Sizers 的样本测试。
Sizers 控制定位
该样本显示了一种潜在的用例,其中 Sizers 通过控制 sizing 来改变策略的行为。查看 backtrader.readthedocs.io 上的文档以了解 sizing interface。
这两个 Sizers:
-
LongOnly
:如果当前位置为 0,则返回固定大小的仓位,并且如果已经在市场上,则返回相同的固定大小以关闭它。class LongOnly(bt.Sizer): params = (('stake', 1),) def _getsizing(self, comminfo, cash, data, isbuy): if isbuy: return self.p.stake # Sell situation position = self.strategy.getposition(data) if not position.size: return 0 # do not sell if nothing is open return self.p.stake`
-
FixedReverser
:如果市场上没有,将返回固定大小的赌注,如果已经在市场上,则将返回加倍的固定大小的赌注,以便进行逆转class FixedReverser(bt.Sizer): params = (('stake', 1),) def _getsizing(self, comminfo, cash, data, isbuy): position = self.broker.getposition(data) size = self.p.stake * (1 + (position.size != 0)) return size`
这两个 Sizers 将与一个非常简单的策略结合在一起。
class CloseSMA(bt.Strategy):
params = (('period', 15),)
def __init__(self):
sma = bt.indicators.SMA(self.data, period=self.p.period)
self.crossover = bt.indicators.CrossOver(self.data, sma)
def next(self):
if self.crossover > 0:
self.buy()
elif self.crossover < 0:
self.sell()
注意策略如何使用 Close-SMA 交叉信号发出 buy 和 sell 命令,并考虑到一个重要的事情:
- 在 strategy 中不进行定位的检查
与下面的执行中看到的相同策略通过在样本中使用此代码(通过开关 --longonly
控制)仅通过更改 sizer 的行为从 long-only 变为 long-short。
if args.longonly:
cerebro.addsizer(LongOnly, stake=args.stake)
else:
cerebro.addsizer(FixedReverser, stake=args.stake)
仅限多头执行
用命令完成:
$ ./sizertest.py --longonly --plot
这个输出。
多空头执行
用命令完成:
$ ./sizertest.py --plot
并输出如下结果。
立即显示:
-
交易次数翻了一番
-
现金(除了一开始)永远不等于价值,因为策略始终在市场上
使用示例
$ ./sizertest.py --help
usage: sizertest.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cash CASH] [--longonly]
[--stake STAKE] [--period PERIOD] [--plot [kwargs]]
Sample for sizer
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to be read in (default:
../../datas/yhoo-1996-2015.txt)
--fromdate FROMDATE Starting date in YYYY-MM-DD format (default:
2005-01-01)
--todate TODATE Ending date in YYYY-MM-DD format (default: 2006-12-31)
--cash CASH Cash to start with (default: 50000)
--longonly Use the LongOnly sizer (default: False)
--stake STAKE Stake to pass to the sizers (default: 1)
--period PERIOD Period for the Simple Moving Average (default: 15)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example: --plot style="candle" (to plot candles)
(default: None)
完整代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import random
import backtrader as bt
class CloseSMA(bt.Strategy):
params = (('period', 15),)
def __init__(self):
sma = bt.indicators.SMA(self.data, period=self.p.period)
self.crossover = bt.indicators.CrossOver(self.data, sma)
def next(self):
if self.crossover > 0:
self.buy()
elif self.crossover < 0:
self.sell()
class LongOnly(bt.Sizer):
params = (('stake', 1),)
def _getsizing(self, comminfo, cash, data, isbuy):
if isbuy:
return self.p.stake
# Sell situation
position = self.strategy.getposition(data)
if not position.size:
return 0 # do not sell if nothing is open
return self.p.stake
class FixedReverser(bt.Sizer):
params = (('stake', 1),)
def _getsizing(self, comminfo, cash, data, isbuy):
position = self.broker.getposition(data)
size = self.p.stake * (1 + (position.size != 0))
return size
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
cerebro.broker.set_cash(args.cash)
dkwargs = dict()
if args.fromdate:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dkwargs['fromdate'] = fromdate
if args.todate:
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dkwargs['todate'] = todate
data0 = bt.feeds.YahooFinanceCSVData(dataname=args.data0, **dkwargs)
cerebro.adddata(data0, name='Data0')
cerebro.addstrategy(CloseSMA, period=args.period)
if args.longonly:
cerebro.addsizer(LongOnly, stake=args.stake)
else:
cerebro.addsizer(FixedReverser, stake=args.stake)
cerebro.run()
if args.plot:
pkwargs = dict()
if args.plot is not True: # evals to True but is not True
pkwargs = eval('dict(' + args.plot + ')') # args were passed
cerebro.plot(**pkwargs)
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for sizer')
parser.add_argument('--data0', required=False,
default='../../datas/yhoo-1996-2015.txt',
help='Data to be read in')
parser.add_argument('--fromdate', required=False,
default='2005-01-01',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', required=False,
default='2006-12-31',
help='Ending date in YYYY-MM-DD format')
parser.add_argument('--cash', required=False, action='store',
type=float, default=50000,
help=('Cash to start with'))
parser.add_argument('--longonly', required=False, action='store_true',
help=('Use the LongOnly sizer'))
parser.add_argument('--stake', required=False, action='store',
type=int, default=1,
help=('Stake to pass to the sizers'))
parser.add_argument('--period', required=False, action='store',
type=int, default=15,
help=('Period for the Simple Moving Average'))
# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example:\n'
'\n'
' --plot style="candle" (to plot candles)\n'))
if pargs is not None:
return parser.parse_args(pargs)
return parser.parse_args()
if __name__ == '__main__':
runstrat()