前言
回测KDJ金叉买入死叉卖出策略
一. 认识KDJ指标
KDJ指标的中文名称又叫随机指标,最早起源于期货市场,由乔治·莱恩首创。KDJ指标主要是研究最高价、最低价和收盘价之间的关系,同时也融合了动量观念、强弱指标和移动平均线的优点。
它通过统计学原理,识别N个交易日内最高价、最低价、最新收盘价三者之间的比例关系来计算随机值(RSV),然后再根据加权移动平均线(EMA)的方法来计算K值、D值、J值。
具体计算方法如下:
RSV = (收盘价-N周期最低价)/(N周期最高价-N周期最低价)*100
K值 = RSV的N周期加权移动平均值
D值 = K值的N周期加权移动平均值
J值 = 3K-2D
一般来说,RSV的N周期选择9,K和D的N周期选择3。
二. KDJ的优缺点
KDJ指标优点是:指标非常敏感,适合短线操作,在常态情况下,具有较高的准确度。
KDJ指标缺点是:指标过于敏感,常过早的发出买入和卖出信号,在极强的市场上和极弱的市场上会出现指标钝化,使投资者无所适从,买入和卖出过早,造成操作失误。
KDJ高低位“钝化”:
钝化也是一种指标盲区。简单理解,所谓的钝化就是说指标在某个阶段或某种情况下,会失去原本的指导意义。这就好像十字路口上的红绿灯坏了一样,红灯不再代表车子禁行,绿灯也不再代表车子可以通行。
KDJ的钝化一般是帮助我们判断阶段顶部或底部的。如图所示,①②号位置都属于指标高位钝化,这个时候J值已经涨得不能再涨,无法带动K判断接下来的方向。而图中的③号位置就是低位钝化,因为此时J值跌得不能再跌了,也无法带动K的走势。这两种情况都不用急着卖出或抄底。因为你不知道什么时候钝化完成,如果在高位钝化时候提前卖出,可能就会错失接下来的一波好行情,但如果低位钝化,看到KDJ指标进入超卖区,以为可以买入,结果可能是抄在半山腰。所以,一句话:钝化时不要急着买卖,等到金叉或死叉再操作。
买买条件:J值上穿K值作为买入信号;J值下穿K值作为卖出信号
三. 使用backtrader回测kdj买卖策略
# -*- coding: UTF-8 -*-
"""
@Project :JQdataQuant
@File :backtraderTest6_kdj.py
@Author :johnny2004
@Date :2022-05-30 19:14
@desc :回测kdj指标,金叉买入,死叉卖出
"""
import backtrader as bt
import data.stock as st
import pandas as pd
class MyStrategy(bt.Strategy):
def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.date(0)
print('%s %s ' % (dt.isoformat(), txt))
def __init__(self):
self.log('=====回测策略初始化!!!')
# 初始化订单情况
self.order = None
# 定义初始资金和最终收益率
self.startCash = None
self.final_profit = None
'''计算kdj指标的k,d,j的值'''
self.kd = bt.indicators.StochasticFull(
self.data,
period=9,
period_dfast=3,
period_dslow=3,
)
self.K = self.kd.percD
self.D = self.kd.percDSlow
self.J = self.K * 3 - self.D * 2
def start(self):
self.log('=====回测策略开始啦!!!')
self.startCash = self.broker.getvalue()
def next(self):
if self.order:
return
# J - D 值
condition1 = self.J[-1] - self.D[-1]
condition2 = self.J[0] - self.D[0]
if not self.position:
# 金叉
if condition2 > 0 and condition1 < 0:
self.log('====金叉买入,买入价%f ' % self.data.close[0])
self.order = self.buy()
else:
# 死叉
if condition2 < 0 and condition1 > 0:
self.log('====死叉买出,卖出价%f ' % self.data.close[0])
self.order = self.sell()
def stop(self):
self.final_profit = (self.broker.getvalue() / self.startCash) - 1
self.log('=====回测结果:初始资金:%f ,剩余资金:%f ,净收益:%f ,最终收益率: %.6f' %
(self.startCash, self.broker.getvalue(),
(self.broker.getvalue() - self.startCash), self.final_profit))
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
# 做多/做空 订单 已提交/已执行 到/被代理 - 无事可做
return
# 检查订单是否已经完成
# 注意:如果没有足够资金,代理可能拒绝订单
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
else: # 做空
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected===可能资金不足!!!')
self.order = None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
def feed_data(code, startDate, endDate):
'''
自定义方法:backtrader喂数据
:param code: 股票代码
:param startDate: 开始日期
:param endDate: 结束日期
:return: bt.feeddata
'''
# 1.1 拿到数据
data = st.get_csv_price(code, startDate, endDate)
data['date'] = pd.to_datetime(data.index)
print(data.head())
# 1.2 把数据传入bt.feeds
data = bt.feeds.PandasData(
dataname=data,
fromdate=pd.to_datetime(startDate),
todate=pd.to_datetime(endDate),
datetime='date',
open='open',
high='high',
low='low',
close='close',
volume='volume',
openinterest=-1
)
return data
def run_cerebo():
# 股票
# code = '000001.XSHE'
code = st.get_code("中芯国际")
cerebo = bt.Cerebro()
# 1.喂数据
data = feed_data(code, '2021-01-01', '2022-01-01')
cerebo.adddata(data)
# 2.添加策略
cerebo.addstrategy(MyStrategy)
# 3.设置初始资金和佣金
cerebo.broker.setcash(200000)
cerebo.broker.setcommission(0.0003)
# 4.运行大脑回测
cerebo.run()
# 5. 可视化
cerebo.plot()
if __name__ == '__main__':
run_cerebo()
打印结果:
2021-11-23 BUY EXECUTED, Price: 55.16, Cost: 55.16, Comm 0.02
2021-12-06 ====死叉买出,卖出价54.040000
2021-12-07 SELL EXECUTED, Price: 54.22, Cost: 55.16, Comm 0.02
2021-12-07 OPERATION PROFIT, GROSS -0.94, NET -0.97
2021-12-09 ====金叉买入,买入价54.950000
2021-12-10 BUY EXECUTED, Price: 54.70, Cost: 54.70, Comm 0.02
2021-12-14 ====死叉买出,卖出价55.010000
2021-12-15 SELL EXECUTED, Price: 54.88, Cost: 54.70, Comm 0.02
2021-12-15 OPERATION PROFIT, GROSS 0.18, NET 0.15
2021-12-28 ====金叉买入,买入价53.170000
2021-12-29 BUY EXECUTED, Price: 53.13, Cost: 53.13, Comm 0.02
2021-12-31 =====回测结果:初始资金:200000.000000 ,剩余资金:200001.172436 ,净收益:1.172436 ,最终收益率: 0.000006图表展示
四. 关于KDJ指标的计算方式
上面使用的计算kdj指标的值,对比同花顺软件,k,d,j的值都对不上。
经验证,使用下面方法是可以做到跟同花顺软件一致:
def calculate_kdj(df):
low_list = df['low'].rolling(9, min_periods=9).min()
high_list = df['high'].rolling(9, min_periods=9).max()
low_list.fillna(value=0, inplace=True)
high_list.fillna(value=0, inplace=True)
rsv = (df['close'] - low_list) / (high_list - low_list) * 100
# print(rsv.tail())
df['K'] = pd.DataFrame(rsv).ewm(com=2).mean()
df['D'] = df['K'].ewm(com=2).mean()
df['J'] = 3 * df['K'] - 2 * df['D']
return df
总结
以上是kdj的基本使用方法,要充分利用好kdj指标,还有很多种策略方式,可自行扩展。