风格指南
策略规范

过去我们的框架没有一个统一的代码规范,导致每个人的代码都长得不一样,现在新版本不仅规范了框架代码,还需要规范策略代码。

导入父类

通过把 K 线图相关代码拆分出去,我们得到了两个基础策略模版类:

  • pythongo.base.BaseStrategy
  • pythongo.ui.BaseStrategy

pythongo.base

本模块定义了完整的策略模版,也封装了从无限易取数据的方法

当我们的策略完全不需要用到 K 线图的时候则导入本模块中的 BaseStrategy,并继承该类

from pythongo.base import BaseStrategy
 
class Demo(BaseStrategy):
    ...

pythongo.ui

本模块继承自上面 pythongo.base 中的 BaseStrategy,但额外包含了所有的 K 线图代码以及处理逻辑

当我们的策略需要使用到 K 线图的时候则导入本模块中的 BaseStrategy,并继承该类

from pythongo.ui import BaseStrategy
 
class Demo(BaseStrategy):
    ...

该类会自动处理 K 线图初始化等相关操作,使得所有的策略代码不再需要手动处理线图的启动、显示、关闭等操作

初始化

自定义变量

很多时候我们需要定义一些自定义变量,用来实现策略更复杂的操作,这些变量就是实例变量,我们和正常写代码一样,直接定义在 self.__init__ 中,对于默认值为 None 或者为对象的变量,应该写上类型注释

class Demo(BaseStrategy):
    def __init__(self) -> None:
        super().__init__()
 
        # 自定义变量
        self.global_var_name: str | None = None
        self.global_var_int = 1
        self.test_string = ""

线图指标定义

当我们使用 K 线图组件时,需要定义线图上显示的指标,现在我们有了更方便的定义方法

@property 装饰器可以把一个类方法装饰成类属性,在我们定义线图显示指标的时候,使用这个装饰器再合适不过了,因为每次调用这个属性,都会去使用最新的指标数据,而不需要自己对这个属性做任何赋值操作

下面定义的两个属性,是以方法来定义的,但可以当做属性调用,所以我们可以实现更进阶的操作

主图指标

主图指标使用属性名 main_indicator_data 定义

from pythongo.ui import BaseStrategy
 
class Demo(BaseStrategy):
    def __init__(self) -> None:
        super().__init__()
 
        self.fast_ma = 0.0
        self.slow_ma = 0.0
 
    @property
    def main_indicator_data(self) -> dict[str, float]:
        """主图指标"""
        return {
            "MA0": self.fast_ma,
            "MA1": self.slow_ma,
        }

副图指标

副图指标使用属性名 sub_indicator_data 定义

from pythongo.ui import BaseStrategy
 
class Demo(BaseStrategy):
    def __init__(self) -> None:
        super().__init__()
 
        self.k = 0.0
        self.d = 0.0
        self.j = 0.0
 
    @property
    def sub_indicator_data(self) -> dict[str, float]:
        """副图指标"""
        return {
            "K": self.k,
            "D": self.d,
            "J": self.j,
        }

进阶操作

在上面定义指标的例子中,KDJ 指标还好,如果是 MA 指标,我们软件一般都会显示为 MA10MA20 这种带周期的名称,但是在策略中,我们已经把指标名称定义为 MA0MA1,且不会再更改了

这就导致即使我们更改了指标的周期,线图上显示的指标名字还是不会变,我们不能很清楚的知道现在显示的是什么周期的指标

这个时候装饰器就起作用了,还记得我们之前说的:「装饰器可以把一个类方法装饰成类属性」吗?既然我们定义的不是属性,而是方法,那就可以在方法里实现更新指标名称的操作了

from pythongo.base import BaseParams, Field
from pythongo.ui import BaseStrategy
 
 
class Params(BaseParams):
    """参数映射模型"""
    fast_period: int = Field(default=5, title="快均线周期", ge=2)
    slow_period: int = Field(default=20, title="慢均线周期")
 
 
class Demo(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,
        }

这样,从 PythonGO 窗口传进来的快均线周期和慢均线周期就会在 self.main_indicator_data 调用后转成一个新的字典:

{
    "MA5": 0.0,
    "MA20": 0.0
}

其中 520 是取的参数映射模型中 self.params_map.fast_periodself.params_map.slow_period 的值,我们通过 f-string 配合 @property 装饰器,实现了在更改指标周期后,线图上显示的指标名是 MA + 更改后的指标周期

回调函数

在自己编写策略的时候,回调函数应该按照以下顺序定义,使用不到的回调函数允许不定义

from pythongo.base import BaseStrategy
 
class Demo(BaseStrategy):
    def __init__(self) -> None:
        super().__init__()
 
    def on_init(self, ...) -> None:
        super().on_init(...)
 
    def on_start(self, ...) -> None:
        super().on_start(...)
 
    def on_stop(self, ...) -> None:
        super().on_stop(...)
 
    def on_tick(self, ...) -> None:
        super().on_tick(...)
 
    def on_contract_status(self, ...) -> None:
        super().on_contract_status(...)
 
    def on_order(self, ...) -> None:
        super().on_order(...)
 
    def on_order_cancel(self, ...) -> None:
        super().on_order_cancel(...)
 
    def on_order_trade(self, ...) -> None:
        super().on_order_trade(...)
 
    def on_trade(self, ...) -> None:
        super().on_trade(...)
 
    def on_error(self, ...) -> None:
        super().on_error(...)

注意:所有回调函数,一定要使用 super() 来调用父类的回调函数

函数调用

调用函数时,如果函数的参数超过两个,则需要在入参的时候填写参数名,这点在代码规范中已经强调了

def foo(a: int, b: int, c: int) -> int:
    return a + b + c
 
# 正确
foo(a=1, b=2, c=3)
 
# 错误
foo(1, 2, 3)