熟悉咱们框架的同学应该知道,咱们对于“大类资产配置”, “多资产轮动”,策略模板写起来特别快,基本不需要代码。
name = "示例-资产配置-再平衡"
desc = ""
start_date = "20100101"
commission = 0.0001
slippage = 0.0001
benchmark = "000300.SH"
symbols = [ "000300.SH", "399006.SZ",]
data_folder = "index"
fields = []
names = []
[[algos]]
name = "RunWeekly"
args = []
[[algos]]
name = "SelectAll"
args = []
[[algos]]
name = "WeightEqually"
args = []
[[algos]]
name = "Rebalance"
args = []
然后就有同学问了,那传统那种择时策略,逻辑可能很个性化,很复杂怎么办,咱们的框架如何支持了。
因此,今天来说说Dual Thrust策略。
(1)N日High的最高价HH, N日Close的最低价LC;
(2)N日Close的最高价HC,N日Low的最低价LL;
(3)Range = Max(HH-LC,HC-LL)
(4)BuyLine = Open + K1×Range
(5)SellLine = Open + K2×Range
(1)当价格向上突破上轨时,如果当时持有空仓,则先平仓,再开多仓;如果没有仓位,则直接开多仓;
(2)当价格向下突破下轨时,如果当时持有多仓,则先平仓,再开空仓;如果没有仓位,则直接开空仓;
我们的dataloader可以直接把所有指标都计算出来:
(原则上讲,多数的趋势策略,都是指标的向量化计算,都可以通过因子表达式预先计算出来,少数如网格,市值平衡,需要根据近期数据动态计算的除外)
通过对proj的name和fields的配置,实现加载B0(螺纹钢)的历史数据,以及相应的通道指标,包括交易信号(long、short)都准备好了。
def gen_dual_thrust(): proj = ProjConfig() proj.name = 'Dual Thrust策略' proj.commission = 0.0001 proj.slippage = 0.0001 proj.symbols = ['B0'] # 证券池列表 proj.benchmark = 'B0' proj.start_date = '20100101' proj.data_folder = 'futures' # 这里指定data/数据目录 fields = ["shift(max(high,10),1)", 'shift(min(close,10),1)', "shift(max(close,10),1)", 'shift(min(low,10),1)'] fields.append('greater(HH-LC,HC-LL)') fields.append('open+range*0.1') fields.append('open-range*0.1') fields.append('close>buyline') fields.append('close<sellline') names = ["HH", "LC", 'HC', 'LL', 'range', 'buyline', 'sellline', 'long', 'short'] proj.fields = fields proj.names = names
这里类似在线量化平台的逻辑:
有了信号,那么就到咱们SelectBySignal出场了。
进一步提升咱们SelectBySignal的通用性,dirction为信号方向,long为开多单,short为开空单 ,即做空,而flat是平仓。在A股就是卖出。
class SelectBySignal(Algo): def __init__(self, rules=[], at_least_count=1, direction='long'): # direction是信号的方向: long:多单, short空单, flat平仓。 super(SelectBySignal, self).__init__() self.rules = rules self.at_least_count = at_least_count self.direction = direction def _check_if_matched(self, df_bar, rules, at_least_count): matched_items = [] for symbol in list(df_bar.index): bar = df_bar.loc[symbol] match = 0 for i, rule in enumerate(rules): if rule in list(bar.index): if bar[rule]: match += 1 else: logger.warning('rule:{}指标未计算?'.format(rule)) if match >= at_least_count: matched_items.append(symbol) return matched_items def __call__(self, target): df_bar = target.df_bar matched = None if self.rules and len(self.rules): matched = self._check_if_matched(df_bar, self.rules, self.at_least_count) if len(matched) == 0: return True if self.direction == 'flat': target.temp['selected_flat'] = matched # 卖出信号,如果允许short,那就是做空单,或者平仓 elif self.direction == 'short': target.temp['selected_short'] = matched else: target.temp['selected'] = matched return True
在使用上,就比较简单了:
# 这里是策略算子列表 proj.algos.append(AlgoConfig(name=SelectBySignal().name, kwargs={'rules': ['long'], 'direction': 'long'})) proj.algos.append(AlgoConfig(name=SelectBySignal().name, kwargs={'rules': ['short'], 'direction': 'short'}))
根据long/short信号,计算当天需要开仓的多空标的。DualThrust是多空操作,没有平仓信号,也就是说,要么开多,要么开空。
再下一步就是仓位计算,确定了要买,那么要买多少?就是仓位分配算子的任务了。
我们策略的逻辑是开仓就是100%,空仓也是。
class WeightEqually(Algo): """ 形成调仓表temp['weights'],三个交易方向,变成统一的调仓权重指令。 我们一般不用buy/sell这种固定size来回测,因为size和你的组合市值有关,10万,还是100万,不一样。 但percent就是一样的,比如单支股票持仓不超过10%。 """ def __init__(self): super(WeightEqually, self).__init__() def __call__(self, target): target.temp["weights"] = {} if 'selected' in target.temp.keys(): selected = target.temp["selected"] n = len(selected) if n > 0: w = 1.0 / n target.temp["weights"] = {x: w for x in selected} if 'selected_short' in target.temp.keys(): selected = target.temp["selected_short"] n = len(selected) if n > 0: w = 1.0 / n target.temp["weights"].update({x: w for x in selected}) if 'selected_flat' in target.temp.keys(): # flat直接仓位归0 selected = target.temp["selected_flat"] n = len(selected) if n > 0: target.temp["weights"].update({x: 0 for x in selected}) return True
最后就是执行调仓,只有这一块与backtrader的api相关。
最后根据调仓表来执行:
在执行中,原则上先卖后买,与你手动交易类似,先平仓才有现金买。
1、目标权重是0的,平仓;
2、目标权重与当前权重相反的,先平仓;(再开反方向单)
3、仓位绝对值变小的,也就是仓位降低的,先执行;相当于换仓的逻辑。
class Rebalance(Algo): def __init__(self): super(Rebalance, self).__init__() def __call__(self, target): if "weights" not in target.temp: return True target_weights = target.temp["weights"] if type(target_weights) is pd.Series: targets = target_weights.to_dict() # 这里只根据当前调仓表调仓,比如当前开多单,本期无信号的,不进行调仓,这也符合我们的常识。 # 先执行平仓 for s, w in target_weights.items(): if w == 0: target.close(s) del target_weights[s] total_mv = target.broker.getvalue() to_sell = [] to_buy = [] for s, w in target_weights.items(): data = self.getdatabyname(s) pos = self.getposition(data) curr_w = target.get_symbol_mv(s) / total_mv if w * curr_w < 0: # 方向相反,先平仓
target.close(s)
to_buy.append(s) # 肯定是开仓操作 continue
if abs(w) < abs(curr_w):
to_sell.append(s)
else:
to_buy.append(s)
# 仓位降低的先执行,得到cash
for s in to_sell:
w = target_weights[s]
target.order_target_percent(s, w * 0.95)
for s in to_buy:
w = target_weights[s]
target.order_target_percent(s, w * 0.95)
return True
策略在如下位置 :
name = "Dual Thrust策略" desc = "" start_date = "20100101" commission = 0.0001 slippage = 0.0001 benchmark = "B0" symbols = [ "B0",] data_folder = "futures" fields = [ "shift(max(high,10),1)", "shift(min(close,10),1)", "shift(max(close,10),1)", "shift(min(low,10),1)", "greater(HH-LC,HC-LL)", "open+range*0.1", "open-range*0.1", "close>buyline", "close<sellline",] names = [ "HH", "LC", "HC", "LL", "range", "buyline", "sellline", "long", "short",] [[algos]] name = "SelectBySignal" args = [] [algos.kwargs] rules = [ "long",] direction = "long" [[algos]] name = "SelectBySignal" args = [] [algos.kwargs] rules = [ "short",] direction = "short" [[algos]] name = "WeightEqually" args = [] [[algos]] name = "Rebalance" args = []
发布者:股市刺客,转载请注明出处:https://www.95sca.cn/archives/103826
站内所有文章皆来自网络转载或读者投稿,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。敬请谅解!