教程
第一个策略

准备工作

工欲善其事,必先利其器,我们前面铺垫了这么多,终于来到编写策略代码的环节了,但是别急,我们还需要一个趁手的代码编辑器,编辑器可以帮我们检查在代码编写阶段出现的 BUG,可以帮我们统一代码的风格,还可以帮我们快速补全代码,让写代码效率更上一层楼。

我们推荐的编辑器是:Visual Studio Code 点击下载 (opens in a new tab)

安装完成之后,我们还需要下载以下扩展:

依次点击下载链接后,会打开对应的下载页面,我们看到「右侧侧边栏」,找到 「Resources」,点击 「Download Extension」,会开始下载扩展,扩展的文件后缀名是 .vsix

当我们把扩展下载好之后,会得到四个扩展文件,现在打开编辑器,点击左侧侧边栏「扩展」按钮,再点击「三个点」按钮,在菜单中选择「Install From VISX...」,中文则是「从 VISX 中安装...」,选择我们刚下载的四个扩展文件后点击「Install」,等待安装完成即可

注意:如果你打不开下载页面,或者觉得操作困难,可以跳过安装扩展的步骤,这些扩展只是让编辑器更好的支持 Python 语言,但不是必须的。

编写策略

我们知道如何加载策略后,也大体了解了一下运行原理,编辑器也准备好了,现在终于开始编写我们第一个策略了。

工作目录

我们首先需要用编辑器打开工作目录:

