BackTrader 中文文档(二十二)(2)https://developer.aliyun.com/article/1505426
并且代码本身
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集成是提供的。
要求
- TA-Lib 的 Python 包装器
- 它需要的任何依赖项(例如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相同的常规选择:
- StockCharts 上的 KAMA
由于我们需要一个初始值来开始计算,第一个 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
输出
BackTrader 中文文档(二十二)(4)https://developer.aliyun.com/article/1505430