教程
第二个策略

请注意,本文将不会和前面文章一样写的很详细,一些已经提过的细节将可能不会再提起,如果你对一些代码还有疑惑,建议你仔细阅读「教程」中的全部文档

现在我们已经有了一些编写策略的基础了,准备开始写第二个策略,这个策略我们将使用到 K 线图,并在 K 线图上显示 MA 主图指标数据

编写代码

定义参数映射

我们定义以下 5 个参数,方便我们在无限易 PythonGO 窗口控制 K 线的周期,指标周期...

注意 fast_period,我们在 Field 中用了一个 ge 参数,这个参数的意思是大于或者等于(greater than or equal to)设置的值,我们设置 2,表示 fast_period 只接受大于等于 2 的数字

DemoKLine.py
class Params(BaseParams):
    """参数映射模型"""
    exchange: str = Field(default="", title="交易所代码")
    instrument_id: str = Field(default="", title="合约代码")
    fast_period: int = Field(default=5, title="快均线周期", ge=2)
    slow_period: int = Field(default=20, title="慢均线周期")
    kline_style: KLineStyleType = Field(default="M1", title="K 线周期")

定义实例变量

我们需要实例化参数映射模型,然后定义两个存储快均线和慢均线值的实例变量

DemoKLine.py
from pythongo.ui import BaseStrategy
 
 
class DemoKLine(BaseStrategy):
    """我的第二个策略"""
    def __init__(self) -> None:
        super().__init__()
        self.params_map = Params()
 
        self.fast_ma = 0.0
        self.slow_ma = 0.0

线图指标定义

详细的文档请看 策略规范 - 线图指标定义

DemoKLine.py
from pythongo.ui import BaseStrategy
 
 
class DemoKLine(BaseStrategy):
    ...
 
    @property
    def main_indicator_data(self) -> dict[str, float]:
        """主图指标"""
        return {
            f"MA{self.params_map.fast_period}": self.fast_ma,
            f"MA{self.params_map.slow_period}": self.slow_ma,
        }

定义回调

on_start()

我们需要在运行策略的时候定义一个 K 线合成器 KLineGenerator,它可以帮助我们获取历史数据,并合成 K 线

push_history_data 是推送历史数据的方法,如果不调用,则不会推送历史数据到 callback 函数中

这里还需要注意一点,定义 K 线合成器和推送历史数据的代码,要写在 super().on_start() 前面,这是因为父类的 on_start() 会设置 self.tradingTrue,这样一来可能会导致我们推送的历史数据触发交易信号,然后报单,如果我们写在父类的 on_start() 前面,则没有这个问题,这也是比较特殊的一点

DemoKLine.py
from pythongo.ui import BaseStrategy
from pythongo.utils import KLineGenerator
 
 
class DemoKLine(BaseStrategy):
    ...
 
    def on_start(self) -> None:
        self.kline_generator = KLineGenerator(
            real_time_callback=self.real_time_callback,
            callback=self.callback,
            exchange=self.params_map.exchange,
            instrument_id=self.params_map.instrument_id,
            style=self.params_map.kline_style
        )
        self.kline_generator.push_history_data()
        super().on_start()

参考文档:

on_tick()

on_tick 是最常用的回调,它可以收到无限易推送过来的行情 tick,有了 tick 数据我们才可以进行一系列操作

在当前例子中,我们需要在 on_tick 回调中将 tick 数据传给「K 线合成器」的 tick_to_kline 方法(K 线合成器 - tick_to_kline)来合成 K 线

DemoKLine.py
from pythongo.ui import BaseStrategy
from pythongo.classdef import TickData
 
 
class DemoKLine(BaseStrategy):
    ...
 
    def on_tick(self, tick: TickData) -> None:
        """收到行情 tick 推送"""
        super().on_tick(tick)
        self.kline_generator.tick_to_kline(tick)

自定义函数

calc_indicator()

既然我们想在 K 线图上显示指标数据,那我们就得写一个计算指标的函数,然后对定义好的两个实例变量 fast_ma, slow_ma 赋值

我们定义的 K 线合成器 self.kline_generatorproducer(K 线生产器)属性,而 K 线生产器继承了技术指标类,所以可以写出以下计算指标代码,我们使用 sma 指标来计算两个周期的指标

DemoKLine.py
from pythongo.ui import BaseStrategy
 
 
class DemoKLine(BaseStrategy):
    ...
 
    def calc_indicator(self) -> None:
        """计算指标数据"""
        self.slow_ma = self.kline_generator.producer.sma(
            timeperiod=self.params_map.slow_period
        )
 
        self.fast_ma = self.kline_generator.producer.sma(
            timeperiod=self.params_map.fast_period
        )

参考文档:

callback()

推送 K 线回调

