BackTrader 中文文档(十七)
欺骗开盘价
原文:
www.backtrader.com/blog/posts/2017-05-01-cheat-on-open/cheat-on-open/
发布1.9.44.116
版本增加了对Cheat-On-Open
的支持。这似乎是人们要求的功能,他们在某个条的结束后进行了计算,但期望与open
价格匹配
当开盘价格出现间隙(向上或向下,取决于buy
或sell
是否生效)且现金不足以进行全仓操作时,这种用例将失败。这迫使经纪人拒绝操作
尽管人们可以尝试通过积极的[1]
索引方法来预测未来,但这需要预加载数据,而这并不总是可用的
这种模式:
cerebro = bt.Cerebro(cheat_on_open=True)
这:
-
在系统中激活了一个额外的周期,该周期调用策略中的
next_open
、nextstart_open
和prenext_open
方法决定增加另一组方法是为了明确区分基于检查的价格不再可用且未来未知的常规方法和作弊模式中的操作
这也避免了对常规
next
方法的两次调用
在xxx_open
方法内部时,以下内容为真:
-
指标尚未重新计算,并保留了在等效的
xxx
常规方法中上一个周期中看到的值 -
经纪人尚未评估新周期的待处理订单,可以引入新订单,如果可能的话将进行评估
请注意:
-
Cerebro
还有一个broker_coo
(默认为True
)参数,告诉 cerebro,如果激活了cheat-on-open
,则尽可能在经纪人中也激活它模拟经纪人有一个名为:
coo
的参数和一个名为set_coo
的方法
尝试欺骗开盘价
下面的示例具有具有 2 种不同行为的策略:
-
如果cheat-on-open为True,则只会从
next_open
操作 -
如果cheat-on-open为False,则只会从
next
操作
在两种情况下,匹配价格必须是相同的
-
如果不作弊,则订单在前一天结束时发布,并将与下一个到来的价格匹配,即
open
价格 -
如果作弊,则订单在执行当天发布。因为订单是在经纪人评估订单之前发布的,所以它也将与下一个到来的价格匹配,即
open
价格这种第二种情况,允许计算全仓策略的确切赌注,因为可以直接访问当前的
open
价格
在两种情况下
- 当前的
open
和close
价格将从next
中打印出来
常规执行:
$ ./cheat-on-open.py --cerebro cheat_on_open=False
...
2005-04-07 next, open 3073.4 close 3090.72
2005-04-08 next, open 3092.07 close 3088.92
Strat Len 68 2005-04-08 Send Buy, fromopen False, close 3088.92
2005-04-11 Buy Executed at price 3088.47
2005-04-11 next, open 3088.47 close 3080.6
2005-04-12 next, open 3080.42 close 3065.18
...
订单:
-
在 2005-04-08 close之后发布
-
在 2005-04-11 以
3088.47
的open
价格执行
欺骗执行:
$ ./cheat-on-open.py --cerebro cheat_on_open=True
...
2005-04-07 next, open 3073.4 close 3090.72
2005-04-08 next, open 3092.07 close 3088.92
2005-04-11 Send Buy, fromopen True, close 3080.6
2005-04-11 Buy Executed at price 3088.47
2005-04-11 next, open 3088.47 close 3080.6
2005-04-12 next, open 3080.42 close 3065.18
...
订单:
-
在 2005 年 04 月 11 日在开盘之前发布
-
它在 2005 年 04 月 11 日以
3088.47
的open
价格执行。
而且在图表上看到的整体结果也是相同的。
结论
在开盘前作弊允许在开盘前发布订单,这样可以例如允许精确计算all-in情景的赌注。
样本用法
$ ./cheat-on-open.py --help
usage: cheat-on-open.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]
Cheat-On-Open Sample
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default:
../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )
样本来源
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class St(bt.Strategy):
params = dict(
periods=[10, 30],
matype=bt.ind.SMA,
)
def __init__(self):
self.cheating = self.cerebro.p.cheat_on_open
mas = [self.p.matype(period=x) for x in self.p.periods]
self.signal = bt.ind.CrossOver(*mas)
self.order = None
def notify_order(self, order):
if order.status != order.Completed:
return
self.order = None
print('{} {} Executed at price {}'.format(
bt.num2date(order.executed.dt).date(),
'Buy' * order.isbuy() or 'Sell', order.executed.price)
)
def operate(self, fromopen):
if self.order is not None:
return
if self.position:
if self.signal < 0:
self.order = self.close()
elif self.signal > 0:
print('{} Send Buy, fromopen {}, close {}'.format(
self.data.datetime.date(),
fromopen, self.data.close[0])
)
self.order = self.buy()
def next(self):
print('{} next, open {} close {}'.format(
self.data.datetime.date(),
self.data.open[0], self.data.close[0])
)
if self.cheating:
return
self.operate(fromopen=False)
def next_open(self):
if not self.cheating:
return
self.operate(fromopen=True)
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
kwargs = dict()
# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)
# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)
# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))
# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))
# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))
# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))
if args.plot: # Plot if requested to
cerebro.plot(**eval('dict(' + args.plot + ')'))
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Cheat-On-Open Sample'
)
)
parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
required=False, help='Data to read in')
# Defaults for dates
parser.add_argument('--fromdate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--todate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--cerebro', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--broker', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--sizer', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--strat', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--plot', required=False, default='',
nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()
交易日历
原文:
www.backtrader.com/blog/posts/2017-04-16-trading-calendar/tradingcalendar/
发行版1.9.42.116
增加了对交易日历的支持。在以下场景中重采样时,这是有用的:
-
现在,每日到每周的重采样可以与该周的最后一根柱子一起传递。
这是因为交易日历确定了下一个交易日,并且本周的最后一个交易日可以提前确定
-
当会话结束不是常规结束时(可以已经在数据源中指定)的子每日到每日重新采样
交易日历接口
有一个基类TradingCalendarBase
,它用作任何交易日历的基础。它定义了必须被重写的两个(2)方法:
class TradingCalendarBase(with_metaclass(MetaParams, object)):
def _nextday(self, day):
'''
Returns the next trading day (datetime/date instance) after ``day``
(datetime/date instance) and the isocalendar components
The return value is a tuple with 2 components: (nextday, (y, w, d))
where (y, w, d)
'''
raise NotImplementedError
def schedule(self, day):
'''
Returns a tuple with the opening and closing times (``datetime.time``)
for the given ``date`` (``datetime/date`` instance)
'''
raise NotImplementedError
实现
PandasMarketCalendar
此实现基于一个整洁的包,该包是 Quantopian 初始功能的衍生物。该软件包位于:pandas_market_calendars,并且可以轻松安装:
pip install pandas_market_calendars
实现具有以下接口:
class PandasMarketCalendar(TradingCalendarBase):
'''
Wrapper of ``pandas_market_calendars`` for a trading calendar. The package
``pandas_market_calendar`` must be installed
Params:
- ``calendar`` (default ``None``)
The param ``calendar`` accepts the following:
- string: the name of one of the calendars supported, for example
`NYSE`. The wrapper will attempt to get a calendar instance
- calendar instance: as returned by ``get_calendar('NYSE')``
- ``cachesize`` (default ``365``)
Number of days to cache in advance for lookup
See also:
- https://github.com/rsheftel/pandas_market_calendars
- http://pandas-market-calendars.readthedocs.io/
'''
params = (
('calendar', None), # A pandas_market_calendars instance or exch name
('cachesize', 365), # Number of days to cache in advance
)
交易日历
此实现允许使用自行收集的信息构建日历,方法是指定节假日、提前天数、非交易工作日和开放和关闭会话时间:
class TradingCalendar(TradingCalendarBase):
'''
Wrapper of ``pandas_market_calendars`` for a trading calendar. The package
``pandas_market_calendar`` must be installed
Params:
- ``open`` (default ``time.min``)
Regular start of the session
- ``close`` (default ``time.max``)
Regular end of the session
- ``holidays`` (default ``[]``)
List of non-trading days (``datetime.datetime`` instances)
- ``earlydays`` (default ``[]``)
List of tuples determining the date and opening/closing times of days
which do not conform to the regular trading hours where each tuple has
(``datetime.datetime``, ``datetime.time``, ``datetime.time`` )
- ``offdays`` (default ``ISOWEEKEND``)
A list of weekdays in ISO format (Monday: 1 -> Sunday: 7) in which the
market doesn't trade. This is usually Saturday and Sunday and hence the
default
'''
params = (
('open', time.min),
('close', _time_max),
('holidays', []), # list of non trading days (date)
('earlydays', []), # list of tuples (date, opentime, closetime)
('offdays', ISOWEEKEND), # list of non trading (isoweekdays)
)
使用模式
全局交易日历
通过Cerebro
,可以添加一个全局日历,该日历是所有数据源的默认日历,除非为数据源指定了一个:
def addcalendar(self, cal):
'''Adds a global trading calendar to the system. Individual data feeds
may have separate calendars which override the global one
``cal`` can be an instance of ``TradingCalendar`` a string or an
instance of ``pandas_market_calendars``. A string will be will be
instantiated as a ``PandasMarketCalendar`` (which needs the module
``pandas_market_calendar`` installed in the system.
If a subclass of `TradingCalendarBase` is passed (not an instance) it
will be instantiated
'''
每个数据源
通过在addcalendar
中描述的相同约定指定calendar
参数。
例如:
...
data = bt.feeds.YahooFinanceData(dataname='YHOO', calendar='NYSE', ...)
cerebro.adddata(data)
...
示例
每日到每周
让我们来看一下下面可以找到的代码示例的运行情况。在 2016 年,复活节星期五(2016-03-25)也是NYSE
的节假日。如果样本运行时没有交易日历,让我们看看那天附近会发生什么。
在这种情况下,正在从每日重新采样到每周(使用YHOO
和 2016 年的每日数据):
$ ./tcal.py
...
Strategy len 56 datetime 2016-03-23 Data0 len 56 datetime 2016-03-23 Data1 len 11 datetime 2016-03-18
Strategy len 57 datetime 2016-03-24 Data0 len 57 datetime 2016-03-24 Data1 len 11 datetime 2016-03-18
Strategy len 58 datetime 2016-03-28 Data0 len 58 datetime 2016-03-28 Data1 len 12 datetime 2016-03-24
...
在这个输出中,第 1^(st)日期是策略进行的会计。第 2^(nd)日期是每日的。
周末如预期地在 2016-03-24(星期四)结束,但没有交易日历,重采样代码无法知道,因此传递了日期为 2016-03-18(前一周)的重采样条。当交易转移到 2016-03-28(星期一)时,重新采样器检测到周转变并在同一天传递了日期为 2016-03-24 的重采样条。
使用PandasMarketCalendar
运行相同的操作,但用于NYSE
(并添加绘图)
$ ./tcal.py --plot --pandascal NYSE
...
Strategy len 56 datetime 2016-03-23 Data0 len 56 datetime 2016-03-23 Data1 len 11 datetime 2016-03-18
Strategy len 57 datetime 2016-03-24 Data0 len 57 datetime 2016-03-24 Data1 len 12 datetime 2016-03-24
Strategy len 58 datetime 2016-03-28 Data0 len 58 datetime 2016-03-28 Data1 len 12 datetime 2016-03-24
...
有一个变化!由于日历,重新采样器知道在 2016-03-24 周结束,并在同一天传递了相应的 2016-03-24 周重采样条。
以及绘图。
由于信息可能不一定对每个市场都可用,因此可以制定日历。对于NYSE
和2016
,它看起来像是:
class NYSE_2016(bt.TradingCalendar):
params = dict(
holidays=[
datetime.date(2016, 1, 1),
datetime.date(2016, 1, 18),
datetime.date(2016, 2, 15),
datetime.date(2016, 3, 25),
datetime.date(2016, 5, 30),
datetime.date(2016, 7, 4),
datetime.date(2016, 9, 5),
datetime.date(2016, 11, 24),
datetime.date(2016, 12, 26),
]
)
复活节星期五(2016-03-25)被列为假期之一。现在运行示例:
$ ./tcal.py --plot --owncal
...
Strategy len 56 datetime 2016-03-23 Data0 len 56 datetime 2016-03-23 Data1 len 11 datetime 2016-03-18
Strategy len 57 datetime 2016-03-24 Data0 len 57 datetime 2016-03-24 Data1 len 12 datetime 2016-03-24
Strategy len 58 datetime 2016-03-28 Data0 len 58 datetime 2016-03-28 Data1 len 12 datetime 2016-03-24
...
并且相同的结果已通过精心制作的日历定义获得。
分钟转换为每日
使用一些私有的日内数据和知道市场在 2016-11-25 提前收盘的知识(感恩节后的第二天,市场在 US/Eastern
时区在 13:00 关闭),进行另一次测试运行,这次是第二个样本。
注意
源数据直接来自显示的数据,并且处于 CET
时区,即使所涉及的资产 YHOO
在美国交易。 代码中使用 tzinput='CET'
和 tz='US/Eastern'
的数据源来让平台适当地转换输入并显示输出
首先没有交易日历
$ ./tcal-intra.py
...
Strategy len 6838 datetime 2016-11-25 18:00:00 Data0 len 6838 datetime 2016-11-25 13:00:00 Data1 len 21 datetime 2016-11-23 16:00:00
Strategy len 6839 datetime 2016-11-25 18:01:00 Data0 len 6839 datetime 2016-11-25 13:01:00 Data1 len 21 datetime 20 16-11-23 16:00:00
Strategy len 6840 datetime 2016-11-28 14:31:00 Data0 len 6840 datetime 2016-11-28 09:31:00 Data1 len 22 datetime 2016-11-25 16:00:00
Strategy len 6841 datetime 2016-11-28 14:32:00 Data0 len 6841 datetime 2016-11-28 09:32:00 Data1 len 22 datetime 2016-11-25 16:00:00
...
如预期的那样,当天在 13:00
提前关闭,但是重新采样器不知道(正式会话在 16:00
结束),并且继续从前一天(2016-11-23)交付重新采样的每日柱,并且新的重新采样的每日柱首次在下一个交易日(2016-11-28)交付,日期为 2016-11-25。
注意
数据有一个额外的分钟柱 13:01
,这可能是由于市场闭市后的竞价过程提供了最后一个价格。
我们可以向流中添加过滤器,以过滤掉在会话时间之外的柱子(过滤器将从交易日历中找到)
但这不是这个示例的重点。
使用 PandasMarketCalendar
实例进行相同的运行:
$ ./tcal-intra.py --pandascal NYSE
...
Strategy len 6838 datetime 2016-11-25 18:00:00 Data0 len 6838 datetime 2016-11-25 13:00:00 Data1 len 15 datetime 2016-11-25 13:00:00
Strategy len 6839 datetime 2016-11-25 18:01:00 Data0 len 6839 datetime 2016-11-25 13:01:00 Data1 len 15 datetime 2016-11-25 13:00:00
Strategy len 6840 datetime 2016-11-28 14:31:00 Data0 len 6840 datetime 2016-11-28 09:31:00 Data1 len 15 datetime 2016-11-25 13:00:00
Strategy len 6841 datetime 2016-11-28 14:32:00 Data0 len 6841 datetime 2016-11-28 09:32:00 Data1 len 15 datetime 2016-11-25 13:00:00
...
现在,当日内 1 分钟的数据源命中 2016-11-25 13:00 时(让我们忽略 13:01 的柱子),每日柱子就被交付,因为交易日历告诉重新采样代码这一天已经结束。
让我们添加一个精心制作的定义。与以前相同,但通过一些 earlydays
扩展它
class NYSE_2016(bt.TradingCalendar):
params = dict(
holidays=[
datetime.date(2016, 1, 1),
datetime.date(2016, 1, 18),
datetime.date(2016, 2, 15),
datetime.date(2016, 3, 25),
datetime.date(2016, 5, 30),
datetime.date(2016, 7, 4),
datetime.date(2016, 9, 5),
datetime.date(2016, 11, 24),
datetime.date(2016, 12, 26),
],
earlydays=[
(datetime.date(2016, 11, 25),
datetime.time(9, 30), datetime.time(13, 1))
],
open=datetime.time(9, 30),
close=datetime.time(16, 0),
)
运行:
$ ./tcal-intra.py --owncal
...
Strategy len 6838 datetime 2016-11-25 18:00:00 Data0 len 6838 datetime 2016-11-25 13:00:00 Data1 len 15 datetime 2016-11-23 16:00:00
Strategy len 6839 datetime 2016-11-25 18:01:00 Data0 len 6839 datetime 2016-11-25 13:01:00 Data1 len 16 datetime 2016-11-25 13:01:00
Strategy len 6840 datetime 2016-11-28 14:31:00 Data0 len 6840 datetime 2016-11-28 09:31:00 Data1 len 16 datetime 2016-11-25 13:01:00
Strategy len 6841 datetime 2016-11-28 14:32:00 Data0 len 6841 datetime 2016-11-28 09:32:00 Data1 len 16 datetime 2016-11-25 13:01:00
...
热心的读者会注意到,精心制作的定义包含了将 13:01
(用 datetime.time(13, 1)
定义)定义为我们 2016-11-25 的短日的会话结束。这仅仅是为了展示精心制作的 TradingCalendar
如何帮助调整事物。
现在,2016-11-25 的每日重新采样的柱子与 13:01 的 1 分钟柱子一起交付。
策略的额外奖金
第一个 datetime
,属于策略的那个时间,总是在另一个时区,实际上是 UTC
。也随着此版本 1.9.42.116
这可以被同步。下面的参数已添加到 Cerebro
中(在实例化期间或使用 cerebro.run
时使用)
- ``tz`` (default: ``None``)
Adds a global timezone for strategies. The argument ``tz`` can be
- ``None``: in this case the datetime displayed by strategies will be
in UTC, which has been always the standard behavior
- ``pytz`` instance. It will be used as such to convert UTC times to
the chosen timezone
- ``string``. Instantiating a ``pytz`` instance will be attempted.
- ``integer``. Use, for the strategy, the same timezone as the
corresponding ``data`` in the ``self.datas`` iterable (``0`` would
use the timezone from ``data0``)
它也支持 cerebro.addtz
方法:
def addtz(self, tz):
'''
This can also be done with the parameter ``tz``
Adds a global timezone for strategies. The argument ``tz`` can be
- ``None``: in this case the datetime displayed by strategies will be
in UTC, which has been always the standard behavior
- ``pytz`` instance. It will be used as such to convert UTC times to
the chosen timezone
- ``string``. Instantiating a ``pytz`` instance will be attempted.
- ``integer``. Use, for the strategy, the same timezone as the
corresponding ``data`` in the ``self.datas`` iterable (``0`` would
use the timezone from ``data0``)
'''
重复上次的日内示例运行,并使用 0
作为 tz
(与 data0
的时区同步),以下是关注相同日期和时间的输出:
$ ./tcal-intra.py --owncal --cerebro tz=0
...
Strategy len 6838 datetime 2016-11-25 13:00:00 Data0 len 6838 datetime 2016-11-25 13:00:00 Data1 len 15 datetime 2016-11-23 16:00:00
Strategy len 6839 datetime 2016-11-25 13:01:00 Data0 len 6839 datetime 2016-11-25 13:01:00 Data1 len 16 datetime 2016-11-25 13:01:00
Strategy len 6840 datetime 2016-11-28 09:31:00 Data0 len 6840 datetime 2016-11-28 09:31:00 Data1 len 16 datetime 2016-11-25 13:01:00
Strategy len 6841 datetime 2016-11-28 09:32:00 Data0 len 6841 datetime 2016-11-28 09:32:00 Data1 len 16 datetime 2016-11-25 13:01:00
...
时间戳现在是时区对齐的。
示例用法(tcal.py)
$ ./tcal.py --help
usage: tcal.py [-h] [--data0 DATA0] [--offline] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]
[--pandascal PANDASCAL | --owncal]
[--timeframe {Weeks,Months,Years}]
Trading Calendar Sample
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default: YHOO)
--offline Read from disk with same name as ticker (default:
False)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default:
2016-01-01)
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default:
2016-12-31)
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )
--pandascal PANDASCAL
Name of trading calendar to use (default: )
--owncal Apply custom NYSE 2016 calendar (default: False)
--timeframe {Weeks,Months,Years}
Timeframe to resample to (default: Weeks)
示例用法(tcal-intra.py)
$ ./tcal-intra.py --help
usage: tcal-intra.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]
[--pandascal PANDASCAL | --owncal] [--timeframe {Days}]
Trading Calendar Sample
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default: yhoo-2016-11.csv)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default:
2016-01-01)
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default:
2016-12-31)
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )
--pandascal PANDASCAL
Name of trading calendar to use (default: )
--owncal Apply custom NYSE 2016 calendar (default: False)
--timeframe {Days} Timeframe to resample to (default: Days)
示例代码(tcal.py)
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class NYSE_2016(bt.TradingCalendar):
params = dict(
holidays=[
datetime.date(2016, 1, 1),
datetime.date(2016, 1, 18),
datetime.date(2016, 2, 15),
datetime.date(2016, 3, 25),
datetime.date(2016, 5, 30),
datetime.date(2016, 7, 4),
datetime.date(2016, 9, 5),
datetime.date(2016, 11, 24),
datetime.date(2016, 12, 26),
]
)
class St(bt.Strategy):
params = dict(
)
def __init__(self):
pass
def start(self):
self.t0 = datetime.datetime.utcnow()
def stop(self):
t1 = datetime.datetime.utcnow()
print('Duration:', t1 - self.t0)
def prenext(self):
self.next()
def next(self):
print('Strategy len {} datetime {}'.format(
len(self), self.datetime.date()), end=' ')
print('Data0 len {} datetime {}'.format(
len(self.data0), self.data0.datetime.date()), end=' ')
if len(self.data1):
print('Data1 len {} datetime {}'.format(
len(self.data1), self.data1.datetime.date()))
else:
print()
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
kwargs = dict()
# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)
YahooData = bt.feeds.YahooFinanceData
if args.offline:
YahooData = bt.feeds.YahooFinanceCSVData # change to read file
# Data feed
data0 = YahooData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)
d1 = cerebro.resampledata(data0,
timeframe=getattr(bt.TimeFrame, args.timeframe))
d1.plotinfo.plotmaster = data0
d1.plotinfo.sameaxis = True
if args.pandascal:
cerebro.addcalendar(args.pandascal)
elif args.owncal:
cerebro.addcalendar(NYSE_2016)
# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))
# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))
# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))
# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))
if args.plot: # Plot if requested to
cerebro.plot(**eval('dict(' + args.plot + ')'))
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Trading Calendar Sample'
)
)
parser.add_argument('--data0', default='YHOO',
required=False, help='Data to read in')
parser.add_argument('--offline', required=False, action='store_true',
help='Read from disk with same name as ticker')
# Defaults for dates
parser.add_argument('--fromdate', required=False, default='2016-01-01',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--todate', required=False, default='2016-12-31',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--cerebro', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--broker', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--sizer', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--strat', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--plot', required=False, default='',
nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')
pgroup = parser.add_mutually_exclusive_group(required=False)
pgroup.add_argument('--pandascal', required=False, action='store',
default='', help='Name of trading calendar to use')
pgroup.add_argument('--owncal', required=False, action='store_true',
help='Apply custom NYSE 2016 calendar')
parser.add_argument('--timeframe', required=False, action='store',
default='Weeks', choices=['Weeks', 'Months', 'Years'],
help='Timeframe to resample to')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()
示例代码(tcal-intra.py)
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class NYSE_2016(bt.TradingCalendar):
params = dict(
holidays=[
datetime.date(2016, 1, 1),
datetime.date(2016, 1, 18),
datetime.date(2016, 2, 15),
datetime.date(2016, 3, 25),
datetime.date(2016, 5, 30),
datetime.date(2016, 7, 4),
datetime.date(2016, 9, 5),
datetime.date(2016, 11, 24),
datetime.date(2016, 12, 26),
],
earlydays=[
(datetime.date(2016, 11, 25),
datetime.time(9, 30), datetime.time(13, 1))
],
open=datetime.time(9, 30),
close=datetime.time(16, 0),
)
class St(bt.Strategy):
params = dict(
)
def __init__(self):
pass
def prenext(self):
self.next()
def next(self):
print('Strategy len {} datetime {}'.format(
len(self), self.datetime.datetime()), end=' ')
print('Data0 len {} datetime {}'.format(
len(self.data0), self.data0.datetime.datetime()), end=' ')
if len(self.data1):
print('Data1 len {} datetime {}'.format(
len(self.data1), self.data1.datetime.datetime()))
else:
print()
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
# kwargs = dict(tz='US/Eastern')
# import pytz
# tz = tzinput = pytz.timezone('Europe/Berlin')
tzinput = 'Europe/Berlin'
# tz = tzinput
tz = 'US/Eastern'
kwargs = dict(tzinput=tzinput, tz=tz)
# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)
# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)
d1 = cerebro.resampledata(data0,
timeframe=getattr(bt.TimeFrame, args.timeframe))
# d1.plotinfo.plotmaster = data0
# d1.plotinfo.sameaxis = False
if args.pandascal:
cerebro.addcalendar(args.pandascal)
elif args.owncal:
cerebro.addcalendar(NYSE_2016()) # or NYSE_2016() to pass an instance
# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))
# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))
# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))
# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))
if args.plot: # Plot if requested to
cerebro.plot(**eval('dict(' + args.plot + ')'))
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Trading Calendar Sample'
)
)
parser.add_argument('--data0', default='yhoo-2016-11.csv',
required=False, help='Data to read in')
# Defaults for dates
parser.add_argument('--fromdate', required=False, default='2016-01-01',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--todate', required=False, default='2016-12-31',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--cerebro', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--broker', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--sizer', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--strat', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--plot', required=False, default='',
nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')
pgroup = parser.add_mutually_exclusive_group(required=False)
pgroup.add_argument('--pandascal', required=False, action='store',
default='', help='Name of trading calendar to use')
pgroup.add_argument('--owncal', required=False, action='store_true',
help='Apply custom NYSE 2016 calendar')
parser.add_argument('--timeframe', required=False, action='store',
default='Days', choices=['Days'],
help='Timeframe to resample to')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()
多个示例
原文:
www.backtrader.com/blog/posts/2017-04-09-multi-example/multi-example/
一些社区中的主题似乎是如何跟踪订单,特别是当有多个数据源在运行时,以及包括多个订单共同工作的情况,如bracket orders的情况
下面的脚本试图作为示例,允许用户:
-
使用 3 个数据源
-
选择使用
- 当
usebracket=False
时,只包含类型为Market
的单个buy订单
或
- 设置bracket订单(
usebracket=True
在这种情况下,可以指定如何创建bracket set
-
当
rawbracket=True
时,发出 3 个订单(1 个父级+2 个子级) -
当
rawbracket=False
时调用buy_bracket
主要bracket订单在
valid
天后具有到期时间(默认为10
) - 当
-
当
weekday
与为每个数据源定义的enter
值匹配时,将进入(或至少尝试进入)一个仓位,默认为[1, 3, 4]
-
将退出一个开放的仓位
- 在
holding
期后正常close
。期间为每个数据源分别定义,参数hold
默认为[7, 10, 15]
如果存在bracket订单的
stop
方被触发,则会触发bracket订单的取消(这将自动取消另一方)- 或者如果使用brackets,则在执行
stop
(限制损失)或limit
(获利)方之一时(系统将自动取消另一方)
- 在
-
订单保留在
-
dict
中,使用data
作为键 -
包含每个
data
条目的订单的list
,这些订单对每个data
都是打开的
一旦订单已发出,管理就在
notify_order
中完成 -
此外:
-
使用
TestSizer
,它将为buy
和sell
订单返回不同的值作为测试因为只有long操作被启动,所以只返回
buy
大小,并且输出日志中不包含sell
大小的痕迹。
创建丰富的操作、持仓和订单管理日志,以帮助理解发生了什么。
让我们看一个带有usebracket=True
和rawbracket=True
默认值的示例执行(为了简化图表,将删除音量):
$ ./mult-values.py --plot volume=False
2001-01-02 d0 Position 0
2001-01-02 Data d0 OType buy Sizing to 1
2001-01-02 d0 Main 1 Stp 2 Lmt 3
2001-01-02 d1 Position 0
2001-01-02 d2 Position 0
2001-01-03 d0 Order 1 Status Accepted
2001-01-03 d0 Order 2 Status Accepted
2001-01-03 d0 Order 3 Status Accepted
2001-01-03 d0 Order 1 Status Completed
-- No longer alive main Ref
2001-01-03 d0 Position 1
2001-01-03 d1 Position 0
2001-01-03 d2 Position 0
2001-01-04 d0 Order 3 Status Completed
-- No longer alive limit Ref
2001-01-04 d0 Order 2 Status Canceled
-- No longer alive stop Ref
...
...
...
2006-12-27 d0 Order 2036 Status Accepted
2006-12-27 d0 Order 2037 Status Accepted
2006-12-27 d0 Order 2038 Status Accepted
2006-12-27 d0 Position 0
2006-12-27 d1 Position 0
2006-12-27 d2 Position 0
2006-12-28 d0 Position 0
2006-12-28 d1 Position 0
2006-12-28 d2 Position 0
2006-12-29 d0 Position 0
2006-12-29 d1 Position 0
2006-12-29 d2 Position 0
第二个执行设置rawbracket=False
$ ./mult-values.py --plot volume=False --strat rawbracket=False
这将输出完全相同的结果,这次使用了buy_bracket
。
最后禁用括号使用:
./mult-values.py --strat usebracket=False --plot volume=False
结论
这应该作为订单管理的良好示例,具有多个数据源和订单集。
示例用法
$ ./mult-values.py --help
usage: mult-values.py [-h] [--data0 DATA0] [--data1 DATA1] [--data2 DATA2]
[--fromdate FROMDATE] [--todate TODATE]
[--cerebro kwargs] [--broker kwargs] [--sizer kwargs]
[--strat kwargs] [--plot [kwargs]]
Multiple Values and Brackets
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data0 to read in (default:
../../datas/nvda-1999-2014.txt)
--data1 DATA1 Data1 to read in (default:
../../datas/yhoo-1996-2014.txt)
--data2 DATA2 Data1 to read in (default:
../../datas/orcl-1995-2014.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default:
2001-01-01)
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default:
2007-01-01)
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )
示例代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class TestSizer(bt.Sizer):
params = dict(stake=1)
def _getsizing(self, comminfo, cash, data, isbuy):
dt, i = self.strategy.datetime.date(), data._id
s = self.p.stake * (1 + (not isbuy))
print('{} Data {} OType {} Sizing to {}'.format(
dt, data._name, ('buy' * isbuy) or 'sell', s))
return s
class St(bt.Strategy):
params = dict(
enter=[1, 3, 4], # data ids are 1 based
hold=[7, 10, 15], # data ids are 1 based
usebracket=True,
rawbracket=True,
pentry=0.015,
plimits=0.03,
valid=10,
)
def notify_order(self, order):
if order.status == order.Submitted:
return
dt, dn = self.datetime.date(), order.data._name
print('{} {} Order {} Status {}'.format(
dt, dn, order.ref, order.getstatusname())
)
whichord = ['main', 'stop', 'limit', 'close']
if not order.alive(): # not alive - nullify
dorders = self.o[order.data]
idx = dorders.index(order)
dorders[idx] = None
print('-- No longer alive {} Ref'.format(whichord[idx]))
if all(x is None for x in dorders):
dorders[:] = [] # empty list - New orders allowed
def __init__(self):
self.o = dict() # orders per data (main, stop, limit, manual-close)
self.holding = dict() # holding periods per data
def next(self):
for i, d in enumerate(self.datas):
dt, dn = self.datetime.date(), d._name
pos = self.getposition(d).size
print('{} {} Position {}'.format(dt, dn, pos))
if not pos and not self.o.get(d, None): # no market / no orders
if dt.weekday() == self.p.enter[i]:
if not self.p.usebracket:
self.o[d] = [self.buy(data=d)]
print('{} {} Buy {}'.format(dt, dn, self.o[d][0].ref))
else:
p = d.close[0] * (1.0 - self.p.pentry)
pstp = p * (1.0 - self.p.plimits)
plmt = p * (1.0 + self.p.plimits)
valid = datetime.timedelta(self.p.valid)
if self.p.rawbracket:
o1 = self.buy(data=d, exectype=bt.Order.Limit,
price=p, valid=valid, transmit=False)
o2 = self.sell(data=d, exectype=bt.Order.Stop,
price=pstp, size=o1.size,
transmit=False, parent=o1)
o3 = self.sell(data=d, exectype=bt.Order.Limit,
price=plmt, size=o1.size,
transmit=True, parent=o1)
self.o[d] = [o1, o2, o3]
else:
self.o[d] = self.buy_bracket(
data=d, price=p, stopprice=pstp,
limitprice=plmt, oargs=dict(valid=valid))
print('{} {} Main {} Stp {} Lmt {}'.format(
dt, dn, *(x.ref for x in self.o[d])))
self.holding[d] = 0
elif pos: # exiting can also happen after a number of days
self.holding[d] += 1
if self.holding[d] >= self.p.hold[i]:
o = self.close(data=d)
self.o[d].append(o) # manual order to list of orders
print('{} {} Manual Close {}'.format(dt, dn, o.ref))
if self.p.usebracket:
self.cancel(self.o[d][1]) # cancel stop side
print('{} {} Cancel {}'.format(dt, dn, self.o[d][1]))
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
kwargs = dict()
# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)
# Data feed
data0 = bt.feeds.YahooFinanceCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0, name='d0')
data1 = bt.feeds.YahooFinanceCSVData(dataname=args.data1, **kwargs)
data1.plotinfo.plotmaster = data0
cerebro.adddata(data1, name='d1')
data2 = bt.feeds.YahooFinanceCSVData(dataname=args.data2, **kwargs)
data2.plotinfo.plotmaster = data0
cerebro.adddata(data2, name='d2')
# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))
cerebro.broker.setcommission(commission=0.001)
# Sizer
# cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))
cerebro.addsizer(TestSizer, **eval('dict(' + args.sizer + ')'))
# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))
# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))
if args.plot: # Plot if requested to
cerebro.plot(**eval('dict(' + args.plot + ')'))
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Multiple Values and Brackets'
)
)
parser.add_argument('--data0', default='../../datas/nvda-1999-2014.txt',
required=False, help='Data0 to read in')
parser.add_argument('--data1', default='../../datas/yhoo-1996-2014.txt',
required=False, help='Data1 to read in')
parser.add_argument('--data2', default='../../datas/orcl-1995-2014.txt',
required=False, help='Data1 to read in')
# Defaults for dates
parser.add_argument('--fromdate', required=False, default='2001-01-01',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--todate', required=False, default='2007-01-01',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--cerebro', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--broker', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--sizer', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--strat', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--plot', required=False, default='',
nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()
括号订单
原文:
www.backtrader.com/blog/posts/2017-04-01-bracket/bracket/
发行 1.9.37.116
版本添加了 bracket
订单,提供了由回测经纪人支持的非常广泛的订单范围(Market
,Limit
,Close
,Stop
,StopLimit
,StopTrail
,StopTrailLimit
,OCO
)
注意
这是为了 回测 和 交互经纪人 商店而实现的
bracket
订单不是单个订单,而实际上是由 3 个订单组成的。让我们考虑长边
-
主边
buy
订单,通常设置为Limit
或StopLimit
订单 -
低边
sell
订单,通常设置为Stop
订单以限制损失 -
高边
sell
订单,通常设置为Limit
订单以获利
对于短边,相应的 sell
和 2 x buy
订单。
低/高边订单实际上确实围绕主边订单创建了一个括号。
为了加入一些逻辑,以下规则适用:
-
为了避免它们中的任何一个被独立触发,三个订单一起提交
-
低/高边订单标记为主边的子订单
-
直到主边执行完毕,子订单都不活跃
-
主边取消则低边和高边都取消
-
主边的执行激活了低边和高边
-
一旦活跃
- 任何低/高边订单的执行或取消都会自动取消另一个
使用模式
创建括号订单集的两种可能性
-
单次发行 3 个订单
-
手动发行 3 个订单
单次发行一个括号
backtrader 在 Strategy
中提供了两种控制 bracket 订单的新方法。
buy_bracket
和sell_bracket
注意
签名和信息在下方或在 Strategy
参考部分。
通过一条语句完整发行 3 个订单集。例如:
brackets = self.buy_bracket(limitprice=14.00, price=13.50, stopprice=13.00)
注意 stopprice
和 limitprice
如何包裹主 price
这应该足够了。实际的目标 data
将是 data0
,而 size
将由默认大小器自动确定。当然,可以指定许多其他参数以对执行进行精细控制。
返回值是:
- 包含 3 个订单的列表,顺序为
[main, stop, limit]
因为当发出 sell_bracket
订单时,低边和高边将被转向,参数按照惯例命名为 stop
和 limit
-
stop
旨在停止损失(在长操作中是低边,在短操作中是高边) -
limit
旨在获利(在长操作中是高边,在短操作中是低边)
手动发行一个括号
这涉及生成 3 个订单并调整 transmit
和 parent
参数。规则如下:
-
主边订单必须首先创建,且
transmit=False
-
低/高边订单必须具有
parent=main_side_order
-
要创建的第一个低/高边订单必须具有
transmit=False
-
要创建的最后一个订单(低端或高端)设置
transmit=True
一个实际示例,执行上述单个命令所做的事情:
mainside = self.buy(price=13.50, exectype=bt.Order.Limit, transmit=False)
lowside = self.sell(price=13.00, size=mainsize.size, exectype=bt.Order.Stop,
transmit=False, parent=mainside)
highside = self.sell(price=14.00, size=mainsize.size, exectype=bt.Order.Limit,
transmit=True, parent=mainside)
还有很多事情要做:
-
跟踪
mainside
的顺序以指示它是其他订单的父订单 -
控制
transmit
以确保只有最后一个订单触发联合传输 -
指定执行类型
-
为低端和高端指定
size
因为
size
必须相同。如果参数没有手动指定,并且最终用户引入了一个 sizer,那么 sizer 实际上可能为订单指示一个不同的值。这就是为什么在为mainside
订单设置后,必须手动添加到调用中的原因。
它的一个示例
运行下面的示例会产生以下输出(为简洁起见)
$ ./bracket.py --plot
2005-01-28: Oref 1 / Buy at 2941.11055
2005-01-28: Oref 2 / Sell Stop at 2881.99275
2005-01-28: Oref 3 / Sell Limit at 3000.22835
2005-01-31: Order ref: 1 / Type Buy / Status Submitted
2005-01-31: Order ref: 2 / Type Sell / Status Submitted
2005-01-31: Order ref: 3 / Type Sell / Status Submitted
2005-01-31: Order ref: 1 / Type Buy / Status Accepted
2005-01-31: Order ref: 2 / Type Sell / Status Accepted
2005-01-31: Order ref: 3 / Type Sell / Status Accepted
2005-02-01: Order ref: 1 / Type Buy / Status Expired
2005-02-01: Order ref: 2 / Type Sell / Status Canceled
2005-02-01: Order ref: 3 / Type Sell / Status Canceled
...
2005-08-11: Oref 16 / Buy at 3337.3892
2005-08-11: Oref 17 / Sell Stop at 3270.306
2005-08-11: Oref 18 / Sell Limit at 3404.4724
2005-08-12: Order ref: 16 / Type Buy / Status Submitted
2005-08-12: Order ref: 17 / Type Sell / Status Submitted
2005-08-12: Order ref: 18 / Type Sell / Status Submitted
2005-08-12: Order ref: 16 / Type Buy / Status Accepted
2005-08-12: Order ref: 17 / Type Sell / Status Accepted
2005-08-12: Order ref: 18 / Type Sell / Status Accepted
2005-08-12: Order ref: 16 / Type Buy / Status Completed
2005-08-18: Order ref: 17 / Type Sell / Status Completed
2005-08-18: Order ref: 18 / Type Sell / Status Canceled
...
2005-09-26: Oref 22 / Buy at 3383.92535
2005-09-26: Oref 23 / Sell Stop at 3315.90675
2005-09-26: Oref 24 / Sell Limit at 3451.94395
2005-09-27: Order ref: 22 / Type Buy / Status Submitted
2005-09-27: Order ref: 23 / Type Sell / Status Submitted
2005-09-27: Order ref: 24 / Type Sell / Status Submitted
2005-09-27: Order ref: 22 / Type Buy / Status Accepted
2005-09-27: Order ref: 23 / Type Sell / Status Accepted
2005-09-27: Order ref: 24 / Type Sell / Status Accepted
2005-09-27: Order ref: 22 / Type Buy / Status Completed
2005-10-04: Order ref: 24 / Type Sell / Status Completed
2005-10-04: Order ref: 23 / Type Sell / Status Canceled
...
显示了 3 种不同的结果:
-
在第 1 种情况下,主订单已过期,这自动取消了其他两个订单
-
在第 2 种情况下,主订单已完成,低端(在买入情况下为止损)已执行,限制损失
-
在第 3 种情况下,主订单已完成,高端(限价)已执行
这可以注意到,因为Completed的 id 分别为
22
和24
,而high方订单是最后发出的,这意味着未执行的 low side 订单的 id 为 23。
视觉上
可以立即看到,亏损交易和盈利交易都围绕着相同的数值对齐,这就是 bracketing 的目的。控制双方。
运行示例手动发出 3 个订单,但可以告诉它使用buy_bracket
。让我们看看输出:
$ ./bracket.py --strat usebracket=True
产生相同的结果
一些参考
查看新的buy_bracket
和sell_bracket
方法
def buy_bracket(self, data=None, size=None, price=None, plimit=None,
exectype=bt.Order.Limit, valid=None, tradeid=0,
trailamount=None, trailpercent=None, oargs={},
stopprice=None, stopexec=bt.Order.Stop, stopargs={},
limitprice=None, limitexec=bt.Order.Limit, limitargs={},
**kwargs):
'''
Create a bracket order group (low side - buy order - high side). The
default behavior is as follows:
- Issue a **buy** order with execution ``Limit``
- Issue a *low side* bracket **sell** order with execution ``Stop``
- Issue a *high side* bracket **sell** order with execution
``Limit``.
See below for the different parameters
- ``data`` (default: ``None``)
For which data the order has to be created. If ``None`` then the
first data in the system, ``self.datas[0] or self.data0`` (aka
``self.data``) will be used
- ``size`` (default: ``None``)
Size to use (positive) of units of data to use for the order.
If ``None`` the ``sizer`` instance retrieved via ``getsizer`` will
be used to determine the size.
**Note**: The same size is applied to all 3 orders of the bracket
- ``price`` (default: ``None``)
Price to use (live brokers may place restrictions on the actual
format if it does not comply to minimum tick size requirements)
``None`` is valid for ``Market`` and ``Close`` orders (the market
determines the price)
For ``Limit``, ``Stop`` and ``StopLimit`` orders this value
determines the trigger point (in the case of ``Limit`` the trigger
is obviously at which price the order should be matched)
- ``plimit`` (default: ``None``)
Only applicable to ``StopLimit`` orders. This is the price at which
to set the implicit *Limit* order, once the *Stop* has been
triggered (for which ``price`` has been used)
- ``trailamount`` (default: ``None``)
If the order type is StopTrail or StopTrailLimit, this is an
absolute amount which determines the distance to the price (below
for a Sell order and above for a buy order) to keep the trailing
stop
- ``trailpercent`` (default: ``None``)
If the order type is StopTrail or StopTrailLimit, this is a
percentage amount which determines the distance to the price (below
for a Sell order and above for a buy order) to keep the trailing
stop (if ``trailamount`` is also specified it will be used)
- ``exectype`` (default: ``bt.Order.Limit``)
Possible values: (see the documentation for the method ``buy``
- ``valid`` (default: ``None``)
Possible values: (see the documentation for the method ``buy``
- ``tradeid`` (default: ``0``)
Possible values: (see the documentation for the method ``buy``
- ``oargs`` (default: ``{}``)
Specific keyword arguments (in a ``dict``) to pass to the main side
order. Arguments from the default ``**kwargs`` will be applied on
top of this.
- ``**kwargs``: additional broker implementations may support extra
parameters. ``backtrader`` will pass the *kwargs* down to the
created order objects
Possible values: (see the documentation for the method ``buy``
**Note**: this ``kwargs`` will be applied to the 3 orders of a
bracket. See below for specific keyword arguments for the low and
high side orders
- ``stopprice`` (default: ``None``)
Specific price for the *low side* stop order
- ``stopexec`` (default: ``bt.Order.Stop``)
Specific execution type for the *low side* order
- ``stopargs`` (default: ``{}``)
Specific keyword arguments (in a ``dict``) to pass to the low side
order. Arguments from the default ``**kwargs`` will be applied on
top of this.
- ``limitprice`` (default: ``None``)
Specific price for the *high side* stop order
- ``stopexec`` (default: ``bt.Order.Limit``)
Specific execution type for the *high side* order
- ``limitargs`` (default: ``{}``)
Specific keyword arguments (in a ``dict``) to pass to the high side
order. Arguments from the default ``**kwargs`` will be applied on
top of this.
Returns:
- A list containing the 3 orders [order, stop side, limit side]
'''
def sell_bracket(self, data=None,
size=None, price=None, plimit=None,
exectype=bt.Order.Limit, valid=None, tradeid=0,
trailamount=None, trailpercent=None,
oargs={},
stopprice=None, stopexec=bt.Order.Stop, stopargs={},
limitprice=None, limitexec=bt.Order.Limit, limitargs={},
**kwargs):
'''
Create a bracket order group (low side - buy order - high side). The
default behavior is as follows:
- Issue a **sell** order with execution ``Limit``
- Issue a *high side* bracket **buy** order with execution ``Stop``
- Issue a *low side* bracket **buy** order with execution ``Limit``.
See ``bracket_buy`` for the meaning of the parameters
Returns:
- A list containing the 3 orders [order, stop side, limit side]
'''
示例用法
$ ./bracket.py --help
usage: bracket.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE]
[--cerebro kwargs] [--broker kwargs] [--sizer kwargs]
[--strat kwargs] [--plot [kwargs]]
Sample Skeleton
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default:
../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )
示例代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class St(bt.Strategy):
params = dict(
ma=bt.ind.SMA,
p1=5,
p2=15,
limit=0.005,
limdays=3,
limdays2=1000,
hold=10,
usebracket=False, # use order_target_size
switchp1p2=False, # switch prices of order1 and order2
)
def notify_order(self, order):
print('{}: Order ref: {} / Type {} / Status {}'.format(
self.data.datetime.date(0),
order.ref, 'Buy' * order.isbuy() or 'Sell',
order.getstatusname()))
if order.status == order.Completed:
self.holdstart = len(self)
if not order.alive() and order.ref in self.orefs:
self.orefs.remove(order.ref)
def __init__(self):
ma1, ma2 = self.p.ma(period=self.p.p1), self.p.ma(period=self.p.p2)
self.cross = bt.ind.CrossOver(ma1, ma2)
self.orefs = list()
if self.p.usebracket:
print('-' * 5, 'Using buy_bracket')
def next(self):
if self.orefs:
return # pending orders do nothing
if not self.position:
if self.cross > 0.0: # crossing up
close = self.data.close[0]
p1 = close * (1.0 - self.p.limit)
p2 = p1 - 0.02 * close
p3 = p1 + 0.02 * close
valid1 = datetime.timedelta(self.p.limdays)
valid2 = valid3 = datetime.timedelta(self.p.limdays2)
if self.p.switchp1p2:
p1, p2 = p2, p1
valid1, valid2 = valid2, valid1
if not self.p.usebracket:
o1 = self.buy(exectype=bt.Order.Limit,
price=p1,
valid=valid1,
transmit=False)
print('{}: Oref {} / Buy at {}'.format(
self.datetime.date(), o1.ref, p1))
o2 = self.sell(exectype=bt.Order.Stop,
price=p2,
valid=valid2,
parent=o1,
transmit=False)
print('{}: Oref {} / Sell Stop at {}'.format(
self.datetime.date(), o2.ref, p2))
o3 = self.sell(exectype=bt.Order.Limit,
price=p3,
valid=valid3,
parent=o1,
transmit=True)
print('{}: Oref {} / Sell Limit at {}'.format(
self.datetime.date(), o3.ref, p3))
self.orefs = [o1.ref, o2.ref, o3.ref]
else:
os = self.buy_bracket(
price=p1, valid=valid1,
stopprice=p2, stopargs=dict(valid=valid2),
limitprice=p3, limitargs=dict(valid=valid3),)
self.orefs = [o.ref for o in os]
else: # in the market
if (len(self) - self.holdstart) >= self.p.hold:
pass # do nothing in this case
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
kwargs = dict()
# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)
# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)
# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))
# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))
# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))
# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))
if args.plot: # Plot if requested to
cerebro.plot(**eval('dict(' + args.plot + ')'))
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Sample Skeleton'
)
)
parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
required=False, help='Data to read in')
# Defaults for dates
parser.add_argument('--fromdate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--todate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--cerebro', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--broker', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--sizer', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--strat', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--plot', required=False, default='',
nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()
StopTrail(Limit)
原文:
www.backtrader.com/blog/posts/2017-03-22-stoptrail/stoptrail/
版本1.9.35.116
为回测工具库添加了StopTrail
和StopTrailLimit
订单执行类型。
注意
这只在回测中实现了,尚未实现实时经纪人的功能
注意
更新至版本1.9.36.116
。交互式经纪人支持StopTrail
、StopTrailLimit
和OCO
。
-
OCO
始终将一组中的第 1 个订单指定为参数oco
-
StopTrailLimit
:经纪人模拟和IB
经纪人具有相同的行为。指定:price
作为初始止损触发价格(还要指定trailamount
),然后plimi
作为初始限价。两者之间的差异将确定limitoffset
(限价价格与停止触发价格之间的距离)
使用模式完全集成到了策略实例的标准buy
、sell
和close
市场操作方法中。要注意:
-
指定所需的执行类型,如
exectype=bt.Order.StopTrail
-
以及尾随价格是固定距离还是基于百分比的距离计算
-
固定距离:
trailamount=10
-
基于百分比的距离:
trailpercent=0.02
(即:2%
)
-
如果通过发出buy
进入市场,则使用StopTrail
和trailamount
的sell
就是这样做的操作:
-
如果未指定
price
,则使用最新的close
价格 -
trailamount
从价格中减去以找到stop
(或触发)价格 -
broker的下一次迭代将检查触发价格是否已达到
-
如果是:订单将以
市价
执行方式执行 -
如果否,则
stop
价格会通过使用最新的close
价格重新计算,并减去trailamount
的距离。 -
如果新价格上涨,则会更新
-
如果新价格会下跌(或保持不变),则将其丢弃
-
也就是说:尾随止损价格会随着价格上涨而上涨,但如果价格开始下跌,价格将保持不变,以潜在地确保利润。
如果已经用sell
进入市场,那么只需使用StopTrail
发出buy
订单就可以做相反的操作,即:价格向下跟随。
一些使用模式
# For a StopTrail going downwards
# last price will be used as reference
self.buy(size=1, exectype=bt.Order.StopTrail, trailamount=0.25)
# or
self.buy(size=1, exectype=bt.Order.StopTrail, price=10.50, trailamount=0.25)
# For a StopTrail going upwards
# last price will be used as reference
self.sell(size=1, exectype=bt.Order.StopTrail, trailamount=0.25)
# or
self.sell(size=1, exectype=bt.Order.StopTrail, price=10.50, trailamount=0.25)
也可以指定trailpercent
而不是trailamount
,并且距离将以价格的百分比计算
# For a StopTrail going downwards with 2% distance
# last price will be used as reference
self.buy(size=1, exectype=bt.Order.StopTrail, trailpercent=0.02)
# or
self.buy(size=1, exectype=bt.Order.StopTrail, price=10.50, trailpercent=0.0.02)
# For a StopTrail going upwards with 2% difference
# last price will be used as reference
self.sell(size=1, exectype=bt.Order.StopTrail, trailpercent=0.02)
# or
self.sell(size=1, exectype=bt.Order.StopTrail, price=10.50, trailpercent=0.02)
对于StopTrailLimit
-
当触发尾随止损价格时,唯一的区别在于发生了什么。
-
在这种情况下,订单将作为
限价
订单执行(与StopLimit
订单具有相同的行为,但在这种情况下具有动态触发价格)注意
必须指定
plimit=x.x
来buy
或sell
,这将是限价。注意
限价价格不像停止/触发价格那样动态更改
例子永远都是值得一千言语的,因此通常的backtrader示例,
-
使用移动平均线交叉进入市场的方法
-
使用跟踪止损退出市场
执行与固定价格距离为50
点
$ ./trail.py --plot --strat trailamount=50.0
产生以下图表
以及以下输出:
**************************************************
2005-02-14,3075.76,3025.76,3025.76
----------
2005-02-15,3086.95,3036.95,3036.95
2005-02-16,3068.55,3036.95,3018.55
2005-02-17,3067.34,3036.95,3017.34
2005-02-18,3072.04,3036.95,3022.04
2005-02-21,3063.64,3036.95,3013.64
...
...
**************************************************
2005-05-19,3051.79,3001.79,3001.79
----------
2005-05-20,3050.45,3001.79,3000.45
2005-05-23,3070.98,3020.98,3020.98
2005-05-24,3066.55,3020.98,3016.55
2005-05-25,3059.84,3020.98,3009.84
2005-05-26,3086.08,3036.08,3036.08
2005-05-27,3084.0,3036.08,3034.0
2005-05-30,3096.54,3046.54,3046.54
2005-05-31,3076.75,3046.54,3026.75
2005-06-01,3125.88,3075.88,3075.88
2005-06-02,3131.03,3081.03,3081.03
2005-06-03,3114.27,3081.03,3064.27
2005-06-06,3099.2,3081.03,3049.2
2005-06-07,3134.82,3084.82,3084.82
2005-06-08,3125.59,3084.82,3075.59
2005-06-09,3122.93,3084.82,3072.93
2005-06-10,3143.85,3093.85,3093.85
2005-06-13,3159.83,3109.83,3109.83
2005-06-14,3162.86,3112.86,3112.86
2005-06-15,3147.55,3112.86,3097.55
2005-06-16,3160.09,3112.86,3110.09
2005-06-17,3178.48,3128.48,3128.48
2005-06-20,3162.14,3128.48,3112.14
2005-06-21,3179.62,3129.62,3129.62
2005-06-22,3182.08,3132.08,3132.08
2005-06-23,3190.8,3140.8,3140.8
2005-06-24,3161.0,3140.8,3111.0
...
...
...
**************************************************
2006-12-19,4100.48,4050.48,4050.48
----------
2006-12-20,4118.54,4068.54,4068.54
2006-12-21,4112.1,4068.54,4062.1
2006-12-22,4073.5,4068.54,4023.5
2006-12-27,4134.86,4084.86,4084.86
2006-12-28,4130.66,4084.86,4080.66
2006-12-29,4119.94,4084.86,4069.94
而不是等待通常的交叉下行模式,系统使用跟踪止损退出市场。让我们以第 1 次操作为例
-
进入多头时的收盘价格:
3075.76
-
系统计算的跟踪止损价格:
3025.76
(距离50
个单位) -
样本计算的跟踪止损价格:
3025.76
(每行显示的最后价格)
在第一次计算之后:
-
收盘价上涨至
3086.95
且止损价格调整为3036.95
-
以下收盘价格均不超过
3086.95
且触发价格不变
在其他 2 次操作中也可以看到相同的模式。
为了比较,使用固定距离为30
点的执行(只有图表)
$ ./trail.py --plot --strat trailamount=30.0
以及图表
接着最后一个执行,使用trailpercent=0.02
$ ./trail.py --plot --strat trailpercent=0.02
相应的图表。
示例用法
$ ./trail.py --help
usage: trail.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE]
[--cerebro kwargs] [--broker kwargs] [--sizer kwargs]
[--strat kwargs] [--plot [kwargs]]
StopTrail Sample
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default:
../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )
示例代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class St(bt.Strategy):
params = dict(
ma=bt.ind.SMA,
p1=10,
p2=30,
stoptype=bt.Order.StopTrail,
trailamount=0.0,
trailpercent=0.0,
)
def __init__(self):
ma1, ma2 = self.p.ma(period=self.p.p1), self.p.ma(period=self.p.p2)
self.crup = bt.ind.CrossUp(ma1, ma2)
self.order = None
def next(self):
if not self.position:
if self.crup:
o = self.buy()
self.order = None
print('*' * 50)
elif self.order is None:
self.order = self.sell(exectype=self.p.stoptype,
trailamount=self.p.trailamount,
trailpercent=self.p.trailpercent)
if self.p.trailamount:
tcheck = self.data.close - self.p.trailamount
else:
tcheck = self.data.close * (1.0 - self.p.trailpercent)
print(','.join(
map(str, [self.datetime.date(), self.data.close[0],
self.order.created.price, tcheck])
)
)
print('-' * 10)
else:
if self.p.trailamount:
tcheck = self.data.close - self.p.trailamount
else:
tcheck = self.data.close * (1.0 - self.p.trailpercent)
print(','.join(
map(str, [self.datetime.date(), self.data.close[0],
self.order.created.price, tcheck])
)
)
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
kwargs = dict()
# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)
# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)
# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))
# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))
# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))
# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))
if args.plot: # Plot if requested to
cerebro.plot(**eval('dict(' + args.plot + ')'))
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'StopTrail Sample'
)
)
parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
required=False, help='Data to read in')
# Defaults for dates
parser.add_argument('--fromdate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--todate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--cerebro', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--broker', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--sizer', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--strat', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--plot', required=False, default='',
nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2023-04-15 Python 人工智能:21~23
2023-04-15 Python 人工智能:16~20
2023-04-15 Python 人工智能:11~15
2023-04-15 Python 人工智能:6~10
2023-04-15 Python 人工智能:1~5
2023-04-15 TensorFlow 卷积神经网络实用指南:6~10
2023-04-15 TensorFlow 卷积神经网络实用指南:1~5