文献和/或行业中缺乏标准公式不是问题,因为问题实际上可以总结为:
- 条形同步
工单 #23 提出了一些问题,即是否可以 backtrader 考虑计算 相对体积 指针。
请求者需要将给定时刻的 volume 与前一个交易日的相同时刻进行比较。包括:
- 一些长度未知的上市前数据
有这样的要求会使大多数指针所依据的基本原则无效:
- 有一个固定的周期,用于向后看
此外,鉴于比较是在日内完成的,因此必须考虑其他一些因素:
-
一些「日内」时刻可能丢失(无论是分钟还是秒)
数据源不太可能缺少每日柱,但缺少分钟或秒柱并不罕见。
主要原因是可能根本没有进行任何谈判。或者它们可能是谈判交流中的一个问题,实际上根本无法记录酒吧。
考虑到上述所有要点,指针发展的一些结论:
-
在这种情况下,周期 不是周期,而是一个缓冲区,以确保有足够的柱线在那里,以便指针尽快启动
-
某些条形图可能缺失
-
主要问题是同步
幸运的是,有一个密钥可以为您提供说明:
- 比较的柱线是「日内」 因此计算给定时刻内已经看到的天数和看到的「柱」数量可以实现同步
前一天的值保存在字典中,因为前面解释的“回溯”周期是未知的。
其他一些早期的想法可以被丢弃,例如实现数据源DataFilter ,因为这实际上会通过删除上市前数据使数据源与系统的其他部分不同步。同步问题也会在那里。
要探索的一个想法是创建一个DataFiller 通过使用 last 收盘价并将 volume 设置为0来填充缺失的分钟/秒。
实践也被证明是很好的,可以识别一些额外的需求backtrader ,比如 time2num 函数(对date2num和num2date系列的补充),以及 lines的额外方法:
-
从一天的浮点表示中提取日部分的「日」和「分数」(时间)
被称为“dt”和“tm”
同时,指针的RelativeVolumeByBar 代码如下所示。在指针内进行“周期”/“缓冲区”计算不是首选模式,但在这种情况下,它达到了目的。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import collections
import datetime
import math
import backtrader as bt
def time2num(tm):
"""
Convert :mod:`time` to the to the preserving hours, minutes, seconds
and microseconds. Return value is a :func:`float`.
"""
HOURS_PER_DAY = 24.0
MINUTES_PER_HOUR = 60.0
SECONDS_PER_MINUTE = 60.0
MUSECONDS_PER_SECOND = 1e6
MINUTES_PER_DAY = MINUTES_PER_HOUR * HOURS_PER_DAY
SECONDS_PER_DAY = SECONDS_PER_MINUTE * MINUTES_PER_DAY
MUSECONDS_PER_DAY = MUSECONDS_PER_SECOND * SECONDS_PER_DAY
tm_num = (tm.hour / HOURS_PER_DAY +
tm.minute / MINUTES_PER_DAY +
tm.second / SECONDS_PER_DAY +
tm.microsecond / MUSECONDS_PER_DAY)
return tm_num
def dtime_dt(dt):
return math.trunc(dt)
def dtime_tm(dt):
return math.modf(dt)[0]
class RelativeVolumeByBar(bt.Indicator):
alias = ('RVBB',)
lines = ('rvbb',)
params = (
('prestart', datetime.time(8, 00)),
('start', datetime.time(9, 10)),
('end', datetime.time(17, 15)),
)
def _plotlabel(self):
plabels = []
for name, value in self.params._getitems():
plabels.append('%s: %s' % (name, value.strftime('%H:%M')))
return plabels
def __init__(self):
# Inform the platform about the minimum period needs
minbuffer = self._calcbuffer()
self.addminperiod(minbuffer)
# Structures/variable to keep synchronization
self.pvol = dict()
self.vcount = collections.defaultdict(int)
self.days = 0
self.dtlast = 0
# Keep the start/end times in numeric format for comparison
self.start = time2num(self.p.start)
self.end = time2num(self.p.end)
# Done after calc to ensure coop inheritance and composition work
super(RelativeVolumeByBar, self).__init__()
def _barisvalid(self, tm):
return self.start <= tm <= self.end
def _daycount(self):
dt = dtime_dt(self.data.datetime[0])
if dt > self.dtlast:
self.days += 1
self.dtlast = dt
def prenext(self):
self._daycount()
tm = dtime_tm(self.data.datetime[0])
if self._barisvalid(tm):
self.pvol[tm] = self.data.volume[0]
self.vcount[tm] += 1
def next(self):
self._daycount()
tm = dtime_tm(self.data.datetime[0])
if not self._barisvalid(tm):
return
# Record the "minute/second" of this day has been seen
self.vcount[tm] += 1
# Get the bar's volume
vol = self.data.volume[0]
# If number of days is right, we saw the same "minute/second" last day
if self.vcount[tm] == self.days:
self.lines.rvbb[0] = vol / self.pvol[tm]
# Synchronize the days and volume count for next cycle
self.vcount[tm] = self.days
# Record the volume for this bar for next cycle
self.pvol[tm] = vol
def _calcbuffer(self):
# Period calculation
minend = self.p.end.hour * 60 + self.p.end.minute
# minstart = session_start.hour * 60 + session_start.minute
# use prestart to account for market_data
minstart = self.p.prestart.hour * 60 + self.p.prestart.minute
minbuffer = minend - minstart
tframe = self.data._timeframe
tcomp = self.data._compression
if tframe == bt.TimeFrame.Seconds:
minbuffer = (minperiod * 60)
minbuffer = (minbuffer // tcomp) + tcomp
return minbuffer
通过脚本调用,可按如下方式使用:
$ ./relative-volume.py --help
usage: relative-volume.py [-h] [--data DATA] [--prestart PRESTART]
[--start START] [--end END] [--fromdate FROMDATE]
[--todate TODATE] [--writer] [--wrcsv] [--plot]
[--numfigs NUMFIGS]
MultiData Strategy
optional arguments:
-h, --help show this help message and exit
--data DATA, -d DATA data to add to the system
--prestart PRESTART Start time for the Session Filter
--start START Start time for the Session Filter
--end END, -te END End time for the Session Filter
--fromdate FROMDATE, -f FROMDATE
Starting date in YYYY-MM-DD format
--todate TODATE, -t TODATE
Starting date in YYYY-MM-DD format
--writer, -w Add a writer to cerebro
--wrcsv, -wc Enable CSV Output in the writer
--plot, -p Plot the read data
--numfigs NUMFIGS, -n NUMFIGS
Plot using numfigs figures
测试调用:
$ ./relative-volume.py --plot
产生此图表:
脚本的代码。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
# The above could be sent to an independent module
import backtrader as bt
import backtrader.feeds as btfeeds
from relvolbybar import RelativeVolumeByBar
def runstrategy():
args = parse_args()
# Create a cerebro
cerebro = bt.Cerebro()
# Get the dates from the args
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
# Create the 1st data
data = btfeeds.VChartCSVData(
dataname=args.data,
fromdate=fromdate,
todate=todate,
)
# Add the 1st data to cerebro
cerebro.adddata(data)
# Add an empty strategy
cerebro.addstrategy(bt.Strategy)
# Get the session times to pass them to the indicator
prestart = datetime.datetime.strptime(args.prestart, '%H:%M')
start = datetime.datetime.strptime(args.start, '%H:%M')
end = datetime.datetime.strptime(args.end, '%H:%M')
# Add the Relative volume indicator
cerebro.addindicator(RelativeVolumeByBar,
prestart=prestart, start=start, end=end)
# Add a writer with CSV
if args.writer:
cerebro.addwriter(bt.WriterFile, csv=args.wrcsv)
# And run it
cerebro.run(stdstats=False)
# Plot if requested
if args.plot:
cerebro.plot(numfigs=args.numfigs, volume=True)
def parse_args():
parser = argparse.ArgumentParser(description='MultiData Strategy')
parser.add_argument('--data', '-d',
default='../../datas/2006-01-02-volume-min-001.txt',
help='data to add to the system')
parser.add_argument('--prestart',
default='08:00',
help='Start time for the Session Filter')
parser.add_argument('--start',
default='09:15',
help='Start time for the Session Filter')
parser.add_argument('--end', '-te',
default='17:15',
help='End time for the Session Filter')
parser.add_argument('--fromdate', '-f',
default='2006-01-01',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', '-t',
default='2006-12-31',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--writer', '-w', action='store_true',
help='Add a writer to cerebro')
parser.add_argument('--wrcsv', '-wc', action='store_true',
help='Enable CSV Output in the writer')
parser.add_argument('--plot', '-p', action='store_true',
help='Plot the read data')
parser.add_argument('--numfigs', '-n', default=1,
help='Plot using numfigs figures')
return parser.parse_args()
if __name__ == '__main__':
runstrategy()