Sizers#
策略提供交易方法,即:buy、sell 和 close。让我们看看 buy 的签名:
def buy(self, data=None, size=None, price=None, plimit=None, exectype=None, valid=None, tradeid=0, **kwargs):注意,如果调用者没有指定 size,则 size 的默认值为 None。这就是 Sizers 发挥重要作用的地方:
size=None请求策略向其 Sizer 询问实际的头寸大小
这显然意味着策略有一个 Sizer:是的,确实如此!如果用户没有添加 Sizer,后台机制会为策略添加一个默认的 Sizer。添加到策略中的默认 Sizer 是 SizerFix。定义的初始行:
class SizerFix(SizerBase):
    params = (('stake', 1),)很容易猜到这个 Sizer 只是使用 1 个单位(无论是股票、合约等)买卖。
使用 Sizers#
从 Cerebro#
Sizers 可以通过 Cerebro 以两种不同的方法添加:
addsizer(sizercls, *args, **kwargs):添加一个 Sizer,将应用于添加到 cerebro 的任何策略。这就是所谓的默认 Sizer。例如:
cerebro = bt.Cerebro()
cerebro.addsizer(bt.sizers.SizerFix, stake=20)  # 默认策略的 Sizeraddsizer_byidx(idx, sizercls, *args, **kwargs):只将 Sizer 添加到idx引用的策略中。
这个 idx 可以作为 addstrategy 的返回值获得。例如:
cerebro = bt.Cerebro()
cerebro.addsizer(bt.sizers.SizerFix, stake=20)  # 默认策略的 Sizer
idx = cerebro.addstrategy(MyStrategy, myparam=myvalue)
cerebro.addsizer_byidx(idx, bt.sizers.SizerFix, stake=5)
cerebro.addstrategy(MyOtherStrategy)在这个例子中:
- 系统添加了一个默认的 Sizer。这适用于所有没有分配特定 Sizer 的策略。
 - 对于 
MyStrategy,在收集其插入idx后,添加了一个特定的 Sizer(更改stake参数)。 - 系统添加了第二个策略 
MyOtherStrategy。没有为其添加特定的 Sizer。 
这意味着:
MyStrategy最终会有一个内部特定的 Sizer。MyOtherStrategy将获得默认的 Sizer。
注意:默认并不意味着策略共享单个 Sizer 实例。每个策略都接收不同的默认 Sizer 实例。要共享单个实例,共享的 Sizer 应该是一个单例类。如何定义一个超出了 backtrader 的范围。
从策略#
Strategy 类提供了一个 API:setsizer 和 getsizer(以及一个属性 sizer)来管理 Sizer。签名:
def setsizer(self, sizer): 它接受一个已经实例化的 Sizer def getsizer(self): 返回当前的 Sizer 实例,sizer 是可以直接获取/设置的属性
在这种情况下,Sizer 可以例如:
- 作为参数传递给策略
 - 在 
__init__中使用属性sizer或setsizer设置,例如: 
class MyStrategy(bt.Strategy):
    params = (('sizer', None),)
    def __init__(self):
        if self.p.sizer is not None:
            self.sizer = self.p.sizer这将允许在与 cerebro 调用相同级别创建一个 Sizer,并将其作为参数传递给系统中的所有策略,从而有效地共享一个 Sizer。
