文獻和/或行業中缺乏標準公式不是問題,因為問題實際上可以總結為:
- 條形同步
工單 #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()