问题描述
笔者最近在尝试写 QMT 的量化策略。QMT 自带的交易函数 passorder 很复杂,比较难用,所以我打算把它封装一下。在 main.py 策略文件里,把 passorder 方法封装进 buy_stock_by_money 函数很成功。
但是当我想让代码更简洁,把 buy_stock_by_money 提取到 utils.py 文件,然后让 main.py(宿主)引用 utils.py 的时候,就出问题了。
# utils.py
def buy_stock_by_money(code, money, C, strategyName=None):
# passorder 是 main.py 运行时的全局变量
passorder(23, 1102, C.accID, code, 5, -1, money, strategyName, C)
# main.py
import utils
# ERROR: 调用的时候会报错,paassorder 未定义
utils.buy_stock_by_money('5100300.SH', 50000, ContextInfo, 'Test Strategy')
原因是 passorder 是 QMT 内置 Python 的方法,在策略文件里能够访问到,但是在模块里是访问不到的。
所以我们需要把 passorder 传入 utils.py 才可以,询问过 ChatGPT 后,得到了下面几个解决方案,个人推荐最后的「终极方案」,可以一劳永逸。
解决方案
方案一:显性传入
最直接,最简单的方法,就是把依赖的全局函数 passorder 作为参数传入,如下:
# utils.py
def buy_stock_by_money(passorder, code, money, C, strategyName=None):
# passorder 作为参数传入
passorder(23, 1102, C.accID, code, 5, -1, money, strategyName, C)
# main.py
import utils
# 调用的时候直接传入 passorder
utils.buy_stock_by_money(passorder, '5100300.SH', 50000, ContextInfo, 'Test Strategy')
这种方法的优点就是让 buy_stock_by_money 保持无副作用,也就是纯函数。也就是无论在任何环境使用 utils.py 都不用担心它的上下文。
缺点也比较明显,除了 passorder 外,还有很多其它的全局变量 order_lots、order_value、order_percent 等等……如果 buy_stock_by_money 依赖多个全局函数,那就要把它们都作为变量,全部显示传入,可能会变成下面这样,无论声明还是调用,都比较麻烦:
# utils.py
def buy_stock_by_money(passorder, order_lots, order_value, order_percent, code, money, C, strategyName=None):
# 依赖多个全局函数
if strategyName == 'order_lots':
order_lots(23, 1102, C.accID, code, 5, -1, money, strategyName, C)
elif strategyName == 'order_value':
order_value(23, 1102, C.accID, code, 5, -1, money, strategyName, C)
elif strategyName == 'order_percent':
order_percent(23, 1102, C.accID, code, 5, -1, money, strategyName, C)
else:
passorder(23, 1102, C.accID, code, 5, -1, money, strategyName, C)
# main.py
import utils
# 调用时要把所有依赖传入
utils.buy_stock_by_money(passorder, order_lots, order_value, order_percent, '5100300.SH', 50000, ContextInfo, 'Test Strategy')
相信这个不是我们想要的,起码在这种可能依赖太多宿主全局变量的时候,这种方法是不太合适的。
方案二:一次性传入全部依赖
上面的方法不行,那就想办法把 main.py 的全局变量也变成 utils.py 的全局变量,示例代码如下:
# utils.py
def init(locals):
global passorder, order_lots, order_value, order_percent
passorder = locals['passorder']
order_lots = locals['order_lots']
order_value = locals['order_value']
order_percent = locals['order_percent']
# 不再需要显性传入所有依赖
def buy_stock_by_money(code, money, C, strategyName=None):
if strategyName == 'order_lots':
order_lots(23, 1102, C.accID, code, 5, -1, money, strategyName, C)
elif strategyName == 'order_value':
order_value(23, 1102, C.accID, code, 5, -1, money, strategyName, C)
elif strategyName == 'order_percent':
order_percent(23, 1102, C.accID, code, 5, -1, money, strategyName, C)
else:
passorder(23, 1102, C.accID, code, 5, -1, money, strategyName, C)
# main.py
import utils
# 调用 init,将全局变量注入 utils.py
utils.init({
'passorder': passorder,
'order_lots': order_lots,
'order_value': order_value,
'order_percent': order_percent
})
# 不再需要传入所有依赖
utils.buy_stock_by_money('5100300.SH', 50000, ContextInfo, 'Test Strategy')
这种方式虽然引入了副作用,但是对于我们的使用场景来说,收益绝对大于成本。所有依赖只需要一次性传入即可。
但是这里还有个小瑕疵,那就是一旦 utils.py 又依赖了新全局函数,要同步修改 main.py 和 utils.py,还是稍微有点麻烦。直到我把这两段代码丢给了 AI(Codeium),问能不能简化一下,于是得到了最后的「终极方案」。
方案三:终极方案
我用 Codeium 选中了 init 的代码,然后让它帮我简化一下,结果竟然有惊喜:
那么一大堆代码,直接一句 globals().update(locals) 就完事了?!?!试了一下还真是。
同理我又试着简化了一下调用的时候的代码,结果直接 utils.init(locals()) 就搞定了。
最关键的是,这种写法把所有全局变量都传进去了,不用再一个一个的声明了,不得不说,AI 编程真的 NB!!
终极代码如下:
# main.py
import utils as u
import imp
imp.reload(u) # 可选:每次重新加载,防止缓存
u.init(globals()) # 精彩!!!!
u.buy_stock_by_money('5100300.SH', 50000, ContextInfo, 'Test Strategy')
# utils.py
def init(locals):
globals().update(locals) # 精彩!!!
def buy_stock_by_money(code, money, C, strategyName=None):
passorder(23, 1102, C.accID, code, 5, -1, money, strategyName, C)
不但代码清爽了很多,连扩展性都解决了,完美!!
一个小坑
我在运行 QMT 策略的时候发现如果策略名称是中文的话,会报加载 utils 错误,而且用了 imp.reload(u) 有时候也不好使。所以我现在的策略文件名,都是英文,现在我用的模板代码是这样的,仅供参考:
#coding:gbk
import sys
if 'qmt_utils' in sys.modules:
del sys.modules['qmt_utils']
import qmt_utils as u
import datetime as dt
class a():
pass
A = a()
A.canceled = False
def init(C):
global A
global logger
u.init(C, globals())
logger = u.MyLogger(C)
today = dt.datetime.now().strftime('%Y-%m-%d')
C.run_time("Test", '3nSecond', today + ' 00:00:00', 'SH')
def Test(C):
now_date = dt.datetime.now().strftime("%Y%m%d")
bar_date = u.get_bar_date(C)
if not C.do_back_test and bar_date < now_date:
return
logger.log(f'AccID: {C.accID}, AccType: {C.accType}', 'DEBUG', False)
def handlebar(C):
if not C.do_back_test and not C.is_last_bar():
return
总结
笔者的主语言是 JS/TS,Python 应用只能算是入门级,但是最起码的代码审美还是有的。AI 的加持真的可以让我这种人受益匪浅,毫无疑问,学习效率提高了不知道多少倍,关键代码质量也可以直接拉满,这种感觉真的太爽了。
实现了 utils.py 的提取,未来开发策略无论是效率还是质量,肯定会有极大提升,后续有任何更新再继续。