Sizer 开发#
开发一个 Sizer 很容易:
- 从 
backtrader.Sizer子类化。 
这使您可以访问 self.strategy 和 self.broker,尽管在大多数情况下不需要。可以通过经纪人访问的内容:
- 数据的头寸,通过 
self.strategy.getposition(data) - 完整的投资组合价值,通过 
self.broker.getvalue() 
注意,这当然也可以通过 self.strategy.broker.getvalue() 来完成。
- 重写方法 
_getsizing(self, comminfo, cash, data, isbuy)。 
参数:
comminfo:包含数据佣金信息的CommissionInfo实例,并允许计算头寸价值、操作成本和操作佣金。cash:经纪人的当前可用现金。data:操作的目标。isbuy:对于买入操作为True,对于卖出操作为False。
此方法返回买入/卖出操作的期望大小。
返回的符号无关紧要,例如:如果操作是卖出操作(isbuy 为 False),方法可以返回 5 或 -5。卖出操作只会使用绝对值。
Sizer 已经去经纪人那里请求了给定数据的佣金信息、实际现金水平,并提供了操作目标数据的直接引用。
让我们定义 FixedSize Sizer:
import backtrader as bt
class FixedSize(bt.Sizer):
    params = (('stake', 1),)
    def _getsizing(self, comminfo, cash, data, isbuy):
        return self.params.stake这非常简单,因为 Sizer 不进行计算,参数只是存在。
但是该机制应该允许构建复杂的头寸系统来管理进入/退出市场时的头寸。
另一个例子:一个头寸反转器:
class FixedReverser(bt.FixedSize):
    def _getsizing(self, comminfo, cash, data, isbuy):
        position = self.broker.getposition(data)
        size = self.p.stake * (1 + (position.size != 0))
        return size这个 Sizer 继承了 FixedSize,覆盖了 _getsizing:
- 通过 
broker属性获取数据的头寸。 - 使用 
position.size决定是否加倍固定头寸。 - 返回计算的值。
 
这将解除策略决定是否反转头寸或开仓的负担,Sizer 负责控制,可以随时替换而不影响逻辑。
实用 Sizer 应用#
不考虑复杂的头寸算法,可以使用两种不同的 Sizers 将策略从仅做多转换为多空策略。只需在 cerebro 执行中更改 Sizer,策略的行为将发生变化。一个非常简单的收盘价交叉 SMA 算法:
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()注意策略并没有考虑当前头寸(通过查看 self.position)来决定是否实际执行买入或卖出操作。只有交叉信号被考虑。Sizer 将负责所有事务。
这个 Sizer 仅在卖出时返回非零头寸大小(如果已有头寸):
class LongOnly(bt.Sizer):
    params = (('stake', 1),)
    def _getsizing(self, comminfo, cash, data, isbuy):
      if isbuy:
          return self.p.stake
      # 卖出情况
      position = self.broker.getposition(data)
      if not position.size:
          return 0  # 如果没有持仓则不卖出
      return self.p.stake将所有内容放在一起(假设已经导入 backtrader 并向系统添加了数据):
cerebro.addstrategy(CloseSMA)
cerebro.addsizer(LongOnly)
cerebro.run()图表(来自源代码中的示例以测试这一点)。

多空版本只需将 Sizer 更改为上面显示的 FixedReverser:
cerebro.addstrategy(CloseSMA)
cerebro.addsizer(FixedReverser)
cerebro.run()输出图表。

注意差异:
- 交易次数翻倍。
 - 现金水平从未恢复到初始值,因为策略始终在市场中。
 
两种方法都负面,但这只是一个示例。
bt.Sizer 参考#
class backtrader.Sizer()这是 Sizers 的基类。任何 Sizer 都应继承此类并覆盖 _getsizing 方法。
成员属性:
strategy:由 Sizer 所在的策略设置。可以访问策略的整个 API,例如如果需要获取实际数据头寸:
position = self.strategy.getposition(data)broker:由 Sizer 所在的策略设置。可以访问一些
复杂 Sizers 可能需要的信息,例如投资组合价值等。
def _getsizing(comminfo, cash, data, isbuy)此方法必须由 Sizer 的子类覆盖以提供大小功能。
参数:
comminfo:包含数据佣金信息的CommissionInfo实例,并允许计算头寸价值、操作成本和操作佣金。cash:经纪人的当前可用现金。data:操作的目标。isbuy:对于买入操作为True,对于卖出操作为False。
此方法必须返回要执行的实际大小(一个整数)。如果返回 0,则不会执行任何操作。
返回值的绝对值将被使用。