on_start() 中,我们要把这个函数传给 K 线合成器(KLineGenerator)的 callback 参数,然后当合成器得到一根我们预设的周期的 K 线时,就会调用这个回调函数,并且把这根 K 线传过来,我们就能得到最新的一根 K 线,当然,历史数据的 K 线也是从这个回调中传入

在这个函数中,因为得到了新的一根 K 线,我们需要调用上面的计算指标数据函数 self.calc_indicator() 来计算最新的指标,然后调用 self.widgetrecv_kline() 方法来接受 K 线和指标数据(** 代表解包),将其更新到 K 线图上

是不是很奇怪,这个 self.widget 哪来的?

原因很简单,还记得我们之前说,用 K 线图的话,就要从 pythongo.ui 中导入基础父类模版吗?我们在 ui 模块的父类模版中添加了这些专用于 K 线图的属性,self.widget 就来自于那里,所以就不用自己定义了

DemoKLine.py
from pythongo.classdef import KLineData
from pythongo.ui import BaseStrategy
 
 
class DemoKLine(BaseStrategy):
    ...
 
    def callback(self, kline: KLineData) -> None:
        """接受 K 线回调"""
        self.calc_indicator()
 
        self.widget.recv_kline({
            "kline": kline,
            **self.main_indicator_data
        })

参考文档:

real_time_callback()

实时推送 K 线回调, 推送频率和 tick 相同

当 K 线有更新,这个回调函数就会被调用,我们可以在里面计算实时指标,或者根据这个频率的 K 线来实现一些高频的操作

在这里,我们仅需计算指标和把 K 线数据更新到线图上,所以我们直接调用 callback(),免得写两份一样的代码了

DemoKLine.py
from pythongo.classdef import KLineData
from pythongo.ui import BaseStrategy
 
 
class DemoKLine(BaseStrategy):
    ...
 
    def real_time_callback(self, kline: KLineData) -> None:
        """使用收到的实时推送 K 线来计算指标并更新线图"""
        self.callback(kline)

完整代码

这样,一个简单的带指标的 K 线图策略就完成了

我们可以通过无限易 PythonGO 窗口修改指标周期或者 K 线周期,启动策略后就能看到对应周期的 K 线图了

对了,在我们这个策略中,没有定义状态映射模型(State),这是允许的,如果策略不需要在状态栏显示数据,则无需定义该模型

K 线支持的类型,看这里:深入框架 - pythongo.core - KLineStyle

DemoKLine.py
from pythongo.base import BaseParams, Field
from pythongo.classdef import KLineData, TickData
from pythongo.core import KLineStyleType
from pythongo.ui import BaseStrategy
from pythongo.utils import KLineGenerator
 
 
class Params(BaseParams):
    """参数映射模型"""
    exchange: str = Field(default="", title="交易所代码")
    instrument_id: str = Field(default="", title="合约代码")
    fast_period: int = Field(default=5, title="快均线周期", ge=2)
    slow_period: int = Field(default=20, title="慢均线周期")
    kline_style: KLineStyleType = Field(default="M1", title="K 线周期")
 
 
class DemoKLine(BaseStrategy):
    """我的第二个策略"""
    def __init__(self) -> None:
        super().__init__()
        self.params_map = Params()
 
        self.fast_ma = 0.0
        self.slow_ma = 0.0
 
    @property
    def main_indicator_data(self) -> dict[str, float]:
        """主图指标"""
        return {
            f"MA{self.params_map.fast_period}": self.fast_ma,
            f"MA{self.params_map.slow_period}": self.slow_ma,
        }
 
    def on_start(self) -> None:
        self.kline_generator = KLineGenerator(
            real_time_callback=self.real_time_callback,
            callback=self.callback,
            exchange=self.params_map.exchange,
            instrument_id=self.params_map.instrument_id,
            style=self.params_map.kline_style
        )
        self.kline_generator.push_history_data()
        super().on_start()
 
    def on_stop(self) -> None:
        super().on_stop()
 
    def on_tick(self, tick: TickData) -> None:
        """收到行情 tick 推送"""
        super().on_tick(tick)
        self.kline_generator.tick_to_kline(tick)
 
    def calc_indicator(self) -> None:
        """计算指标数据"""
        self.slow_ma = self.kline_generator.producer.sma(
            timeperiod=self.params_map.slow_period
        )
 
        self.fast_ma = self.kline_generator.producer.sma(
            timeperiod=self.params_map.fast_period
        )
 
    def callback(self, kline: KLineData) -> None:
        """接受 K 线回调"""
        self.calc_indicator()
 
        self.widget.recv_kline({
            "kline": kline,
            **self.main_indicator_data
        })
 
    def real_time_callback(self, kline: KLineData) -> None:
        """使用收到的实时推送 K 线来计算指标并更新线图"""
        self.callback(kline)