"""
写这个模型花了小半天, 还没来得及测试, 先开源出来;
等期货回测器的cta分支没问题后,再测试该模型;
PS: 为了方便模型的回测, 价格设置上确实有不合理的地方, 勿用于实盘, 请见谅;
"""
from tqz_strategy.template import CtaTemplate
from public_module.object import StopOrder, TickData, BarData, TradeData, OrderData
from public_module.utility import BarGenerator
class TQZTurtleTradingStrategy(CtaTemplate):
author = "tqz"
# --- param part ---
donchian_channel_window = 20
fast_window = 10
slow_window = 60
clear_position_days_window = 10
n_window = 20
parameters = [
"fast_window",
"slow_window",
"donchian_channel_window",
"clear_position_days_window",
"n_window"
]
# --- var part ---
fast_ma0 = 0.0
fast_ma1 = 0.0
slow_ma0 = 0.0
slow_ma1 = 0.0
donchian_channel_up = 0.0
donchian_channel_down = 0.0
clear_position_level_price = 0 # profit
stop_loss_level_price = 0 # loss
variables = [
"fast_ma0",
"fast_ma1",
"slow_ma0",
"slow_ma1",
"donchian_channel_up",
"donchian_channel_down",
"stop_loss_level_price",
"clear_position_level_price",
]
def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
""""""
super().__init__(cta_engine, strategy_name, vt_symbol, setting)
self.bg = BarGenerator(self.on_bar)
self.history_bars = []
def on_init(self):
"""
Callback when strategy is inited.
"""
# self.write_log(msg=f'strategy_name: {self.strategy_name} on_init.')
pass
def on_start(self):
"""
Callback when strategy is started.
"""
# self.write_log(msg=f'strategy_name: {self.strategy_name} on_start.')
pass
def on_stop(self):
"""
Callback when strategy is stopped.
"""
# self.write_log(msg=f'strategy_name: {self.strategy_name} on_stop.')
pass
def on_tick(self, tick: TickData):
"""
Callback of new tick data update.
"""
self.bg.update_tick(tick)
def on_bar(self, bar: BarData):
"""
Callback of new bar data update.
"""
if self.__update_params_ok(new_bar=bar) is False:
return
# trend direction: turtle user single ma
long_market = bar.close_price > self.slow_ma0
short_market = bar.close_price < self.slow_ma0
lots = 1
if long_market:
if self.pos is 0:
if bar.high_price > self.donchian_channel_up:
self.set_position(position=lots, position_change_price=self.donchian_channel_up)
elif self.pos > 0:
if bar.low_price < self.clear_position_level_price:
self.set_position(position=0, position_change_price=self.clear_position_level_price)
elif bar.low_price < self.stop_loss_level_price and self.stop_loss_level_price is not 0:
self.set_position(position=0, position_change_price=self.stop_loss_level_price)
elif self.pos < 0:
self.set_position(position=0, position_change_price=bar.close_price)
elif short_market:
if self.pos is 0:
if bar.low_price < self.donchian_channel_down:
self.set_position(position=-lots, position_change_price=self.donchian_channel_down)
elif self.pos < 0:
if bar.high_price > self.clear_position_level_price:
self.set_position(position=0, position_change_price=self.clear_position_level_price)
elif bar.high_price > self.stop_loss_level_price and self.stop_loss_level_price is not 0:
self.set_position(position=0, position_change_price=self.stop_loss_level_price)
elif self.pos > 0:
self.set_position(position=0, position_change_price=bar.close_price)
def __update_params_ok(self, new_bar: BarData) -> bool:
if len(self.history_bars) < self.slow_window:
self.history_bars.append(new_bar)
return False
first_bar = self.history_bars[0]
self.history_bars.remove(first_bar)
self.history_bars.append(new_bar)
# fast ma value
tmp_fast_ma0 = 0
for bar in self.history_bars[-self.fast_window:]:
tmp_fast_ma0 += bar.close_price
tmp_fast_ma1 = 0
for bar in self.history_bars[-self.fast_window-1:-1]:
tmp_fast_ma1 += bar.close_price
self.fast_ma0 = tmp_fast_ma0 / self.fast_window
self.fast_ma1 = tmp_fast_ma1 / self.fast_window
# slow ma value
tmp_slow_ma0 = 0
for bar in self.history_bars:
tmp_slow_ma0 += bar.close_price
tmp_slow_ma1 = 0
for bar in self.history_bars[:-1]:
tmp_slow_ma1 += bar.close_price
tmp_slow_ma1 += first_bar.close_price
self.slow_ma0 = tmp_slow_ma0 / self.slow_window
self.slow_ma1 = tmp_slow_ma1 / self.slow_window
# donchian up & down value
self.donchian_channel_up, self.donchian_channel_down = self.__get_new_donchian_value()
# clear_position_level_price
self.clear_position_level_price = self.__get_clear_position_level_price()
# stop_loss_level_price
if self.pos > 0:
self.stop_loss_level_price = self.position_change_price - self.__get_n_value() * 2
elif self.pos < 0:
self.stop_loss_level_price = self.position_change_price + self.__get_n_value() * 2
return True
def __get_n_value(self):
n_value = 0
if self.pos is not 0:
n_values = []
for index, bar in enumerate(self.history_bars):
if index > len(self.history_bars) - self.n_window - 1:
pre_bar = self.history_bars[index - 1]
tr = max(bar.high_price - bar.close_price, bar.high_price - pre_bar.close_price, pre_bar.close_price - bar.low_price)
if len(n_values) is 0:
n_values.append(tr)
else:
n_values.append((n_values[-1] * (self.n_window - 1) + tr) / self.n_window)
n_value = n_values[-1]
return n_value
def __get_clear_position_level_price(self):
tmp_clear_position_level_price = 0
if self.pos is not 0:
for bar in self.history_bars[-self.clear_position_days_window:]:
if self.pos > 0:
if bar.low_price < tmp_clear_position_level_price or tmp_clear_position_level_price is 0:
tmp_clear_position_level_price = bar.low_price
elif self.pos < 0:
if bar.high_price > tmp_clear_position_level_price or tmp_clear_position_level_price is 0:
tmp_clear_position_level_price = bar.high_price
return tmp_clear_position_level_price
def __get_new_donchian_value(self):
tmp_donchian_channel_up = 0
tmp_donchian_channel_down = 0
for bar in self.history_bars[-self.donchian_channel_window:]:
if bar.high_price > self.donchian_channel_up or tmp_donchian_channel_up is 0:
tmp_donchian_channel_up = bar.high_price
if bar.low_price < self.donchian_channel_down or tmp_donchian_channel_down is 0:
tmp_donchian_channel_down = bar.low_price
return tmp_donchian_channel_up, tmp_donchian_channel_down
def set_position(self, position: int, position_change_price: float):
super().set_position(position=position, position_change_price=position_change_price)
if self.pos > 0:
self.stop_loss_level_price = position_change_price - 2 * self.__get_n_value()
elif self.pos < 0:
self.stop_loss_level_price = position_change_price + 2 * self.__get_n_value()
else:
self.stop_loss_level_price = 0
# useless defination
def on_order(self, order: OrderData):
"""
Callback of new order data update.
"""
pass
def on_trade(self, trade: TradeData):
"""
Callback of new trade data update.
"""
pass
def on_stop_order(self, stop_order: StopOrder):
"""
Callback of stop order update.
"""
pass
量化交易之回测篇 - 海龟交易策略(初版)
于 2022-03-10 20:56:14 首次发布