说明
使用FSM来对状态的变化进行管理,一方面有助于我们将问题定义的更清晰,同时也让程序设计更可靠、可读性(事后)更强。
内容
1 问题描述
假设有一笔投资用于证券交易,随着交易、市场价格变化,投资的状态也随之改变。我们需要了解并定义这些状态,以便设计策略长期执行。
一般我们关心投资的盈亏,而这些与是否持仓相关,如果持仓的话又与价格涨跌有关。假设我们暂时不管决策,那么可以先定义以下一些状态:
Init: 初始化
Stop:停止
EB: Empty Breakeven 空仓(盈亏)平衡
HB: Hold Breakeven 持仓(盈亏)平衡
HW1: Hold Win 1 持仓盈利-小幅
HW2: Hold Win 2 持仓盈利-大幅
EW1: Empty Win1 空仓盈利-小幅
EW2: Empty Win2 空仓盈利-大幅
HL1: Hold Loss 1 持仓亏损-小幅
HL2: Hold Loss2 持仓亏损-大幅
EL1: Empty Loss 1 空仓亏损-小幅
EL2: Empty Loss 1 空仓亏损-大幅
事件:
- 1 涨跌 up / down (相对于期初资产)
- 2 买卖 buy / sell
- 3 控制停止 control stop(输赢都可能被控制停止)
- 4 初始化: init 重新按照参数初始化实例
- 5 无变化 unchange
以上有一个假设:在一个时隙(一分钟内)不可能会出现资产的大幅波动。如果出现意外情况(try…except),程序将根据资产盈利、订单持有状态将对象的状态直接置为对应值。
2 代码实验
用代码实现如下:
状态为包含Init和Stop在内的十二个状态,事件则为unchange, up,down, buy,sell以及stop和init 等六种。
其中unchange和stop是对所有状态都生效的。stop本来分为正常停止以及手动停止的,但是总体上都可以归于stop。
class BT2:
pass
# 12个状态
states = ['Init','EB','HB','HW1','HW2','EW1','EW2','HL1','HL2','EL1','EL2','Stop']
transitions = [
# unchange事件
{'trigger': 'unchange', 'source': 'Init', 'dest': 'EB'},
{'trigger': 'unchange', 'source': 'EB', 'dest': 'EB'},
{'trigger': 'unchange', 'source': 'HB', 'dest': 'HB'},
{'trigger': 'unchange', 'source': 'HW1', 'dest': 'HW1'},
{'trigger': 'unchange', 'source': 'HW2', 'dest': 'HW2'},
{'trigger': 'unchange', 'source': 'EW1', 'dest': 'EW1'},
{'trigger': 'unchange', 'source': 'EW2', 'dest': 'EW2'},
{'trigger': 'unchange', 'source': 'HL1', 'dest': 'HL1'},
{'trigger': 'unchange', 'source': 'HL2', 'dest': 'HL2'},
{'trigger': 'unchange', 'source': 'EL1', 'dest': 'EL1'},
{'trigger': 'unchange', 'source': 'EL2', 'dest': 'EL2'},
# up事件:比上一个level高3个点, Init有一个InitCap,约定 B_center = InitCap, B的Band定为3个点, B+3pt = W1的下界以此类推
{'trigger': 'up', 'source': 'Init', 'dest': 'EB'},
# up对E无影响
{'trigger': 'up', 'source': 'EB', 'dest': 'EB'},
{'trigger': 'up', 'source': 'EW1', 'dest': 'EW1'},
{'trigger': 'up', 'source': 'EW2', 'dest': 'EW2'},
{'trigger': 'up', 'source': 'EL1', 'dest': 'EL1'},
{'trigger': 'up', 'source': 'EL2', 'dest': 'EL2'},
# up对H有影响
{'trigger': 'up', 'source': 'HB', 'dest': 'HW1'},
{'trigger': 'up', 'source': 'HW1', 'dest': 'HW2'},
{'trigger': 'up', 'source': 'HW2', 'dest': 'Stop'},
{'trigger': 'up', 'source': 'HL1', 'dest': 'HB'},
{'trigger': 'up', 'source': 'HL2', 'dest': 'HL1'},
# down事件:类似up事件
{'trigger': 'down', 'source': 'Init', 'dest': 'EB'},
{'trigger': 'down', 'source': 'EB', 'dest': 'EB'},
{'trigger': 'down', 'source': 'EW1', 'dest': 'EW1'},
{'trigger': 'down', 'source': 'EW2', 'dest': 'EW2'},
{'trigger': 'down', 'source': 'EL1', 'dest': 'EL1'},
{'trigger': 'down', 'source': 'EL2', 'dest': 'EL2'},
# down对H有影响
{'trigger': 'down', 'source': 'HB', 'dest': 'HL1'},
{'trigger': 'down', 'source': 'HW1', 'dest': 'HB'},
{'trigger': 'down', 'source': 'HW2', 'dest': 'HW1'},
{'trigger': 'down', 'source': 'HL1', 'dest': 'EL2'},
{'trigger': 'down', 'source': 'HL2', 'dest': 'Stop'},
# buy事件,仅对E类生效
{'trigger': 'buy', 'source': 'EB', 'dest': 'HB'},
{'trigger': 'buy', 'source': 'EW1', 'dest': 'HW1'},
{'trigger': 'buy', 'source': 'EW2', 'dest': 'HW2'},
{'trigger': 'buy', 'source': 'EL1', 'dest': 'HL1'},
{'trigger': 'buy', 'source': 'EL2', 'dest': 'HL2'},
# sell事件,仅对H类生效
{'trigger': 'sell', 'source': 'HB', 'dest': 'EB'},
{'trigger': 'sell', 'source': 'HW1', 'dest': 'EW1'},
{'trigger': 'sell', 'source': 'HW2', 'dest': 'EW2'},
{'trigger': 'sell', 'source': 'HL1', 'dest': 'EL1'},
{'trigger': 'sell', 'source': 'HL2', 'dest': 'EL2'},
# stop事件
{'trigger': 'stop', 'source': 'Init', 'dest': 'Stop'},
{'trigger': 'stop', 'source': 'HB', 'dest': 'Stop'},
{'trigger': 'stop', 'source': 'HL1', 'dest': 'Stop'},
{'trigger': 'stop', 'source': 'HL2', 'dest': 'Stop'},
{'trigger': 'stop', 'source': 'HW1', 'dest': 'Stop'},
{'trigger': 'stop', 'source': 'HW2', 'dest': 'Stop'},
{'trigger': 'stop', 'source': 'EB', 'dest': 'Stop'},
{'trigger': 'stop', 'source': 'EL1', 'dest': 'Stop'},
{'trigger': 'stop', 'source': 'EL2', 'dest': 'Stop'},
{'trigger': 'stop', 'source': 'EW1', 'dest': 'Stop'},
{'trigger': 'stop', 'source': 'EW2', 'dest': 'Stop'},
# init事件
{'trigger': 'init', 'source': 'Stop', 'dest': 'Init'},
]
# 创建状态机
machine = Machine(model=BT2, states=states, transitions=transitions, initial='Init')
然后进行测试
import random
# 定义状态机事件列表
events = ['unchange', 'up', 'down', 'buy', 'sell']
# 生成随机事件列表
random_events = [random.choice(events) for _ in range(10)] # 生成包含10个随机事件的列表
print(random_events)
['sell', 'unchange', 'sell', 'buy', 'unchange', 'unchange', 'up', 'unchange', 'sell', 'unchange']
bt2_instance = BT2()
for i,event in enumerate(random_events):
print('>>>>>>> %s >>>>>' %i)
print(bt2_instance.state)
try:
bt2_instance.trigger(event)
print(bt2_instance.state)
except:
print(' conflicts ' ,bt2_instance.state, ' vs ' ,event)
>>>>>>> 0 >>>>>
Init
conflicts Init vs sell
>>>>>>> 1 >>>>>
Init
EB
>>>>>>> 2 >>>>>
EB
conflicts EB vs sell
>>>>>>> 3 >>>>>
EB
HB
>>>>>>> 4 >>>>>
HB
HB
>>>>>>> 5 >>>>>
HB
HB
>>>>>>> 6 >>>>>
HB
HW1
>>>>>>> 7 >>>>>
HW1
HW1
>>>>>>> 8 >>>>>
HW1
EW1
>>>>>>> 9 >>>>>
EW1
EW1
可以看到,正常情况下,合法的状态转移都是ok的;而未定义的转移则会报错。
3 总结
带来的好处:
清晰化
这部分的好处是体现在代码之前的。能够设定目标问题的状态及其变化,首先就需要思考及明确其中的关键因素,而这些可能是在开始时没有想好的。
例如,有哪些状态是基本的,有哪些是对业务有帮助的。在整个运行过程中,可能会有哪些变化(事件),这些事件又会如何转变当前实例的状态。以及,有哪些不合法的事件:在状态A下不可能发生某些事件。
在代码的运行过程中,每次的执行,总是能够被简单且清晰的记录下来。即便在事后,也可以很容易的通过这些状态记录,对问题进行快速的分析。
结构化
在定义了状态之后,很显然就会对方法进行区分。例如 processing这个方法,在状态A和状态B下显然是不同的,可以针对每一个状态都单独的写(当然也可以复用部分通用的)。这样processing 实际上是按状态区分的。
从图的角度来说,FSM实际上是一个规模较小的子图,在执行的过程中不断进行(循环)游历。它强调了图结构,以及重复执行图。