Article Blog Image

从Strategy101开始学习

策略

在本文中我们将引导您创建第一个交易策略:Strategy101策略。它会周期性进行开仓、等待、平仓操作,并且还演示了如何使用State、Timer、Parameter、Metrics等基本功能。

Strategy101策略实现了一个循环下单的逻辑:

  • 1.下单开仓
  • 2.等待20秒
  • 3.平仓并重复上述逻辑

与此同时考虑到报单成交超时以及报单错误的情况,实现自动撤单并且重新报单。虽然现实中不存在这样的交易逻辑,但是可以作为一个很好的教学示例,这个例子足以展示如何基于有限状态自动机以及Timer功能实现自动化交易。策略将会接入2个目标合约,并在自定义看板上试试检测合约各自的价格与成交量,以及两个合约价格相减形成的套利价差。同时,策略还支持发送人工干预信号,进行开仓、平仓、定时器的创建与撤销等操作。

定义策略参数

策略定义了四个参数,abc以及enable_order_test分别为int、double、string类型。其中abc三个参数仅作为示例,并没有在策略中实际用到,而enable_order_test如果被设置为1则策略会开启”开仓-等待-平仓”的循环自动交易逻辑。在Native策略中参数可直接通过相关指令进行定义:

BEGIN_PARAMETER_BIND
    INT_PARAMETER("a", "parameter a is an int64_t", 5)
    DOUBLE_PARAMETER("b", "parameter b is a double", 0.2)
    STRING_PARAMETER("c", "parameter c is a std::string", "1,2,3,4")
    INT_PARAMETER("enable_order_test", "Make test order automatically if value is 1", 0)
END_PARAMETER_BIND

在策略中可以通过context来读取参数实际值:

ctx->logging(severity_levels::info, "enable_order_test=%d", ctx->int_parameter("enable_order_test"));

在Remote策略中通过返回JSON的方式定义参数,这里以python为例:

def get_parameter_meta(self):
    return {
         "a": super().int_parameter("parameter a is an int64_t", 5),
         "b": super().double_parameter("parameter b is a double", 0.2),
        "c": super().string_parameter("parameter c is a string", "1,2,3,4"),
        "enable_order_test": super().int_parameter("Make test order automatically if value is 1", "0")
     }

而参数的值会携带在on_deployment回调接口的请求体中。 当您完成上述参数定义后,在engine的策略部署界面可以看到它们: thunder-engine-native-deploy-2.png

创建自定义指标看板

在策略实盘交易过程中,我们需要能够对一些关键指标进行实时监控,方便判断策略当前执行是否符合预期。Strategy101策略支持对2个slot进行监控,我们在监控面板中同时配置每个slot的价格、成交量、盘口量、当前持仓、slot1与slot2的价差以及当前策略的盈利情况。

strategy101-metrics.jpg

首先定义所有指标的变量,类型需要为std::atomic<double>

std::atomic<double> ask_price[2] = {NAN, NAN};
std::atomic<double> bid_price[2] = {NAN, NAN};
std::atomic<double> last_price[2] = {NAN, NAN};
...
std::atomic<double> unrealized_profit[2] = {NAN, NAN};
std::atomic<double> sum_realized_profit = NAN;
std::atomic<double> sum_unrealized_profit = NAN;

然后我们需要用对指标进行注册与绑定:

BEGIN_DASHBOARD
    BEGIN_PANEL
        METRIC(&ask_price[0], "ask_price_0", color_t::GREEN)
        METRIC(&bid_price[0], "bid_price_0", color_t::BLUE)
        METRIC(&last_price[0], "last_price_0", color_t::RED)
        METRIC_EX(&long_position[0], "long_position", color_t::ORANGE, 0.8)
        METRIC_EX(&short_position[0], "short_position", color_t::POWDERBLUE, 0.8)
    END_PANEL(R"({"row":[0], "column":[0]})")
    ...
    BEGIN_PANEL
        METRIC(&unrealized_profit[0], "unrealized_profit_0", color_t::GREEN)
        METRIC(&unrealized_profit[1], "unrealized_profit_1", color_t::BLUE)
        METRIC(&realized_profit[0], "realized_profit_0", color_t::GREEN)
        METRIC(&realized_profit[1], "realized_profit_1", color_t::BLUE)
        METRIC(&sum_realized_profit, "sum_realized_profit", color_t::ORANGE)
        METRIC(&sum_unrealized_profit, "sum_unrealized_profit", color_t::POWDERBLUE)
    END_PANEL(R"({"row":[2], "column":[0]})")
