真的是无法忍受自己的画图水平,在画图和写代码之间我选择写代码。⊙﹏⊙
那么我们就来讲讲策略的回测。
一起来看一个网格策略的回测,深入了解下网格策略的玩法。
如有不适,请直接无视代码部分。
数据获取
从雪球爬取日K数据,留下5个字段
- 日期:timestape
- 开盘价:open
- 当日最高价:high
- 当日最低价:low
- 收盘价:close
stock_code:就是指致富代码
period:数据的获取周期,日K就是每天 day
# 雪球
import json
import time
import requests
def get_cookies(stock_code):
# https://xueqiu.com/S/%s
# 需要先请求页面获取cookie
url_str = "https://xueqiu.com/S/%s" % stock_code
headers = {
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36"
}
resp = requests.request("GET", url_str, headers=headers)
if resp.status_code == 200:
return resp.cookies
else:
print(resp, resp.text)
def get_data(stock_code, period):
cookies = get_cookies(stock_code)
print(stock_code, period)
begin = int(time.time() * 1000)
time_type = "before"
count = 2000000000
url_string = "https://stock.xueqiu.com/v5/stock/chart/kline.json?symbol=%s&begin=%d&period=%s&type=%s&count=%d" \
"&indicator=%s" % (
stock_code, begin, period, time_type, -count, "kline,pe,pb,ps,pcf,market_capital,agt,ggt,balance")
print(url_string)
headers = {
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36"
}
resp = requests.request("GET", url_string, headers=headers, cookies=cookies)
resp.encoding = 'utf-8'
if resp.status_code == 200:
data = json.loads(resp.text)
return data["data"]["item"]
else:
print(resp)
最后的return data["data"]["item"]
就是 stock_code 每天的数据。
画线
根据每日收盘价,先画一条线看看。
import time
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import api.xq
stock_code = "SH512000"
# 从雪球获取数据
data = api.xq.get_data(stock_code, "day")
data = np.array(data)
data = np.delete(data[:, 0:6], 1, axis=1)
# 时间戳转日期字符串
for row in data:
row[0] = time.strftime("%Y-%m-%d", time.localtime(row[0] / 1000))
print(data)
data = pd.DataFrame(data, columns=['timestamp', 'open', 'high', 'low', 'close'])
data.set_index('timestamp', drop=True, inplace=True)
data.sort_index()
print(data)
plt.plot(data.index, data['close'])
# x轴文字倾斜程度
# plt.xticks(rotation=90, size=7)
plt.xticks(size=7)
# 设置标签
plt.xlabel('day')
plt.ylabel('close')
plt.title(stock_code)
from matplotlib.pyplot import MultipleLocator
# 把x轴的刻度间隔设置为40,并存在变量里
x_major_locator = MultipleLocator(40)
# ax为两条坐标轴的实例
ax = plt.gca()
# 把x轴的主刻度设置为40的倍数
ax.xaxis.set_major_locator(x_major_locator)
# 把x轴的刻度范围设置为0到1200
plt.xlim(0, 1200)
plt.show()
这就是SH512000(券商ETF)自发行以来的每日收盘价,模仿日K图。(简直就是轻松愉悦)
一切的回测都因该基于历史真实数据,这才算是比较严谨的。
拟定网格
首先我们需要来确定下网格顶部和底部。
就以SH512000自发行以来的历史业绩的最高点和最低点,作为网格的底部和顶部。
基准价就是 (最高点 + 最低点) / 2
接下来就是拟定格子
- 每下跌5%,我们就买入
- 每上涨5%,我们就卖出
每份就买一手,100股
# 最大值中的最大值
max = data['high'].max()
min = data['low'].min()
# 基准价
benchmark = float(Decimal((max + min) / 2 * 1.2).quantize(Decimal('0.000')))
print(max, min, benchmark)
grid = 0.05
count = 100
max_consume_money = Decimal(0)
consume_money = Decimal(0)
opt = []
# [日期, 价格, -1/1] 用于画点
# 历史
opt_b = []
opt_s = []
# 总利润
profit = 0
因为决定做网格的第一天开盘价并不会运气好到直接是基准价。
如果 基准价*(1-0.05) >= 开盘价
,我们需要买进基准价与第一天开盘价之间差的格子,来完成减仓。
基准价*(1-0.05) < 开盘价
,我们则什么都不做
# 建仓
first = data.iloc[0]
while benchmark * (1 - grid) >= first['open']:
# 一手买入 或者 倍数买入
# 买入
# 基准价变更
benchmark = float(Decimal(benchmark * (1 - grid)).quantize(Decimal('0.000')))
print(data.index[0], "建仓买入", benchmark)
# 计算的操作
consume_money += Decimal(benchmark) * Decimal(count)
# 添加记录
h = history.History(stock_code, 1, benchmark, count)
opt.append(h)
opt_b.append([data.index[0], benchmark, 1])
正式运行我们的网格策略:
- 每下跌5%,买入
- 每上涨5%,卖出
max_consume_money = consume_money
for day_up_down in data.index:
open = data.loc[day_up_down].values[0]
high = data.loc[day_up_down].values[1]
low = data.loc[day_up_down].values[2]
# close = data.loc[day_up_down].values[3]
# 盘前
# 如果 opt 为空,没有任何操作, 基准价 > 开盘价,触发买入
# if len(opt) == 0 and benchmark > open:
if benchmark * (1 - grid) > open:
# 一手买入 或者 倍数买入
# 买入
# 基准价变更
benchmark = open
print(day_up_down, "开盘买入", benchmark)
# 计算的操作
consume_money += Decimal(benchmark) * Decimal(count)
if consume_money.compare(max_consume_money) > 0:
max_consume_money = consume_money
# 添加记录
h = history.History(stock_code, 1, open, count)
opt.append(h)
opt_b.append([day_up_down, benchmark, 1])
elif benchmark * (1 + grid) <= open:
if len(opt) > 0:
# 卖出
# 基准价变更
benchmark = open
# 计算的操作
# 利润
temp = float(
((Decimal(benchmark) - Decimal(opt[len(opt) - 1].price)) * count).quantize(Decimal('0.00')))
profit += temp
consume_money -= Decimal(benchmark) * Decimal(count)
print(day_up_down, "开盘卖出", benchmark, opt[len(opt) - 1].price, "收益", temp)
# 修改记录
h = history.History(stock_code, -1, benchmark, count)
opt.pop()
opt_s.append([day_up_down, benchmark, -1])
while benchmark * (1 - grid) >= low:
# 盘中
# 一手买入 或者 倍数买入
# 买入
# 基准价变更
benchmark = float(Decimal(benchmark * (1 - grid)).quantize(Decimal('0.000')))
print(day_up_down, "盘中买入", benchmark)
# 计算的操作
consume_money += Decimal(benchmark) * Decimal(count)
if consume_money.compare(max_consume_money) > 0:
max_consume_money = consume_money
# 添加记录
h = history.History(stock_code, 1, benchmark, count)
opt.append(h)
opt_b.append([day_up_down, benchmark, 1])
# open = high开盘价就是最高价的情况 到时候再触发 low 会多买,这是一个假收益
# 不会那么巧吧开盘价跟最高价一样
if len(opt) > 0 and open != high:
while len(opt) > 0 and benchmark * (1 + grid) <= high:
# 卖出
# 基准价变更
benchmark = float(Decimal(benchmark * (1 + grid)).quantize(Decimal('0.000')))
# 计算的操作
temp = float(
((Decimal(benchmark) - Decimal(opt[len(opt) - 1].price)) * count)
.quantize(Decimal('0.00')))
profit += temp
consume_money -= Decimal(benchmark) * Decimal(count)
print(day_up_down, "盘中卖出", benchmark, opt[len(opt) - 1].price, "收益", temp)
# 修改记录
h = history.History(stock_code, -1, benchmark, count)
opt.pop()
opt_s.append([day_up_down, benchmark, -1])
这里存在一个比较有难度的地方,因为获取的数据是日K,没法获取到日内的波动数据(每分钟的波动值),所以会出现,
open->low->high->close
的情况
也会出现
open->high->low->close
的情况
说人话就是,因为数据来源的局限性,我们没法知道当天开盘后是先涨再跌,还是先跌后涨,这会造成我们回测的误差,具体表现为,我的逻辑判断是默认先跌后涨,但如果当天是涨后跌的,可能 high < 卖出价
(high达不到卖出价),但因为我的判断方法导致先买入low,基准价变为low,这时候再去跟high比,铁定是可以卖出的了,这是一个误差点,会导致最后收益的增多,但是并不妨碍我们赚钱。
df_b = pd.DataFrame(opt_b, columns=['timestamp', 'price', 'opt'])
df_b.set_index('timestamp', drop=True, inplace=True)
df_b.sort_index()
df_s = pd.DataFrame(opt_s, columns=['timestamp', 'price', 'opt'])
df_s.set_index('timestamp', drop=True, inplace=True)
df_s.sort_index()
plt.plot(df_b.index, df_b['price'], 'or')
plt.plot(df_s.index, df_s['price'], 'og')
plt.show()
最后画出买入卖出点,以及计算总共消耗的资金,和获取到的收益
红点是买入,绿点是卖出。
可以看到,第一天的时候有多个买点,这就是我们的建仓操作。
可以看出收益还是很可观的。
是不是有点迫不及待,跃跃欲试了呢?
别急,其实这里面还有存在一些问题。
问题
这个策略真的就那么完美,没有任何的问题了么,显然并不是这样的。
我从回测中发现了一些卖出的时候收益为负数的数据,为负的意思可是当笔卖出交易我们在亏钱啊!
那么这是为什么呢,我们来研究一下。(很可能是我写的bug,就看我怎么掰扯了。。。。)
其实经过前几篇文章的学习,我们不难看出,我们的网格策略一个卖出价必定对应着一个买入价。
现在我们的格子大小是5%
次数 | 价格 | 操作 |
---|---|---|
1 | 1 | 买入 |
2 | 0.95 | 买入 |
3 | 0.998 | 卖出 |
4 | 0.948 | 买入 |
5 | 0.995 | 卖出 |
6 | 1.047 | 卖出 |
次数 | 价格 | 操作 |
---|---|---|
1 | 1 | 买入 |
2 | 0.95 | 买入 |
3 | 0.902 | 买入 |
4 | 0.947 | 卖出 |
5 | 0.994 | 卖出 |
6 | 1.043 | 卖出 |
是不是发现问题所在了。正是因为我们的格子大小为5%
,所以当基准价为0.95的时候上涨5%卖出的价格是0.998并不是1,出现了亏损,就是因为这个差值,而且前期买入的越多卖出的越少,到最后积少成多,造成了卖出的那一笔收益为负数。
之前几篇文章举的例子都是一个固定的数值,所以并不会出现这种问题。
当然因为我们严格执行低买高卖所以总体上是挣钱的。
那么有没有什么方法可以优化掉这个插值嘛?
当然是有的,我们的策略就是应该不断进化的。
其实很简单,我们可以改成:
- 每下跌5%,买入
- 每上涨5.5%,卖出
这样就不会发生 有卖出收益为负的情况了。
回测是我们下场实战前的必要工作,因为下一步我们就要在市场里亏真金白银了,回测会带给我们心理安慰。
大量回测不同标的,也可以让我们挑到更好的品种,不断进化自己的策略。
公众号:韭要亏钱
转载:https://blog.csdn.net/qq_32808045/article/details/117486454