再平衡与保守式Formula – backtrader中文教程
与保守Formula
TheConservative Formula方法是再平衡本文提出了:在保守公式在Python:制作简单量化投资
这是一个许多可能的再平衡的方法,而且是一个很容易把握。该方法的概述:
x
库存数据的Y
(1000 100)中的宇宙选择- 的选择标准are
- Low volatility
- 高净值赔付Yield
- High Momentum
- Rebalancing每month
With这一点让“走吧,并提出一个可能的实现方式backtrader
data
Even如果一个人有一个成功的策略,什么都不会被实际上,如果没有数据是赢得可用于战略。这意味着它必须考虑如何数据看起来像以及如何装入it.
A组CSV(“逗号分隔值”)文件被假设为是可用的,具有以下features
ohlcv
月data- With包含
v
包含Net Payout Yield后,一个额外的字段(npy
),具有一个ohlcvn
数据set.
格式的CSV数据将因此看起来像this
date, open, high, low, close, volume, npy 2001-12-31, 1.0, 1.0, 1.0, 1.0, 0.5, 3.0 2002-01-31, 2.0, 2.5, 1.1, 1.2, 3.0, 5.0 ...
I.e:每月一行。数据加载器引擎现在可以用于制备其的简单扩展通用内置CSV装载机交付使用backtrader将created.
class NetPayOutData(bt.feeds.GenericCSVData): lines = ("npy",) # add a line containing the net payout yield params = dict( npy=6, # npy field is in the 6th column (0 based index) dtformat="%Y-%m-%d", # fix date format a yyyy-mm-dd timeframe=bt.TimeFrame.Months, # fixed the timeframe openinterest=-1, # -1 indicates there is no openinterest field )
And是。通知已多么容易添加基础数据的点ohlcv
使用expresion数据stream.
- By
lines=("npy",)
。常见的其它领域(open
,
high
,…)已经部分GenericCSVData
- 通过指示与
params = dict(npy=6)
装载位置。该等领域都有一个预定义的position.
Thetimeframe也已经在参数更新,以反映每月在data.
Note
See文件的性质 – 数据供稿参考 -GenericCSVData对于实际的领域和装载位置(这都可以定制)
数据装载必须与文件名正确实例化,但说:“有些问题需要以后,当一个标准的样板呈现以下有一个完整的script.
The Strategy
让“S把逻辑放到一个标准的backtrader战略。为了使它作为通用和定制成为可能,同样的相同params
方法会使用,因为它是与data.
Before钻研策略之前使用,让“来考虑从该点之一快速summary
x
库存数据的’域选择Y
战略本身是不负责加入股票的宇宙,但它负责挑选的。其中一个可能是一种情况,其中仅50库存已经增加,仍然尽量选择100,如果x
和Y
是固定的编码。为了应付这种情况,下面会做:
- 有一个
selperc
用0.10
(即:10%
),价值参数指示装置的股票被从universe.This选择的量,如果1000存在时,只有100将被选择,并且如果宇宙包括50只股票中,只有5将是公式排名股票selected.
As,它看起来像这样:
(momentum * net payout) / volatility
这意味着,那些具有更高的动力,更高的支出和较低波动会具有更高的score.
Formomentum
的RateOfChange
指示器(又名ROC
)将被使用,这measures the ratio of change in prices over a period.
的net payout
已经是数据feed.
To计算`的volatility
,在StandardDeviation
的一部分n-periods
股票的收益率(n-periods
,因为事情将保留作为参数)将used.
With这一信息,该策略已经可以initialize用正确的参数和指标和计算的设置将在后面在每个月iteration.
First使用的声明和一些没有上述被加上了parameters
class St(bt.Strategy): params = dict( selcperc=0.10, # percentage of stocks to select from the universe rperiod=1, # period for the returns calculation, default 1 period vperiod=36, # lookback period for volatility - default 36 periods mperiod=12, # lookback period for momentum - default 12 periods reserve=0.05 # 5% reserve capital )
Notice,那就是参数reserve=0.05
(即5%),其用于计算每只股票比例分配,保持资本准备金的银行。虽然模拟人能想到的要使用的100%资本,一个可以打到的常见问题这样做,如价格差距,浮点精确度,并最终丢失了一些市场entries.
Before别的,就是创建了一个小型测井方法,这将使登录该组合是如何rebalanced.
def log(self, arg): print("{} {}".format(self.datetime.date(), arg))
At的__init__
方法,排名股票数量是开始计算和保留资本参数被应用以确定每在bank.
def __init__(self): # calculate 1st the amount of stocks that will be selected self.selnum = int(len(self.datas) * self.p.selcperc) # allocation perc per stock # reserve kept to make sure orders are not rejected due to # margin. Prices are calculated when known (close), but orders can only # be executed next day (opening price). Price can gap upwards self.perctarget = (1.0 - self.p.reserve) % self.selnum
And的股票比例终于初始化是在用的每只股票的计算指标的波动性和动力,然后再在每只股票应用排名公式calculation.
# returns, volatilities and momentums rs = [bt.ind.PctChange(d, period=self.p.rperiod) for d in self.datas] vs = [bt.ind.StdDev(ret, period=self.p.vperiod) for ret in rs] ms = [bt.ind.ROC(d, period=self.p.mperiod) for d in self.datas] # simple rank formula: (momentum * net payout) / volatility # the highest ranked: low vol, large momentum, large payout self.ranks = {d: d.npy * m / v for d, v, m in zip(self.datas, vs, ms)}
It“现在该迭代每个月,排名可在self.ranks
字典。该key/value双待排序的每个迭代,得到哪些项目必须去,哪些必须的一部分组合(保留或加入)
def next(self): # sort data and current rank ranks = sorted( self.ranks.items(), # get the (d, rank), pair key=lambda x: x[1][0], # use rank (elem 1) and current time "0" reverse=True, # highest ranked 1st ... please )
的迭代被以相反的顺序进行排序,因为排名式输送分数较高的排名最高的stocks.
Rebalancing现在due.
Rebalancing 1:获取排名靠前,并与Python弄虚作假的开放positions
# put top ranked in dict with data as key to test for presence rtop = dict(ranks[:self.selnum]) # For logging purposes of stocks leaving the portfolio rbot = dict(ranks[self.selnum:])
A位的个股正在发生这里,因为dict
是被使用。该理由是,如果排名靠前的个股都放在了list
运营==
将由内部使用Python,以检查用于与所述操作者存在in
。虽然不可能有可能为两股有在同一天同一个值。当使用dict
的散列值时,使用检查项目的存在作为keys.
Note的部分:为了记录目的rbot
还与创建的(ranked bottom)股票中不存在rtop
.
要在以后有离开投资组合,这些股票区别对待这根本就重新平衡和新排名靠前,的最新列表在投资组合中的股票是prepared.
# prepare quick lookup list of stocks currently holding a position posdata = [d for d, pos in self.getpositions().items() if pos]
Rebalancing 2:卖的不再顶部ranked
Just像在现实世界中,在backtrader生态系统销售前买盘必须保证足够的现金是there.
# remove those no longer top ranked # do this first to issue sell orders and free cash for d in (d for d in posdata if d not in rtop): self.log("Exit {} - Rank {:.2f}".format(d._name, rbot[d][0])) self.order_target_percent(d, target=0.0)
Stocks目前以开放的位置,不再排名靠前的出售(即target=0.0
).
Note
A简单self.close(data)
本来这里就够了,而不是明确说明目标percentage.
Rebalancing 3:发出目标为了使所有顶部的排名随着时间的推移stocks
The总投资组合的价值变化和这些股票已经投资组合可能要稍微增加/减少当前位置相匹配预期的百分比。order_target_percent
是进入一个理想的方法市场,因为它会自动计算是否buy
或sell
顺序是needed.
# rebalance those already top ranked and still there for d in (d for d in posdata if d in rtop): self.log("Rebal {} - Rank {:.2f}".format(d._name, rtop[d][0])) self.order_target_percent(d, target=self.perctarget) del rtop[d] # remove it, to simplify next iteration
与位置已重新平衡的股票是添加新的之前完成那些投资组合,作为新会唯一的问题buy
订单和消费现金。从已经去除了现有库存rtop[data].pop()
后具有重新平衡时,剩余库存在rtop
是那些将是新添加到portfolio.
# issue a target order for the newly top ranked stocks # do this last, as this will generate buy orders consuming cash for d in rtop: self.log("Enter {} - Rank {:.2f}".format(d._name, rtop[d][0])) self.order_target_percent(d, target=self.perctarget)
Running这一切,并评估了!
有一个数据加载器类和策略是不够的。只要有任何喜欢其他的框架,需要一些样板。下面的代码使得possible.
def run(args=None): args = parse_args(args) cerebro = bt.Cerebro() # Data feed kwargs dkwargs = dict(**eval("dict(" + args.dargs + ")")) # Parse from/to-date dtfmt, tmfmt = "%Y-%m-%d", "T%H:%M:%S" if args.fromdate: fmt = dtfmt + tmfmt * ("T" in args.fromdate) dkwargs["fromdate"] = datetime.datetime.strptime(args.fromdate, fmt) if args.todate: fmt = dtfmt + tmfmt * ("T" in args.todate) dkwargs["todate"] = datetime.datetime.strptime(args.todate, fmt) # add all the data files available in the directory datadir for fname in glob.glob(os.path.join(args.datadir, "*")): data = NetPayOutData(dataname=fname, **dkwargs) cerebro.adddata(data) # add strategy cerebro.addstrategy(St, **eval("dict(" + args.strat + ")")) # set the cash cerebro.broker.setcash(args.cash) cerebro.run() # execute it all # Basic performance evaluation ... final value ... minus starting cash pnl = cerebro.broker.get_value() - args.cash print("Profit ... or Loss: {:.2f}".format(pnl))
Where以下做:
- 解析参数,并有此可用的(这显然是可选的,因为一切都可以被硬编码,但良好的做法是很好的做法)
- 创建
cerebro
引擎实例。是的,这是西班牙语“brain”和是负责协调管弦乐的框架的一部分演习在黑暗中。虽然它可以接受几个选项,默认值应该足以满足大多数使用cases. - 加载数据文件,它是用一个简单的目录进行扫描
args.datadir
放完所有文件都加载了NetPayOutData
和加入到cerebro
的instance - Adding strategy
- Setting现金,缺省
1,000,000
。鉴于使用情况对于100
在500
一个宇宙的股票,似乎可以公平地有一些现金备用。它也可以changed. - And调用的参数
cerebro.run()
- 最后的表现是evaluated
To使人们有可能使用不同的参数直接从运行的东西命令线,argparse
启用样板介绍如下,用整个code
Performance Evaluation
A天真绩效评估的最终形式加入结果值,即:最终的净资产值减去出发cash.
Thebacktrader生态系统提供了一套内置性能分析器其中也可以使用,如:SharpeRatio
, Variability-Weighted Return
, SQN
和别的。见文档 – 分析仪Reference
The完整script
最后工作的大部分呈现为整体。享受!
import argparse import datetime import glob import os.path import backtrader as bt class NetPayOutData(bt.feeds.GenericCSVData): lines = ("npy",) # add a line containing the net payout yield params = dict( npy=6, # npy field is in the 6th column (0 based index) dtformat="%Y-%m-%d", # fix date format a yyyy-mm-dd timeframe=bt.TimeFrame.Months, # fixed the timeframe openinterest=-1, # -1 indicates there is no openinterest field ) class St(bt.Strategy): params = dict( selcperc=0.10, # percentage of stocks to select from the universe rperiod=1, # period for the returns calculation, default 1 period vperiod=36, # lookback period for volatility - default 36 periods mperiod=12, # lookback period for momentum - default 12 periods reserve=0.05 # 5% reserve capital ) def log(self, arg): print("{} {}".format(self.datetime.date(), arg)) def __init__(self): # calculate 1st the amount of stocks that will be selected self.selnum = int(len(self.datas) * self.p.selcperc) # allocation perc per stock # reserve kept to make sure orders are not rejected due to # margin. Prices are calculated when known (close), but orders can only # be executed next day (opening price). Price can gap upwards self.perctarget = (1.0 - self.p.reserve) / self.selnum # returns, volatilities and momentums rs = [bt.ind.PctChange(d, period=self.p.rperiod) for d in self.datas] vs = [bt.ind.StdDev(ret, period=self.p.vperiod) for ret in rs] ms = [bt.ind.ROC(d, period=self.p.mperiod) for d in self.datas] # simple rank formula: (momentum * net payout) / volatility # the highest ranked: low vol, large momentum, large payout self.ranks = {d: d.npy * m / v for d, v, m in zip(self.datas, vs, ms)} def next(self): # sort data and current rank ranks = sorted( self.ranks.items(), # get the (d, rank), pair key=lambda x: x[1][0], # use rank (elem 1) and current time "0" reverse=True, # highest ranked 1st ... please ) # put top ranked in dict with data as key to test for presence rtop = dict(ranks[:self.selnum]) # For logging purposes of stocks leaving the portfolio rbot = dict(ranks[self.selnum:]) # prepare quick lookup list of stocks currently holding a position posdata = [d for d, pos in self.getpositions().items() if pos] # remove those no longer top ranked # do this first to issue sell orders and free cash for d in (d for d in posdata if d not in rtop): self.log("Leave {} - Rank {:.2f}".format(d._name, rbot[d][0])) self.order_target_percent(d, target=0.0) # rebalance those already top ranked and still there for d in (d for d in posdata if d in rtop): self.log("Rebal {} - Rank {:.2f}".format(d._name, rtop[d][0])) self.order_target_percent(d, target=self.perctarget) del rtop[d] # remove it, to simplify next iteration # issue a target order for the newly top ranked stocks # do this last, as this will generate buy orders consuming cash for d in rtop: self.log("Enter {} - Rank {:.2f}".format(d._name, rtop[d][0])) self.order_target_percent(d, target=self.perctarget) def run(args=None): args = parse_args(args) cerebro = bt.Cerebro() # Data feed kwargs dkwargs = dict(**eval("dict(" + args.dargs + ")")) # Parse from/to-date dtfmt, tmfmt = "%Y-%m-%d", "T%H:%M:%S" if args.fromdate: fmt = dtfmt + tmfmt * ("T" in args.fromdate) dkwargs["fromdate"] = datetime.datetime.strptime(args.fromdate, fmt) if args.todate: fmt = dtfmt + tmfmt * ("T" in args.todate) dkwargs["todate"] = datetime.datetime.strptime(args.todate, fmt) # add all the data files available in the directory datadir for fname in glob.glob(os.path.join(args.datadir, "*")): data = NetPayOutData(dataname=fname, **dkwargs) cerebro.adddata(data) # add strategy cerebro.addstrategy(St, **eval("dict(" + args.strat + ")")) # set the cash cerebro.broker.setcash(args.cash) cerebro.run() # execute it all # Basic performance evaluation ... final value ... minus starting cash pnl = cerebro.broker.get_value() - args.cash print("Profit ... or Loss: {:.2f}".format(pnl)) def parse_args(pargs=None): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description=("Rebalancing with the Conservative Formula"), ) parser.add_argument("--datadir", required=True, help="Directory with data files") parser.add_argument("--dargs", default="", metavar="kwargs", help="kwargs in k1=v1,k2=v2 format") # 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 k1=v1,k2=v2 format") parser.add_argument("--cash", default=1000000.0, type=float, metavar="kwargs", help="kwargs in k1=v1,k2=v2 format") parser.add_argument("--strat", required=False, default="", metavar="kwargs", help="kwargs in k1=v1,k2=v2 format") return parser.parse_args(pargs) if __name__ == "__main__": run()
评论被关闭。