END_DASHBOARD

由于篇幅限制,上述代码示例我们做了一些省略,您可以在DevelStudio/strategy101目录下看到全部代码定义。同样,Remote策略也可以定义监控指标,这里以Python为例:

def get_dashboard_meta(self):
        return [
            {
                "metrics": [
                    {"name": "ask_price_0", "color": "GREEN", "alpha": 1.0, "axis": "LEFT"},
                    {"name": "bid_price_0", "color": "BLUE", "alpha": 1.0, "axis": "LEFT"},
                    {"name": "last_price_0", "color": "RED", "alpha": 1.0, "axis": "LEFT"},
                    {"name": "long_position", "color": "ORANGE", "alpha": 0.8, "axis": "LEFT"},
                    {"name": "short_position", "color": "POWDERBLUE", "alpha": 0.8, "axis": "LEFT"},
                ],
                "layout": {"row": [0, 0], "column": [0, 0]},
            },
            ...
            {
                "metrics": [
                    {"name": "unrealized_profit_0", "color": "GREEN", "alpha": 1.0, "axis": "LEFT"},
                    {"name": "unrealized_profit_1", "color": "BLUE", "alpha": 1.0, "axis": "LEFT"},
                    {"name": "realized_profit_0", "color": "GREEN", "alpha": 1.0, "axis": "LEFT"},
                    {"name": "realized_profit_1", "color": "BLUE", "alpha": 1.0, "axis": "LEFT"},
                    {"name": "sum_realized_profit", "color": "ORANGE", "alpha": 1.0, "axis": "LEFT"},
                    {"name": "sum_unrealized_profit", "color": "POWDERBLUE", "alpha": 1.0, "axis": "LEFT"},
                ],
                "layout": {"row": [2, 2], "column": [0, 0]},
            }
        ]

完成监控指标配置有,您可以在engine的控制面板中看到相关指标的展示:

thunder-engine-strategy-dashboard.png

同样,在策略仿真环境中,您同样可以绘制策略仿真生成的监控指标:

develstudio-strategy-metric-plot.png

人工干预信号

我们可以预定义一些干预指令,在交易过程中如果遇到特殊情况,可以通过这些干预指令对策略的行为进行人工干预。例如调整指标、开关等等。这里我们定义5个指令:

指令 参数 描述
increase slot以及开仓方向 执行开仓指令
decrease slot 如果当前有仓位,则执行平仓指令
show_status 显示策略状态
test_create_timer 定时器延迟时间 创建一个测试定时器
test_cancel_timer 定时器名称 撤销测试定时器

在Native策略中,我们通过BEGIN_SIGNAL_HANDLER_DEFEND_SIGNAL_HANDLER_DEFSIGNAL_HANDLER可以将普通的C++函数注册为自定义干预指令

std::string increase(int slot, order_direction_t direction);
std::string decrease(int slot);
std::string show_status();
std::string test_create_timer(int timeout_ms);
std::string test_cancel_timer(std::string timer_name);

BEGIN_SIGNAL_HANDLER_DEF
  SIGNAL_HANDLER(strategy_101_t, "Increase position", increase, "slot", "direction")
  SIGNAL_HANDLER(strategy_101_t, "Decrease position", decrease, "slot")
  SIGNAL_HANDLER(strategy_101_t, "Show status", show_status)
  SIGNAL_HANDLER(strategy_101_t, "Test create timer", test_create_timer, "timeout(ms)")
  SIGNAL_HANDLER(strategy_101_t, "Test cancel timer", test_cancel_timer, "timer name")
END_SIGNAL_HANDLER_DEF

在Remote策略中,我们通过实现get_signal_meta函数来返回支持的干预指令,这里以Python为例:

def get_signal_meta(self):
    return ["increase_slot_0_buy", "increase_slot_0_sell", "decrease_slot_0", "create_test_timer", "cancel_test_timer"]

