BTFD逢低买入:唯一重要的投资策略

BTFD逢低买入:唯一重要的投资策略

本案例源自:《TOPQ极宽backtrader量化实盘课件–F系列》
BackTrader量化实盘课件E系列正式发布 – 极宽 http://www.topquant.vip/?p=859

BTFD – Buy The Fucking Dip
逢低买入(BTFD)策略,也就是就常说的抄底。
由于股票等金融产品价格暴跌时,股票会被大量超卖, 即使股票价格正常下跌,也不可避免地会出现波动。
资产 泡沫破灭之后,人们由于恐慌而杀跌的时候,就是买入的时机。
理论上,如果在暴跌到底时买入股票等金融产品,因为随后价格会出现部分反弹,交易者可以赚取可观的回报。
有交易者甚至认为:BTFD是唯一重要的投资策略
BTFD! One Chart Shows Why This Is The Only Strategy That Matters
http://dark-bid.com/BTFD-only-strategy-that-matters.html

有关BTFD策略的资料很多:
Can anyone replicate this strategy : algotrading
https://www.reddit.com/r/algotrading/comments/5jez2b/can_anyone_replicate_this_strategy/

有趣的是,backtrader官方blog,也引用以上QE相关的BTFD策略分析图,并连续两期在blog,对BTFD策略进行了讲解和编程实现:
BTFD (Buy The F… Dip) — backtrader documentation https://www.backtrader.com/blog/posts/2016-12-26-btfd/btfd.html
BTFD – Reality Bites — backtrader documentation https://www.backtrader.com/blog/posts/2016-12-28-btfd-bites/btfd-bites.html

需要说明的是,随着量化交易的推广,以及QE货币政策的滥用,BTFD策略目前,效果没有以前那么明显,不过还是值得一试的策略。

btr官方blog的案例,侧重于数据分析对比,不是实盘策略,我们根据相关资料,重新编写了BTFD的量化回测案例。
案例源码参见文章最后:

以下是运行结果:

BT回测数据分析
时间周期:2018-01-01 至 2018-12-31
==================================================
起始资金 Starting Portfolio Value: 100000.00
资产总值 Final Portfolio Value: 100459.45
利润总额 Total Profit: 459.45
ROI投资回报率 Return on Investment: 0.46 %
夏普指数 SharpeRatio : 0.00
SQN策略评级指数,sqn: 1.10, sqn_ntrd: 14


案例测试数据采用的是:A股002046个股,ROI回报率0.46%。
ROI数据,看起来有些偏低,考虑到2018行业平均亏损额度:20-30%,这个BTFD策略已经不错了。
SQN策略评级指数1.1分,2018年度只有14次交易,交易次数有些少,SQN评分还有部分优化空间。

另外需要补充说明的是,标准BTFD策略是:

  • Buy The Close: this part is clear. Buy during the closing auctionan ;当天收市前,以close收盘价买入
  • When S&P Is Down At Least 1%: in most cases that means that the current close is 1% below the previous close;买入时机,是标普下跌至少1%时:大多数情况下,这意味着当前收盘价,比上一个收盘价低1%。
  • Hold For 2 Days: the interpretation here would be to sell at the close 2 trading sessions later;持有2天,在2个交易日结束时出售。
  • 2x Leverage: Each buy operation is leveraged 2x (or 100% credit);2倍杠杆,每个购买操作,都是2倍杠杆。

案例当中有所修改:

  • 采用的是上证指数,而不是标准普尔指数,理论上应该采用上证50或者沪深300等蓝筹股指数作为参考;ps,不过测试数据roi更低,可能测试数据量不够。
  • 默认参数是下跌10%,持有2天;案例参数是下跌10%,持有5天。这个5天持有,是根据002046个股,opt参数优化的结果,不是标准最优解。
  • 默认采用2x倍以上杠杆,案例没有采用杠杆模式,所以收益相对偏低。
  • 默认是当天收市前,按close收盘价买入。这个实际操作有些难度,我们改为:按当天收盘价close挂单,在次日后的交易时间买入。

附录,BTFD策略案例源码:

# -*- coding: utf-8 -*-
'''
TopQuant-TQ极宽智能量化回溯分析系统
培训课件系列 2019版

Top极宽量化(原zw量化),Python量化第一品牌
by Top极宽·量化开源团队 2019.01.011 首发

网站: www.TopQuant.vip www.ziwang.com
QQ群: Top极宽量化总群,124134140

'''
#

import sys;
sys.path.append("topqt/")
#
import os,time,arrow,math,random,pytz
import numpy as np
import pandas as pd
import datetime as dt
import logging

import matplotlib as mpl
import matplotlib.pyplot as plt

#
#
import backtrader as bt
import topquant2019 as tq
import topq_edu2019 as tqedu
#

#----------------------

class sta_btfd(bt.Strategy):
params = (('day_hold', 2),('k_down', -10))
#
def __init__(self):
#self.pctdown = self.data.close / self.data.close(-1) - 1.0
#
self.barexit =0

