股票量化交易进阶005_回测框架backtrader(五)

本文介绍了KDJ指标的原理、优缺点,并通过Python的backtrader库进行了KDJ策略的回测,展示了回测过程及结果,探讨了KDJ指标的计算方式与一致性问题。
摘要由CSDN通过智能技术生成


前言

回测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指标,还有很多种策略方式,可自行扩展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Johnny2004

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值