完成预定义指令的配置后,我们就可以在engine的控制面板看到:

thunder-strategy-signal.png

定义交易状态转移逻辑

我们知道,任何计算机程序都可以被建模成一个有限状态自动机,尤其在交易策略这个细分领域,我们更应该使用状态机的思维来指导策略逻辑的设计和实现。 这样可以确保策略在任何情况下的行为都是正确的,包括各种边界情况。有限状态自动机是计算机领域的一种程序设计工具,这里引用Wiki百科的定义:

A finite-state machine (FSM)

Or finite-state automaton (FSA, plural: automata), finite automaton, or simply a state machine, is a mathematical model of computation. It is an abstract machine that can be in exactly one of a finite number of states at any given time. The FSM can change from one state to another in response to some inputs; the change from one state to another is called a transition. An FSM is defined by a list of its states, its initial state, and the inputs that trigger each transition. Finite-state machines are of two types—deterministic finite-state machines and non-deterministic finite-state machines. For any non-deterministic finite-state machine, an equivalent deterministic one can be constructed.

在Strategy101策略中我们需要实现“下单开仓-等待20秒-平仓并重复上述逻辑”的过程,并且考虑报单未成交等边界情况,我们设计如下的状态转移过程:

strategy101-state-portal.jpg

状态

1.IDLE

IDLE状态表示策略当前没有任何持仓,也没有悬而未决的报单等待成交。在此状态下表明策略是可以执行开仓的,我们在on_tick中发送开仓报单指令。与此同时,为了防止报单因为内部原因或者外部原因未成交,我们创建一个定时器,确保报单在一定时间内未成交,能够完成撤单的操作,这个定时器命名为FORCE_RESET_STATE_TIMER。完成上述操作后,我们的状态跳转为INCREASE_ORDER_SENT,代表开仓报单已经发送。

2.INCREASE_ORDER_SENT

INCREASE_ORDER_SENT状态表示开仓报单已经发送,但是还未成交,此刻如果收到报单成交信号on_trade信号,那我们将状态切换为INCREASE_ORDER_TRADED,代表开仓报单已经成交,同时创建一个延迟20秒触发的定时器DECREASE_POSITION_TIME。当然还有其他的情况出现:

  • a.当收到on_secondary_ref信号:代表我们的报单已经成功被交易所接受,进入了交易所的订单薄。此时我们撤销之前的FORCE_RESET_STATE_TIMER定时器,同时创建CANCEL_ORDER_TIMER定时器,以便在报单未在规定时间内成交的情况下撤销报单。
  • b.当收到on_order_state_change信号,并且内容为报单已经撤销成功时,代表本次开仓失败,并将状态切换回IDLE

3.INCREASE_ORDER_TRADED

INCREASE_ORDER_TRADED状态表示开仓报单成交,我们已经完成入场,此刻平仓定时器也已经创建。在该状态下如果收到平仓定时器触发信号时,我们发送平仓指令,并且将状态切换到DECREASE_ORDER_SENT,代表平仓报单已经发送。

4.DECREASE_ORDER_SENT

DECREASE_ORDER_SENT状态表示平仓报单已经发送,但是还没有成交,此时策略正处于出场执行中的状态。同理,当前状态下如果收到on_secondary_ref信号则建立撤单定时器,如果收到撤单成功的信号则需要重新下单且保持状态不变。如果收到on_trade信号则表示出场成功,状态恢复到IDLE,此时可进入下一轮执行循环。

定时器

1.FORCE_RESET_STATE_TIMER

该定时器的主要目的是在报单失效时(因为外部原因没有进入交易所订单薄)能重置策略状态。

2.CANCEL_ORDER_TIMER

该定时器的主要目的是当报单无法成交时(报单已经被交易所接受)能够撤销报单。一般主要为内部原因,比如价格不合适,或者出现涨跌停板被封禁等等。注意这两种情况虽然都是抛弃当前的报单,但是也有很大的区别:FORCE_RESET_STATE_TIMER定时器作用于报单进入交易所之前,而CANCEL_ORDER_TIMER作用于报单进入交易所之后。

3.DECREASE_POSITION_TIMER

开仓成功后停留20秒后进行平仓操作的定时器。

Tags: