目录
- 前言
- 一、回测的主方法
- 二、回测实现
-
- 1 - 获取回测数据ticks
- 2 - 运行回测
- 3 - 为回测数据添加生成方法
- 4 - sell中添加订单的pnl收益计算
- 5 - 策略执行中调整买卖ma20的比例
- 三、回测订单分析
-
- 1 - 订单打印数据
- 2 - matplotlib盈亏柱状图
- 3 - k线生成并投影订单进出时间价格
- 五、完整源码
前言
- 上一篇我们已经通过padas导入bar回测数据,这篇我们通过导入的ticks数据进行回测的实现
- 生成1:matplotlib盈亏柱状图
- 生成2:k线图
- 生成3:投影订单进出场时间与价格到k线图
一、回测的主方法
if __name__ == '__main__':
ticks = get_ticks_for_backtesting("E:\\Downloads\\600036_data\\600036_ticks.csv",
"E:\\Downloads\\600036_data\\600036_5m.csv")
ast = AstockTrading('ma')
ast.run_backtestting(ticks)
print('ast._current_orders:')
print(ast._current_orders)
print("-------------------------------------")
print('ast._history_orders:')
print(ast._history_orders)
profit_orders = 0
loss_orders = 0
orders = ast._history_orders
for key in orders.keys():
if orders[key]['pnl'] >= 0:
profit_orders += 1
else:
loss_orders += 1
win_rate = profit_orders / len(orders)
loss_rate = loss_orders / len(orders)
orders_df = pd.DataFrame(orders).T
orders_df.loc[:, 'pnl'].plot.bar()
plt.show()
bar5 = pd.read_csv("E:\\Downloads\\600036_data\\600036_5m.csv",
parse_dates=['datetime']
)
bar5.loc[:, 'datetime'] = [date2num(x) for x in bar5.loc[:, 'datetime']]
fig, ax = plt.subplots()
candlestick_ohlc(
ax,
quotes=bar5.values,
width=0.2,
colorup="r",
colordown='g',
alpha=1.0,
)
for index, row in orders_df.iterrows():
ax.plot(
[row['open_datetime'], row['close_datetime']],
[row['open_price'], row['close_price']],
color='darkblue',
marker='o',
)
plt.show()
二、回测实现
1 - 获取回测数据ticks
def get_ticks_for_backtesting(tick_path, bar_path):
"""
:func: get ticks for backtesting, need two params
:param1 tick_path: 生成的回测数据路径
csv file with tick data,
when there is not tick data,
use bat_path to create tick data
example: "E:\\Downloads\\600036_data\\600036_ticks.csv"
:param2 bar_path: 历史数据的tick路径
csv file with bar data,
used in creating tick data
example: "E:\\Downloads\\600036_data\\600036_5m.csv"
:return: ticks in list with tuples in it, such as
[(datetime, last_price), (datetime, last_price)]
"""
if os.path.exists(tick_path):
ticks = pd.read_csv(
tick_path,
parse_dates=['datetime'],
index_col='datetime'
)
tick_list = []
for index, row in ticks.iterrows():
tick_list.append((index, row[0]))
ticks = np.array(tick_list)
else:
bar_5m = pd.read_csv(bar_path)
ticks = []
for index, row in bar_5m.iterrows():
if row['open'] < 30:
step = 0.01
elif row['open'] < 60:
step = 0.03
elif row['open'] < 90:
step = 0.05
else:
step = 0.1
arr = np.arange(row['open'], row['high'], step)
arr = np.append(arr, row['high'])
arr = np.append(arr, np.arange(row['open'] - step, row['low'], -step))
arr = np.append(arr, row['low'])
arr = np.append(arr, row['close'])
i = 0
dt = parser.parse(row['datetime']) - timedelta(minutes=5)
for item in arr:
ticks.append((dt + timedelta(seconds=0.1 * i), item))
i += 1
tick_df = pd.DataFrame(ticks, columns=['datetime', 'price'])
tick_df.to_csv(tick_path, index=0)
return ticks
2 - 运行回测
def run_backtestting(self, ticks):
"""
:method: ticks will be used to generate bars,
when bars is long enough, call strategy()
:param ticks: list with (datetime, price) in the list
:return: none
"""
for tick in ticks:
self.bar_generator_for_backtesting(tick)
if self._init:
self.strategy()
else:
if len(self._Open) >= 100:
self._init = True
self.strategy()
3 - 为回测数据添加生成方法
def bar_generator_for_backtesting(self, tick):
"""
:method: for backtesting only, used to update _Open, _ High, etc, It needs just one param
:param tick: tick info in tuple, (datetime, price)
:return:
"""
if tick[0].minute % 5 == 0 and tick[0].minute != self._last_bar_start_minute:
self._last_bar_start_minute = tick[0].minute
self._Open.insert(0, tick[1])
self._High.insert(0, tick[1])
self._Low.insert(0, tick[1])
self._Close.insert(0, tick[1])
self._Dt.insert(0, tick[0])
self._is_new_bar = True
else:
self._High[0] = max(self._High[0], tick[1])
self._Low[0] = max(self._Low[0], tick[1])
self._Close[0] = tick[1]
self._Dt[0] = tick[0]
self._is_new_bar = False
4 - sell中添加订单的pnl收益计算
def _sell(self, key, price):
"""
:method: close a long order, It needs two params
:param1 key: long order's key
:param2 price: selling price
:return:
"""
self._current_orders[key]['close_price'] = price
self._current_orders[key]['close_datetime'] = self._Dt[0]
self._current_orders[key]['pnl'] = \
(price - self._current_orders[key]['open_price']) \
* self._current_orders[key]['volume'] \
- price * self._current_orders[key]['volume'] * 1 / 1000 \
- (price - self._current_orders[key]['open_price']) \
* self._current_orders[key]['volume'] * 3 / 10000
self._history_orders[key] = self._current_orders.pop(key)
5 - 策略执行中调整买卖ma20的比例
- 买入比例调整为ma20 * 0.97
- 卖出比例调整为ma20 * 1.03
def strategy(self):
if self._is_new_bar:
sum_ = 0
for item in self._Close[1:21]:
sum_ = sum_ + item
self._ma20 = sum_ / 20
if 0 == len(self._current_orders):
if self._Close[0] < 0.97 * self._ma20:
volume = int(100000 / self._Close[0] / 100) * 100
self._buy(self._Close[0] + 0.01, volume)
elif 1 == len(self._current_orders):
if self._Close[0] > self._ma20 * 1.03:
key = list(self._current_orders.keys())[0]
self._sell(key, self._Close[0] - 0.01)
else:
raise ValueError("we have more then 1 current orders")
三、回测订单分析
1 - 订单打印数据
ast._current_orders:
{'order5': {'open_datetime': Timestamp('2020-11-30 13:25:00.800000'), 'open_price': 45.23, 'volume': 2200}}
-------------------------------------
ast._history_orders:
{
'order1': {'open_datetime': Timestamp('2020-02-03 09:30:00'), 'open_price': 32.79, 'volume': 3000, 'close_price': 29.56000000000001, 'close_datetime': Timestamp('2020-03-20 09:50:01'), 'pnl': -9775.77299999997},
'order2': {'open_datetime': Timestamp('2020-06-18 09:30:02.500000'), 'open_price': 32.129999999999995, 'volume': 3100, 'close_price': 35.25, 'close_datetime': Timestamp('2020-07-03 09:35:01'), 'pnl': 9559.823400000016},
'order3': {'open_datetime': Timestamp('2020-10-26 09:50:01.300000'), 'open_price': 40.12999999999999, 'volume': 2400, 'close_price': 41.41000000000003, 'close_datetime': Timestamp('2020-11-03 09:30:02.400000'), 'pnl': 2971.694400000105},
'order4': {'open_datetime': Timestamp('2020-11-13 09:40:00.700000'), 'open_price': 42.14, 'volume': 2300, 'close_price': 46.80000000000004, 'close_datetime': Timestamp('2020-11-30 09:30:03.200000'), 'pnl': 10607.144600000092}}
2 - matplotlib盈亏柱状图
3 - k线生成并投影订单进出时间价格
五、完整源码
import requests
from time import sleep
from datetime import datetime, time, timedelta
from dateutil import parser
import pandas as pd
import numpy as np
import os
import matplotlib.pyplot as plt
from mplfinance.original_flavor import candlestick_ohlc
from matplotlib.dates import date2num
def get_ticks_for_backtesting(tick_path, bar_path):
"""
:func: get ticks for backtesting, need two params
:param1 tick_path: 生成的回测数据路径
csv file with tick data,
when there is not tick data,
use bat_path to create tick data
example: "E:\\Downloads\\600036_data\\600036_ticks.csv"
:param2 bar_path: 历史数据的tick路径
csv file with bar data,
used in creating tick data
example: "E:\\Downloads\\600036_data\\600036_5m.csv"
:return: ticks in list with tuples in it, such as
[(datetime, last_price), (datetime, last_price)]
"""
if os.path.exists(tick_path):
ticks = pd.read_csv(
tick_path,
parse_dates=['datetime'],
index_col='datetime'
)
tick_list = []
for index, row in ticks.iterrows():
tick_list.append((index, row[0]))
ticks = np.array(tick_list)
else:
bar_5m = pd.read_csv(bar_path)
ticks = []
for index, row in bar_5m.iterrows():
if row['open'] < 30:
step = 0.01
elif row['open'] < 60:
step = 0.03
elif row['open'] < 90:
step = 0.05
else:
step = 0.1
arr = np.arange(row['open'], row['high'], step)
arr = np.append(arr, row['high'])
arr = np.append(arr, np.arange(row['open'] - step, row['low'], -step))
arr = np.append(arr, row['low'])
arr = np.append(arr, row['close'])
i = 0
dt = parser.parse(row['datetime']) - timedelta(minutes=5)
for item in arr:
ticks.append((dt + timedelta(seconds=0.1 * i), item))
i += 1
tick_df = pd.DataFrame(ticks, columns=['datetime', 'price'])
tick_df.to_csv(tick_path, index=0)
return ticks
class AstockTrading(object):
"""
:class: A stock trading platform, needs one param,
It has backtesting, paper trading, and real trading.
:param1: strategy_name: strategy_name
"""
def __init__(self, strategy_name):
self._strategy_name = strategy_name
self._Dt = []
self._Open = []
self._High = []
self._Low = []
self._Close = []
self._Volume = []
self._tick = []
self._last_bar_start_minute = None
self._is_new_bar = False
self._ma20 = None
self._current_orders = {}
self._history_orders = {}
self._order_number = 0
self._init = False
def get_tick(self):
"""
:func: for paper trading or real trading, not for backtesting
It goes to sina to get last tick info,
address is: https://hq.sinajs.cn/list=sh600519,
sh600519 can be changed
need to set headers Referer to: https://finance.sina.com.cn
A股的开盘时间是9:15,9:15-9:25是集合竞价 -> 开盘价,9:25
9:25-9:30不交易,时间>9:30,交易开始
start this method after 9:25
tick info is organized in tuple,
such as (trade_datetime, last_price),
tick info is save in self._tick.
:param: no param
:return: None
"""
headers = {'Referer': "https://finance.sina.com.cn"}
page = requests.get("https://hq.sinajs.cn/list=sh600519", headers=headers)
stock_info = page.text
mt_info = stock_info.replace("\"", "").split("=")[1].split(",")
last = float(mt_info[1])
trade_datetime = mt_info[30] + ' ' + mt_info[31]
self._tick = (trade_datetime, last)
def get_history_data_from_local_machine(self):
"""
:not done yet
:return:
"""
self._Open = []
self._High = []
self._Low = []
self._Close = []
self._Dt = []
def bar_generator(self):
"""
:not done yet
:how save and import history data?
:return:
"""
if self._tick[0].minute % 5 == 0 and self._tick[0].minute != self._last_bar_start_minute:
self._last_bar_start_minute = self._tick[0].minute
self._Open.insert(0, self._tick[1])
self._High.insert(0, self._tick[1])
self._Low.insert(0, self._tick[1])
self._Close.insert(0, self._tick[1])
self._Dt.insert(0, self._tick[0])
self._is_new_bar = True
else:
self._High[0] = max(self._High[0], self._tick[1])
self._Low[0] = max(self._Low[0], self._tick[1])
self._Close[0] = self._tick[1]
self._Dt[0] = self._tick[0]
self._is_new_bar = False
def _buy(self, price, volume):
"""
:method: create am order
:param1 price: buying price
:param2 volume: buying volume
:return: none
"""
self._order_number += 1
key = "order" + str(self._order_number)
self._current_orders[key] = {
"open_datetime": self._Dt[0],
"open_price": price,
"volume": volume
}
pass
def _sell(self, key, price):
"""
:method: close a long order, It needs two params
:param1 key: long order's key
:param2 price: selling price
:return:
"""
self._current_orders[key]['close_price'] = price
self._current_orders[key]['close_datetime'] = self._Dt[0]
self._current_orders[key]['pnl'] = \
(price - self._current_orders[key]['open_price']) \
* self._current_orders[key]['volume'] \
- price * self._current_orders[key]['volume'] * 1 / 1000 \
- (price - self._current_orders[key]['open_price']) \
* self._current_orders[key]['volume'] * 3 / 10000
self._history_orders[key] = self._current_orders.pop(key)
def strategy(self):
if self._is_new_bar:
sum_ = 0
for item in self._Close[1:21]:
sum_ = sum_ + item
self._ma20 = sum_ / 20
if 0 == len(self._current_orders):
if self._Close[0] < 0.97 * self._ma20:
volume = int(100000 / self._Close[0] / 100) * 100
self._buy(self._Close[0] + 0.01, volume)
elif 1 == len(self._current_orders):
if self._Close[0] > self._ma20 * 1.03:
key = list(self._current_orders.keys())[0]
self._sell(key, self._Close[0] - 0.01)
else:
raise ValueError("we have more then 1 current orders")
def bar_generator_for_backtesting(self, tick):
"""
:method: for backtesting only, used to update _Open, _ High, etc, It needs just one param
:param tick: tick info in tuple, (datetime, price)
:return:
"""
if tick[0].minute % 5 == 0 and tick[0].minute != self._last_bar_start_minute:
self._last_bar_start_minute = tick[0].minute
self._Open.insert(0, tick[1])
self._High.insert(0, tick[1])
self._Low.insert(0, tick[1])
self._Close.insert(0, tick[1])
self._Dt.insert(0, tick[0])
self._is_new_bar = True
else:
self._High[0] = max(self._High[0], tick[1])
self._Low[0] = max(self._Low[0], tick[1])
self._Close[0] = tick[1]
self._Dt[0] = tick[0]
self._is_new_bar = False
def run_backtestting(self, ticks):
"""
:method: ticks will be used to generate bars,
when bars is long enough, call strategy()
:param ticks: list with (datetime, price) in the list
:return: none
"""
for tick in ticks:
self.bar_generator_for_backtesting(tick)
if self._init:
self.strategy()
else:
if len(self._Open) >= 100:
self._init = True
self.strategy()
if __name__ == '__main__':
ticks = get_ticks_for_backtesting("E:\\Downloads\\600036_data\\600036_ticks.csv",
"E:\\Downloads\\600036_data\\600036_5m.csv")
ast = AstockTrading('ma')
ast.run_backtestting(ticks)
print('ast._current_orders:')
print(ast._current_orders)
print("-------------------------------------")
print('ast._history_orders:')
print(ast._history_orders)
profit_orders = 0
loss_orders = 0
orders = ast._history_orders
for key in orders.keys():
if orders[key]['pnl'] >= 0:
profit_orders += 1
else:
loss_orders += 1
win_rate = profit_orders / len(orders)
loss_rate = loss_orders / len(orders)
orders_df = pd.DataFrame(orders).T
orders_df.loc[:, 'pnl'].plot.bar()
plt.show()
bar5 = pd.read_csv("E:\\Downloads\\600036_data\\600036_5m.csv",
parse_dates=['datetime']
)
bar5.loc[:, 'datetime'] = [date2num(x) for x in bar5.loc[:, 'datetime']]
fig, ax = plt.subplots()
candlestick_ohlc(
ax,
quotes=bar5.values,
width=0.2,
colorup="r",
colordown='g',
alpha=1.0,
)
for index, row in orders_df.iterrows():
ax.plot(
[row['open_datetime'], row['close_datetime']],
[row['open_price'], row['close_price']],
color='darkblue',
marker='o',
)
plt.show()