飞道的博客

Python ------ 一起来对网格做下回测

449人阅读  评论(0)

真的是无法忍受自己的画图水平,在画图和写代码之间我选择写代码。⊙﹏⊙

那么我们就来讲讲策略的回测。

一起来看一个网格策略的回测,深入了解下网格策略的玩法。

如有不适,请直接无视代码部分。


数据获取

从雪球爬取日K数据,留下5个字段

  1. 日期:timestape
  2. 开盘价:open
  3. 当日最高价:high
  4. 当日最低价:low
  5. 收盘价: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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场