#self.sgn_btfd=bt.ind.Momentum(period=1)
#bt.indicators.Momentum(self.data, period=12)
self.sgn_btfd={}
for xc,xdat in enumerate(self.datas):
xnam=xdat._name
#print('x',xnam)
fgInx=xnam.find('inx_')>=0
if fgInx:
#self.sgn_btfd[xnam]=(self.data.close / self.data.close(-1))*1000
self.sgn_btfd[xnam]=bt.ind.Momentum(period=1)
#self.sgn_btfd100[xnam]=self.sgn_btfd[xnam]/ self.data.close(-1)*1000
#xxx

def next(self):
#self.log('Close, %.2f' % self.dataclose[0])
#
#if self.order:
# return
#print('self.order:',self.order)
#
#dstake,dcash0=self.p.stake,self.p.cash0
#cash0_buy=self.p.cash0*self.p.cash_size
#
for xc,xdat in enumerate(self.datas):
xnam=xdat._name
fgInx=xnam.find('inx_')>=0

if fgInx:
fgBuy=False
#if self.sgn_btfd100[xnam][0]<self.p.k_down:fgBuy=True
xd=xdat.close[0]-xdat.close[-1]
kxd=xd*1000/xdat.close[-1]
if kxd<self.p.k_down:fgBuy=True
#
#print('x',self.sgn_btfd100[xnam][0])
#xxx
#
else:
css=''
dprice=xdat.close[0]
xpos = self.getposition(xdat)
pos_price5,pos_num=xpos.price,xpos.size
tim_str = xdat.datetime.date(0).isoformat()
#
if fgBuy:
css='Buy'
#nsiz=round(cash0_buy/dprice)
#
#self.buy(data=xdat,size=nsiz)
self.buy(data=xdat)
self.barexit = len(self) + self.p.day_hold
#
if (pos_num>0)and(len(self) == self.barexit):
css='Sel'
#self.sell(data=xdat,size=nsiz)
self.sell(data=xdat,size=pos_num)
#
#
if css!='':
xss='{}# {},{} @{}, $:{:.06f} pos:{} $:{:.06f}'.format(xc,css,tim_str,xnam
,dprice,pos_num,pos_price5)
#
tq.log(xss)

def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
# 检查订单执行状态order.status:
# Buy/Sell order submitted/accepted to/by broker
# broker经纪人:submitted提交/accepted接受,Buy买单/Sell卖单
# 正常流程,无需额外操作
return

# 检查订单order是否完成
# 注意: 如果现金不足,经纪人broker会拒绝订单reject order
# 可以修改相关参数,调整进行空头交易
if order.status in [order.Completed]:
xss=',成交价: %.2f,小计 Cost: %.2f,佣金 Comm %.2f'% (order.executed.price,order.executed.value,order.executed.comm)
#print(order['info'])
#xxx
if order.isbuy():
#tq.log('买单执行 Buy EXECUTED'+xss)
#self.log('卖单执行SELL EXECUTED,成交价: %.2f,小计 Cost: %.2f,佣金 Comm %.2f' % (order.executed.price,order.executed.value,order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
#
#self.buy_tim,self.buy_ntim='',0
#
elif order.issell():
#tq.log('卖单执行 SELL EXECUTED'+xss)
#self.log('卖单执行SELL EXECUTED,成交价: %.2f,小计 Cost: %.2f,佣金 Comm %.2f' % (order.executed.price,order.executed.value,order.executed.comm))
pass

self.bar_executed = len(self)

elif order.status in [order.Canceled, order.Margin, order.Rejected]:
#self.log('订单Order: 取消Canceled/保证金Margin/拒绝Rejected')
tq.log('订单Order: 取消Canceled/保证金Margin/拒绝Rejected')

# 检查完成,没有交易中订单(pending order)
self.order = None

def notify_trade(self, trade):
# 检查交易trade是关闭
if not trade.isclosed:
return

#tq.log('交易操盘利润OPERATION PROFIT, 毛利GROSS %.2f, 净利NET %.2f' %(trade.pnl, trade.pnlcomm))

def stop(self):
print('(策略参数 day_hold=%2d,k_down=%2d) ,最终资产总值: %.2f' %(self.p.day_hold,self.p.k_down, self.broker.getvalue()))

#----------------------

print('\n#1,数值数据')

rs0='data/'
rdat=rs0+'stk/'
rbas=rs0+'inx/'
tim0str,tim9str='2018-01-01','2018-12-31'
#tim0str,tim9str='',''
#
syb='002046'
#

baslst=['000001']
syblst=[syb]
qx=tq.tq_init('tq01')
#
tq.pools_get4flst(qx,rbas,baslst,tim0str,tim9str,fgInx=True)
tq.pools_get4flst(qx,rdat,syblst,tim0str,tim9str,fgInx=False)

tq.bt_set(qx)
#
print('\n#2-1,添加自定义分析指标:LegDownUpAnalyzer')
#--anz.diy
#qx.cb.addanalyzer(LegDownUpAnalyzer, _name='legdown')
#
print('\n#2-2,添加回测策略:ma_cross')
qx.cb.addstrategy(sta_btfd,day_hold=5,k_down=-10)

#
print('\n#3,调用回溯测试主程序:run')
qx.bt_results= qx.cb.run()
#
#
print('\n#8,根据回测数据,绘制分析图表')
qx.cb.plot()

#
print('\n#9,分析自定义分析指标:legdown')
tq.bt_anz(qx)

发表评论

电子邮件地址不会被公开。 必填项已用*标注