做套:惊呆了!探究如果机器用KDJ法对股票进行做套的胜率,竟高达%80。
前言:
接上一篇:惊呆了,探究如果机器用MACD法对股票进行做套的胜率,竟高达%80
今天在整理自研量化系统的代码屎山时,也无意间想记录一下如果仅仅严格按照KDJ法对买入的股票进行做套,胜率将会是多少呢?
接下来就来尝试一下,首先得简单了解一下什么是KDJ,这个我也不多解释了,“dddd”,所以简单百度百科一下:随机指标KDJ一般是用于股票分析的统计体系,根据统计学原理,通过一个特定的周期(常为9日、9周等)内出现过的最高价、最低价及最后一个计算周期的收盘价及这三者之间的比例关系,来计算最后一个计算周期的未成熟随机值RSV,然后根据平滑移动平均线的方法来计算K值、D值与J值,并绘成曲线图来研判股票走势。
简单了解完之后就可以开始实现了:
-
第一步导入数据:
df = pd.read_csv("2021-11-19.csv")
这里用一下2021年11月19号的上证A股所有股票的日分钟数据
忽略’code’列股票代码显示不全的问题,从数据库备份转csv时没注意code特征列数值的属性,但对这次实验不造成影响,后期把“0”补回去就好了,就是csv保存时把前面的“0”都去掉了,数据量有54w条,足够大了。
-
构造计算KDJ的函数
这里就需要用到kdj的公式了
- 自己查,很简单的
完整的计算kdj函数的代码为:
def CalculateKDJ(price_list, _kdj): """ :param price_list: :param _kdj: 列表 例如 [0, 0, 0] :return: """ RSV = 0 if max(price_list) - min(price_list) != 0: RSV = (price_list[-1] - min(price_list)) / (max(price_list) - min(price_list)) * 100 K = 2 / 3 * _kdj[0] + 1 / 3 * RSV D = 2 / 3 * _kdj[1] + 1 / 3 * K J = 3 * K - 2 * D return [round(K, 2), round(D, 2), round(J, 2)]
-
此外,还利用sklearn的线性模型库linear_model构造一个可以计算一段时间内价格变动趋势的函数,其实就是分析线性方程组的斜率,从而得知在这段时间,价格变动的趋势
def CalculateTrend(price_list): """ :param price_list: :return: """ regr = linear_model.LinearRegression() X = list() for x in range(1, len(price_list) + 1): X.append([x]) regr.fit(X, price_list) return regr.coef_.tolist()[0]
-
重头戏来了,函数都备好了,万事俱备只欠东风,得构造一个逻辑函数或称为步骤函数去利用数据然后作出决策,然后再判断决策是否正确
-
做套的两件大事(不要做反套):
- 卖的时候比原本持仓价格要高
- 买的时候比原本持仓价格要低
因此在函数中设置一个阈值"threshold"来从当这个原本持仓价格
-
初始值设立:
- Ki-1表示前一天的K值,若无前一天的K值,则用50来代替。Di-1表示前一天的D值,若无前一天的D值,则用50来代替。所以初始KDJ设为[50,50,0]。
- 原本持仓价格我们不可能每一个股票都输入一个持仓价格,因此我将持仓价格设置为当天开盘的数据,其实原本持仓价格只是从当一个阈值效果,帮助我们不要做反套,所以这样设立也确实没多大问题。
- 不设立买入和卖出限制的股数,因为要尽可能的多测试样例,但这在现时中是不合理的,因为我们买入卖出的股份不可能是无限多。所以在想要使用的时候,可以添加一个限制操作股数的值,当买或卖达到了这个值,就不能也不用再操作这只股票了
但KDJ就是遵循多种操作方式:
# K与D值永远介于0到100之间。D大于80时,行情呈现超买现象。D小于20时,行情呈现超卖现象。 # J指标取值超过100和低于0,都属于价格的非正常区域,大于100为超买,小0为超卖。 # 当K值由较小逐渐大于D值,在图形上显示K线从下方上穿D线,所以在图形上K线向上突破D线时,俗称金叉,即为买进的讯号 # 当K值由较大逐渐小于D值,在图形上显示K线从上方下穿D线,所以在图形上K线向下突破D线时,俗称死叉,即为卖出的讯号
为了探究其中哪个更适合用于"做套",可以把它看成超短期交易,所以可视化一下KDJ的分布规律:
- 首先获取到全部的股票的日KDJ全部数据
data = {'K':list(),'D':list(),'J':list()} for code in set(df['code'].to_list()): price_list = df[df['code'] == code]['最新价'].to_list() threshold = price_list[0] # Ki-1表示前一天的K值,若无前一天的K值,则用50来代替。 # Di-1表示前一天的D值,若无前一天的D值,则用50来代替。 _KDJ = [50, 50, 0] for i in range(9, len(price_list), 9): KDJ = CalculateKDJ(price_list[i - 9:i], _kdj=_KDJ) data['K'].append(KDJ[0]) data['D'].append(KDJ[1]) data['J'].append(KDJ[2]) _KDJ = KDJ data = pd.DataFrame(data)
随随便便看看吧,会的也不是很多,看一下每列分布吧:
从左往右依次为K,D,J的分布情况:
-
符合:K与D值永远介于0到100之间,J指标可超过100和低于0
但对于D大于80时,行情呈现超买现象。D小于20时,行情呈现超卖现象。根据数据列分布情况,我觉得这个可能对于超短期帮助不大,因此待会实验,也添加一个条件试验一下就好了。
D值和J值的变化曲线:
然后我叠加了一个用于判断趋势的函数:
-
当判断到买时趋势是向下的(<0),则放弃购买
-
当判断到卖时趋势是向上的(>0),则放弃售卖
判断胜负的方式用,买入(卖出)时的价格低于(高于)接下来10分钟的平均价,则判为胜。
for code in tqdm(set(df['code'].to_list())): price_list = df[df['code'] == code]['最新价'].to_list() r = 0 t = 0 threshold = price_list[0] # Ki-1表示前一天的K值,若无前一天的K值,则用50来代替。 # Di-1表示前一天的D值,若无前一天的D值,则用50来代替。 _KDJ = [50, 50, 0] for i in range(5, len(price_list), 5): KDJ = CalculateKDJ(price_list[i - 5:i], _kdj=_KDJ) if price_list[i] < threshold: if KDJ[2] >= 100: if CalculateTrend(price_list[i - 5:i]) > 0: buy['code'].append(code) buy['买入价'].append(price_list[i]) buy['买入后十分钟平均价:'].append(np.mean(price_list[i + 1:i + 11])) r += 1 if price_list[i] <= np.mean(price_list[i + 1:i + 11]) else -1 t += 1 if price_list[i] > threshold: if KDJ[2] <= 0: if CalculateTrend(price_list[i - 5:i]) < 0: sell['code'].append(code) sell['卖出价'].append(price_list[i]) sell['卖出后十分钟平均价:'].append(np.mean(price_list[i + 1:i + 11])) r += 1 if price_list[i] >= np.mean(price_list[i + 1:i + 11]) else -1 t += 1 _KDJ = KDJ if t != 0: rate['code'].append(code) rate['胜率'].append(r / t)
一通捣鼓下来,观看实验效果:
-
仅用“J指标取值超过100和低于0,都属于价格的非正常区域,大于100为超买,小0为超卖。”
胜率为
-
用“J指标取值超过100和低于0,都属于价格的非正常区域,大于100为超买,小0为超卖。”和
D大于80时,行情呈现超买现象。D小于20时,行情呈现超卖现象。“胜率为
,反而降低了
-
用“当K值由较小逐渐大于D值,在图形上显示K线从下方上穿D线,所以在图形上K线向上突破D线时,俗称金叉,即为买进的讯号”和“当K值由较大逐渐小于D值,在图形上显示K线从上方下穿D线,所以在图形上K线向下突破D线时,俗称死叉,即为卖出的讯号”胜率为
可见J值更敏感,对于这种“日内做套”的手段,胜率显得更高一点。
-
完整代码:
import pandas as pd
from sklearn import linear_model
from tqdm import tqdm
import numpy as np
def CalculateKDJ(price_list, _kdj):
"""
:param price_list:
:param _kdj: 列表 例如 [0, 0, 0]
:return:
"""
RSV = 0
if max(price_list) - min(price_list) != 0:
RSV = (price_list[-1] - min(price_list)) / (max(price_list) - min(price_list)) * 100
K = 2 / 3 * _kdj[0] + 1 / 3 * RSV
D = 2 / 3 * _kdj[1] + 1 / 3 * K
J = 3 * K - 2 * D
return [round(K, 2), round(D, 2), round(J, 2)]
def CalculateTrend(price_list):
"""
:param price_list:
:return:
"""
regr = linear_model.LinearRegression()
X = list()
for x in range(1, len(price_list) + 1):
X.append([x])
regr.fit(X, price_list)
return regr.coef_.tolist()[0]
if __name__ == '__main__':
# K与D值永远介于0到100之间。D大于80时,行情呈现超买现象。D小于20时,行情呈现超卖现象。
# J指标取值超过100和低于0,都属于价格的非正常区域,大于100为超买,小0为超卖。
# 当K值由较小逐渐大于D值,在图形上显示K线从下方上穿D线,所以在图形上K线向上突破D线时,俗称金叉,即为买进的讯号
# 当K值由较大逐渐小于D值,在图形上显示K线从上方下穿D线,所以在图形上K线向下突破D线时,俗称死叉,即为卖出的讯号
df = pd.read_csv("min_test_data.csv")
buy = {'code': list(), '买入价': list(), '买入后十分钟平均价:': list()}
sell = {'code': list(), '卖出价': list(), '卖出后十分钟平均价:': list()}
rate = {'code': list(), '胜率': list()}
for code in tqdm(set(df['code'].to_list())):
price_list = df[df['code'] == code]['最新价'].to_list()
r = 0
t = 0
threshold = price_list[0]
# Ki-1表示前一天的K值,若无前一天的K值,则用50来代替。
# Di-1表示前一天的D值,若无前一天的D值,则用50来代替。
_KDJ = [50, 50, 0]
for i in range(5, len(price_list), 5):
KDJ = CalculateKDJ(price_list[i - 5:i], _kdj=_KDJ)
if price_list[i] < threshold:
if KDJ[2] >= 100:
if CalculateTrend(price_list[i - 5:i]) > 0:
buy['code'].append(code)
buy['买入价'].append(price_list[i])
buy['买入后十分钟平均价:'].append(np.mean(price_list[i + 1:i + 11]))
r += 1 if price_list[i] <= np.mean(price_list[i + 1:i + 11]) else -1
t += 1
if price_list[i] > threshold:
if KDJ[2] <= 0:
if CalculateTrend(price_list[i - 5:i]) < 0:
sell['code'].append(code)
sell['卖出价'].append(price_list[i])
sell['卖出后十分钟平均价:'].append(np.mean(price_list[i + 1:i + 11]))
r += 1 if price_list[i] >= np.mean(price_list[i + 1:i + 11]) else -1
t += 1
_KDJ = KDJ
if t != 0:
rate['code'].append(code)
rate['胜率'].append(r / t)
buy = pd.DataFrame(buy)
print(buy.head())
sell = pd.DataFrame(sell)
print(sell.head())
rate = pd.DataFrame(rate)
print(rate.head())
print(rate['胜率'].sum() / len(rate))