点击「策略管理」按钮后点击「文件夹」按钮打开策略路径,此时是位于 self_strategy 目录(策略路径)下,我们需要往上两级,按住 Alt + 上方向键 两次,进入以 InfiniTrader 开头的目录,比如:InfiniTrader_Simulation,此时进入了无限易的主目录,我们可以在这个目录下看到 pyStrategy 文件夹,这是 PythonGO 整体的工作目录,右键该目录

  • 如果是 Windows 10:在弹出的菜单中点击「通过 Code 打开」

  • 如果是 Windows 11:在弹出的菜单中点击「显示更多选项」,再点击「通过 Code 打开」

  • 如果没有「通过 Code 打开」,则自行打开编辑器后,把 pyStrategy 文件夹拖入到编辑器中

  • 之后的开发会一直在该工作目录中进行。

    创建策略

    在左侧文件栏中选中 self_strategy 文件夹,再点击「新建文件」按钮,在出现的输入框中输入策略文件名,这里我们取名 DemoTest.py(注意不要和自带的策略重名)

    当然你也可以直接在 self_strategy 文件夹直接右键新建 .py 策略文件

    编写代码

    现在我们来写一个最简单的一旦运行策略就按照无限易 PythonGO 窗口的参数下单的策略

    定义参数映射

    参数映射,是指可以通过无限易 PythonGO 窗口参数栏对策略的参数进行修改的映射操作

    现阶段,我们的参数映射都是通过 pydantic 这个强大的数据验证库,它可以根据我们定义的类型注释严格控制数据类型,在我们写代码的过程中就可以感受到该库的强大之处。

    我们通过一个「参数映射模型」即可完成映射

    DemoTest.py
    from typing import Literal
     
    # 从 base 库中导入定义参数和状态映射模型必须的三个方法
    from pythongo.base import BaseParams, BaseState, Field
     
     
    class Params(BaseParams):
        """参数映射模型"""
        exchange: str = Field(default="", title="交易所代码")
        instrument_id: str = Field(default="", title="合约代码")
        order_price: int | float = Field(default=0, title="报单价格")
        order_volume: int = Field(default=1, title="报单手数")
        order_direction: Literal["buy", "sell"] = Field(default="buy", title="报单方向")

    上面的代码表示:

    • 我们自己的参数映射模型叫做 Params,继承自 BaseParams

    • 我们想从无限易 PythonGO 窗口参数栏传入定义的这 5 个值(或者说修改 5 个值)

    • 类型注释在参数映射模型中是一定要写的,所有的值类型都会按照定义的类型注释被解析和自动转换,其中 Literal 的作用是表示这个变量只允许使用定义好的字面值,当前代码表示 order_direction 只允许使用 buy 或者 sell 这两个字符串

    • Field 用来自定义元数据并将其添加到参数映射模型的字段中

    • default(必填)定义这个参数默认值,相当于 self.exchange = ""

    • title(必填)定义这个参数的中文名,会在无限易 PythonGO 窗口显示

    效果如图所示:

    定义状态映射

    状态映射,是指可以通过无限易 PythonGO 窗口状态栏对策略的参数或变量进行实时查看的映射操作(如没该需求可以不定义)

    定义方法和参数映射是一样的,通过一个「状态映射模型」即可完成映射

    DemoTest.py
    # 从 base 库中导入定义参数和状态映射模型必须的三个方法
    from pythongo.base import BaseParams, BaseState, Field
     
     
    class State(BaseState):
        """状态映射模型"""
        order_id: int | None = Field(default=None, title="报单编号")

    上面的代码表示:

    • 我们自己的状态映射模型叫做 State,继承自 BaseState

    • 我们想从无限易 PythonGO 窗口状态栏查看报单编号(order_id)的值(注意:这个报单编号需要我们自己赋值,和报单成功后的返回的报单编号没有主动映射关系,仅仅是同名,你也可以定义为任意名称)

    • 其他和参数映射说明一样

    效果如图所示:

    定义策略类

    当我们把映射模型定义好之后,就要开始写策略类了

    根据规范 策略规范 - 导入父类 导入我们需要的父类基础策略模版 BaseStrategy,然后基于我们之前学过的知识 运行原理 - 加载策略,我们知道了「策略类名」要和「策略文件名」保持一致:

    DemoTest.py
    # 从 base 库中导入基础策略模版 BaseStrategy 
    from pythongo.base import BaseParams, BaseState, BaseStrategy, Field
     
     
    class DemoTest(BaseStrategy):
        """我的第一个策略"""
        ...

    根据 PythonGO 代码规范,我们还需要给策略写一个函数文档字符串,来告诉无限易这个策略的描述

    效果如图所示:

    实例化映射模型

    上面定义好了两个映射模型,也定义好了策略类,现在我们把映射模型实例化,作为策略的实例属性,这样才可以在策略中获取到定义的映射数据

    DemoTest.py
    from typing import Literal
     
    from pythongo.base import BaseParams, BaseState, BaseStrategy, Field
     
     
    class Params(BaseParams):
        """参数映射模型"""
        exchange: str = Field(default="", title="交易所代码")
        instrument_id: str = Field(default="", title="合约代码")
        order_price: int | float = Field(default=0, title="报单价格")
        order_volume: int = Field(default=1, title="报单手数")
        order_direction: Literal["buy", "sell"] = Field(default="buy", title="报单方向")
     
     
    class State(BaseState):
        """状态映射模型"""
        order_id: int | None = Field(default=None, title="报单编号")
     
     
    class DemoTest(BaseStrategy):
        """我的第一个策略"""
        def __init__(self) -> None:
            super().__init__()
            self.params_map = Params()
            self.state_map = State()

    定义回调

    在 PythonGO 的父类基础策略模版中,以 on 开头的函数都是回调函数

    运行原理 中,我们大致的了解了回调函数,那在当前的示例中我们只需要用到三个回调函数,根据 策略规范 - 回调函数 按顺序定义如下:

    on_start()

    查看启动策略回调定义

    我们的策略的逻辑是一旦运行就按照无限易 PythonGO 窗口的参数下单,那下单函数就应该写在本回调中

    我们使用 报单函数 - send_order 来进行报单操作,对应的报单数据我们从参数 self.params_map 中获取,得到的报单编号,我们赋值给状态映射模型中的 order_id,然后需要调用 update_status_bar 函数才可以刷新状态栏的值:

    DemoTest.py
    def on_start(self) -> None:
        super().on_start()
     
        self.state_map.order_id = self.send_order(
            exchange=self.params_map.exchange,
            instrument_id=self.params_map.instrument_id,
            volume=self.params_map.order_volume,
            price=self.params_map.order_price,
            order_direction=self.params_map.order_direction
        )
     
        self.update_status_bar()

    update_status_bar 函数定义

    on_stop()

    查看启动策略回调定义

    在暂停回调中,我们输出一句自定义信息

    DemoTest.py
    def on_stop(self) -> None:
        super().on_stop()
        self.output("我的第一个策略暂停了")
    on_order()

    查看报单变化回调定义

    在报单变化回调中,我们只需要输出看看报单后回调收到的报单数据(OrderData)信息。要注意的是,参数 order 是一个实例对象,不是字典对象,所以他的取值是用 . 取值的,比如 order.exchange,但是我们直接输出 order,显示的内容确是字典,这是因为我们对这个数据对象做了处理,如果没做处理的话,看到的是内存地址 <order.OrderData at 0x96b06c0>

    DemoTest.py
    from pythongo.classdef import OrderData
     
    def on_order(self, order: OrderData) -> None:
        super().on_order(order)
        self.output("报单信息:", order)

    完整代码

    是的,一个简单的策略就完成了,代码不多,希望你可以仔细阅读这些代码

    现在这个策略会根据你在无限易 PythonGO 窗口填写的参数信息(交易所代码,合约代码,报单价格,报单手数,报单方向)来进行报单,最终的代码是这样:

    DemoTest.py
    from typing import Literal
     
    from pythongo.base import BaseParams, BaseState, BaseStrategy, Field
    from pythongo.classdef import OrderData
     
     
    class Params(BaseParams):
        """参数映射模型"""
        exchange: str = Field(default="", title="交易所代码")
        instrument_id: str = Field(default="", title="合约代码")
        order_price: int | float = Field(default=0, title="报单价格")
        order_volume: int = Field(default=1, title="报单手数")
        order_direction: Literal["buy", "sell"] = Field(default="buy", title="报单方向")
     
     
    class State(BaseState):
        """状态映射模型"""
        order_id: int | None = Field(default=None, title="报单编号")
     
     
    class DemoTest(BaseStrategy):
        """我的第一个策略"""
        def __init__(self) -> None:
            super().__init__()
            self.params_map = Params()
            self.state_map = State()
     
        def on_order(self, order: OrderData) -> None:
            super().on_order(order)
            self.output("报单信息:", order)
     
        def on_start(self) -> None:
            super().on_start()
     
            self.state_map.order_id = self.send_order(
                exchange=self.params_map.exchange,
                instrument_id=self.params_map.instrument_id,
                volume=self.params_map.order_volume,
                price=self.params_map.order_price,
                order_direction=self.params_map.order_direction
            )
     
            self.update_status_bar()
     
        def on_stop(self) -> None:
            super().on_stop()
            self.output("我的第一个策略暂停了")

    然后按照 操作入门 来加载我们刚写的第一个策略即可,快去试一试!