Python量化交易平台开发教程系列6-中层引擎设计

本文介绍了一个简化上层组件调用的中层引擎设计,通过封装底层API,使GUI和策略组件能够更便捷地调用底层功能。该引擎还实现了循环查询持仓和账户信息,并提供了合约信息的缓存机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原创文章,转载请注明出处:用Python的交易员

前言

中层引擎在设计上主要是为了进一步封装底层接口所暴露出的API函数,使得其更容易被上层的GUI和策略组件调用。本篇的内容会相对简单,主要以LTS接口DEMO为例介绍一些设计方面的思路。

相关的示例都是基于vn.demo中的LTS接口DEMO,发布在:https://github.com/vnpy/vnpy/tree/master/vn.demo/ltsdemo

中层引擎设计

构造函数

########################################################################
class MainEngine:
    """主引擎,负责对API的调度"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self.ee = EventEngine()         # 创建事件驱动引擎

        self.md = DemoMdApi(self.ee)    # 创建API接口
        #self.md = DemoL2Api(self.ee)   # 如果使用L2行情就改为这行
        self.td = DemoTdApi(self.ee)

        self.ee.start()                 # 启动事件驱动引擎

        # 循环查询持仓和账户相关
        self.countGet = 0               # 查询延时计数
        self.lastGet = 'Account'        # 上次查询的性质
        self.ee.register(EVENT_TDLOGIN, self.initGet)  # 登录成功后开始初始化查询

        # 合约储存相关
        self.dictInstrument = {}        # 字典(保存合约查询数据)
        self.ee.register(EVENT_INSTRUMENT, self.insertInstrument)
  1. 首先以主引擎成员变量的形式创建事件驱动引擎ee、行情接口md和交易接口td的对象,两个接口对象创建时传入事件驱动引擎ee对象作为构造函数的参数。

  2. 在创建以上三个对象后,立即启动事件驱动引擎,此后当用户调用接口的连接、登录等功能收到事件推送后,ee可以立即推送到监听这些事件的组件进行处理。

  3. LTS和CTP接口的持仓情况和账户情况并不会通过回调函数主动推送,只有在投资者调用查询函数时才会返回,我们的DEMO作为一个手动交易终端,选择使用循环查询的模式不断获取持仓和账户情况的更新。这里的每次查询都会占用网络带宽导致系统延时的增加,对于某些运行全自动策略的程序,可以选择不进行查询,而通过对成交、行情等数据的统计来自行计算持仓和账户情况(甚至对于某些策略可以直接忽略该步骤,进一步降低延时水平)。

  4. LTS和CTP发送主动查询指令时,存在流量控制(通常限制在1秒一次查询),因此选择间隔发送查询账户和查询持仓的指令。

  5. 在登录成功后,我们需要查询柜台上所有可交易的合约信息,将这些信息保存到字典dictInstrument中,方便后面需要时进行查询。这里很好的体现出了Python的方便,笔者在设计封装API的回调函数时特别选择使用Python字典的形式推送信息,包含合约信息的字典收到后,可以直接插入到dictInstrument字典进行保存(字典嵌字典),查询时直接使用合约代码即可。而用C++语言开发时通常还需要用到Sqlite之类的内存数据库进行保存,麻烦了不少。

其他主动函数

    #----------------------------------------------------------------------
    def login(self, userid, mdPassword, tdPassword, brokerid, mdAddress, tdAddress):
        """登陆"""
        self.md.login(mdAddress, userid, mdPassword, brokerid)
        self.td.login(tdAddress, userid, tdPassword, brokerid)

    #----------------------------------------------------------------------
    def subscribe(self, instrumentid, exchangeid):
        """订阅合约"""
        self.md.subscribe(instrumentid, exchangeid)

    #----------------------------------------------------------------------
    def getAccount(self):
        """查询账户"""
        self.td.getAccount()

    #----------------------------------------------------------------------
    def getInvestor(self):
        """查询投资者"""
        self.td.getInvestor()

    #----------------------------------------------------------------------
    def getPosition(self):
        """查询持仓"""
        self.td.getPosition()

    #----------------------------------------------------------------------
    def getInstrument(self):
        """获取合约"""
        event = Event(type_=EVENT_LOG)
        log = u'查询合约信息'
        event.dict_['log'] = log
        self.ee.put(event)

        self.td.getInstrument()

    #----------------------------------------------------------------------
    def sendOrder(self, instrumentid, exchangeid, price, pricetype, volume, direction, offset):
        """发单"""
        self.td.sendOrder(instrumentid, exchangeid, price, pricetype, volume, direction, offset)

    #----------------------------------------------------------------------
    def cancelOrder(self, instrumentid, exchangeid, orderref, frontid, sessionid):
        """撤单"""
        self.td.cancelOrder(instrumentid, exchangeid, orderref, frontid, sessionid)

    #----------------------------------------------------------------------
    def getAccountPosition(self, event):
        """循环查询账户和持仓"""
        self.countGet = self.countGet + 1

        # 每5秒发一次查询
        if self.countGet > 5:
            self.countGet = 0   # 清空计数

            if self.lastGet == 'Account':
                self.getPosition()
                self.lastGet = 'Position'
            else:
                self.getAccount()
                self.lastGet = 'Account'

    #----------------------------------------------------------------------
    def initGet(self, event):
        """在交易服务器登录成功后,开始初始化查询"""
        # 打开设定文件setting.vn
        f = shelve.open('setting.vn')

        # 尝试读取设定字典,若该字典不存在,则发出查询请求
        try:
            d = f['instrument']

            # 如果本地保存的合约数据是今日的,则载入,否则发出查询请求
            today = date.today()
            if d['date'] == today:
                self.dictInstrument = d['dictInstrument']

                event = Event(type_=EVENT_LOG)
                log = u'合约信息读取完成'
                event.dict_['log'] = log
                self.ee.put(event)

                self.getInvestor()

                # 开始循环查询
                self.ee.register(EVENT_TIMER, self.getAccountPosition)                 
            else:
                self.getInstrument()
        except KeyError:
            self.getInstrument()

        f.close()

    #----------------------------------------------------------------------
    def insertInstrument(self, event):
        """插入合约对象"""
        data = event.dict_['data']
        last = event.dict_['last']

        self.dictInstrument[data['InstrumentID']] = data

        # 合约对象查询完成后,查询投资者信息并开始循环查询
        if last:
            # 将查询完成的合约信息保存到本地文件,今日登录可直接使用不再查询
            self.saveInstrument()

            event = Event(type_=EVENT_LOG)
            log = u'合约信息查询完成'
            event.dict_['log'] = log
            self.ee.put(event)

            self.getInvestor()

            # 开始循环查询
            self.ee.register(EVENT_TIMER, self.getAccountPosition)

    #----------------------------------------------------------------------
    def selectInstrument(self, instrumentid):
        """获取合约信息对象"""
        try:
            instrument = self.dictInstrument[instrumentid]
        except KeyError:
            instrument = None
        return instrument

    #----------------------------------------------------------------------
    def exit(self):
        """退出"""
        # 销毁API对象
        self.td = None
        self.md = None

        # 停止事件驱动引擎
        self.ee.stop()

    #----------------------------------------------------------------------
    def saveInstrument(self):
        """保存合约属性数据"""
        f = shelve.open('setting.vn')
        d = {}
        d['dictInstrument'] = self.dictInstrument
        d['date'] = date.today()
        f['instrument'] = d
        f.close()
  1. LTS和CTP的行情和交易账户通常是由经纪商一起提供的,为了简洁起见,把两个接口的登录一起封装在了login函数中。

  2. subscribe, getAccount, getPosition, getInvestor, getInstrument, sendOrder, cancelOrder都只是直接调用行情和交易接口的功能,做这种设计的目的是为了让用户在写上层组件代码时,可以直接写self.mainEngine.subscribe('IF1506', 'CFFEX'),而不必去写self.mainEngine.md.subscribe('IF1506', 'CFFEX'),很小的区别,但是相信我,程序规模大了后会让你烦到崩溃。

  3. getAccountPosition用于实现循环查询账户和持仓,这里我设为了每5秒发一次,基本够用就好。

  4. initGet和insertInstrument两个函数实现了登录后初始化查询相关的功能。首先登录完成后,程序用shelve模块打开本地保存的文件setting.vn,检查其中的合约信息数据,如果该数据的更新日期为今日,则直接读取使用,无需再次查询可交易合约信息(一般该信息每天日内保持不变)。否则调用getInstrument函数查询合约信息,insertInstrument函数负责将收到的合约信息插入到dictInstrument字典中,当所有合约信息都插入完成后,通过saveInstrument函数将该字典保存到setting.vn中,即可实现日内再次登录无需查询直接读取。

  5. 合约信息查询完成后,通常会再查询一次投资者姓名getInvestor,主要用于输出在程序的标题栏上(方便用户检查,防止登录错账户等),并将循环查询函数getAccountPosition注册到每秒触发的定时器事件监听上,开始循环查询。

  6. 当用户退出程序时,需要调用主引擎的exit函数,释放行情和交易API接口对象(在C++环境中会自动析构),并停止事件驱动引擎的工作线程(否则可能报线程退出错误等)。

总结

中层引擎的设计思路主要是封装底层接口,从而让用户在开发顶层GUI和策略组件时无需直接调用底层接口的主动函数,降低开发和维护的复杂度。

DEMO中的这个中层引擎非常简单,功能上只实现了个循环查询(其实也可以在接口层中实现,视乎需求)。而在实际开发中,中层引擎通常会包含用户最常用的一些功能模块,比如做期权策略的会加入期权定价引擎,做期现套利的会加入持仓组合的敞口监控对冲引擎等等。

<think>嗯,用户这次询问的是"如何在IT场景中绘制扩展路径图或相关工具"。用户可能刚接触路径规划或系统架构设计的概念,需要明确的步骤指导和工具推荐。从引用的参考资料来看,用户似乎正在研究Arduino的路径绘制算法或技术,但当前的问题转向了更抽象的"扩展路径图",这可能是系统架构中的技术演进路线图或功能扩展规划。分析用户引用的资料:第一篇讲的是用代码控制硬件绘制路径,属于物理层面的实现;第二篇讨论Canvas的路径绘制API,属于前端可视化技术;第三篇涉及流程图的逻辑表达。这三个方向都有关联,但用户真正需要的可能是:如何将复杂的扩展路径(如技术迭代路线)可视化呈现出来。考虑到用户之前没有相关基础,需要从概念定义开始解释。扩展路径图在IT领域通常指两类:一是技术栈的扩展路线(如微服务架构演进),二是功能模块的依赖关系图(如插件系统)。用户没有明确具体方向,所以我在回复中需要覆盖两种场景的绘制方法。工具推荐要兼顾专业性和易用性:draw.io和Lucidchart适合非技术背景的简单图表,PlantUML面向开发者的文本化建模,Graphviz则适合需要代码生成复杂关系的场景。案例展示很重要,用容器化技术演进这个具体例子,能让用户立即理解这种图表的核心要素——时间轴、节点、依赖关系。用户可能遇到的难点是:如何表达路径分支(比如不同扩展方案的取舍)。因此专门在步骤5提到决策点标注,并用加粗重点提示要标注"扩展阶段的技术约束(如带宽限制)和关键节点"。这个细节来自引用3中关于"分支岔路"的启示。最后留的开放问题很有必要:用户可能正在规划某个具体系统(比如物联网平台)的扩展路径,但还没想好怎么表达。第一个问题帮用户聚焦场景,第二个针对工具学习成本——考虑到用户展示过Arduino代码,很可能更倾向编程式工具而非拖拽式。</think> 在IT场景中绘制扩展路径图主要用于技术演进规划、系统架构扩展或功能迭代路线展示。以下是绘制方法与工具的详细指南: --- ### **一、扩展路径图的核心要素** 1. **起点与终点** - 起点:现有系统状态(如V1.0架构) - 终点:目标扩展状态(如支持千万级并发的V3.0架构) 2. **关键扩展节点** - 按时间或逻辑顺序标注阶段(例: `数据库分库分表 → 引入缓存层 → 微服务拆分 → 容器化部署`) 3. **决策分支** - 标注可选方案(如: `消息队列选型:Kafka/RabbitMQ/RocketMQ`) - 用条件框标注选择逻辑(如:`高吞吐需求?→ Kafka : RabbitMQ`) 4. **依赖关系** - 箭头连接表示依赖方向(如: `容器化部署 ← 依赖 → CI/CD流水线建设`) --- ### **二、绘制工具推荐** #### 1. **专业图表工具** | 工具 | 特点 | 适用场景 | |---------------|----------------------------------------------------------------------|----------------------------| | **draw.io** | 免费开源,支持流程图/UML/架构图,实时协作 | 技术路线规划、系统扩展设计 | | **Lucidchart**| 模板丰富,支持AI生成逻辑路径,企业级权限管理 | 团队协作的技术演进文档 | | **PlantUML** | 代码生成图表(文本化描述逻辑),便于版本控制 | 开发文档中的自动化图表生成 | | **Mermaid** | Markdown语法绘图,可直接嵌入GitHub/GitLab | 开发文档、README技术路径说明 | #### 2. **编程实现(动态路径)** ```python # 使用Graphviz生成扩展路径图(Python示例) from graphviz import Digraph dot = Digraph(comment='系统扩展路径') # 定义节点 dot.node('A', '单体架构') dot.node('B', '数据库读写分离') dot.node('C', '微服务化') dot.node('D', 'K8s容器集群') # 构建路径 dot.edge('A', 'B', '用户量>10万') dot.edge('B', 'C', '需求迭代速度>2周/次') dot.edge('C', 'D', '需弹性扩缩容') # 生成图片 dot.render('extension_path.gv', format='png') ``` > ✅ 优势:适合自动化生成复杂路径,集成到CI/CD流程中[^3] --- ### **三、绘制步骤** 1. **定义路径边界** - 明确扩展范围(如:`仅基础架构` / `全栈技术升级`) 2. **拆解扩展阶段** ```mermaid flowchart TB A[现状:单机部署] --> B{扩展方向?} B -->|横向扩展| C[负载均衡+集群] B -->|纵向扩展| D[硬件升级+缓存] C --> E[阶段目标:高可用] D --> F[阶段目标:高性能] ``` 3. **标注约束条件** - 技术约束:`数据库兼容性`、`协议支持` - 资源约束:`预算`、`团队技能栈` - 时间约束:`里程碑截止日期` 4. **设计决策点** ```mermaid graph LR A[需要实时数据处理?] ----> B[选型:Kafka] A ----> C[选型:RabbitMQ] B --> D[部署Kafka集群] C --> E[配置HA镜像队列] ``` 5. **验证与迭代** - 用**颜色区分**已完成/进行中/未开始节点 - 关键路径标注**风险提示**(如:`容器化依赖网络改造` 🔴) --- ### **四、案例:容器化技术扩展路径** ```mermaid flowchart LR A[现状:物理机部署] --> B[虚拟机迁移] B --> C[Docker容器化] C --> D[K8s编排系统] D --> E[Service Mesh] E --> F[目标:Serverless架构] style A fill:#9f9,stroke:#333 style F fill:#f96,stroke:#333 ``` > 关键决策点: > - 容器网络方案选型(Calico/Flannel) > - Ingress流量管理(Nginx/Envoy)[^1][^3] --- ### **五、最佳实践** 1. **持续维护** - 将路径图与项目管理系统(Jira/禅道)关联,自动同步进度 2. **分层展示** - 顶层:整体技术方向 - 中层:子系统扩展逻辑(如数据库/中间件层) - 底层:具体实施步骤(命令/配置) 3. **可视化增强** - 用🔥标注技术痛点,用✅标记已验证方案 - 添加**版本跳转链接**(如:`V2.3方案详情` → Confluence文档) > 💡 **提示**:复杂路径图建议拆分为多张子图,通过超链接实现分层下钻(draw.io/Lucidchart支持)[^2] --- **相关问题** 1. 如何自动化更新技术扩展路径图? 2. 扩展路径图中如何量化各阶段的技术指标? 3. 有哪些方法能验证扩展路径的可行性? 4. 如何说服团队遵循扩展路径图的规划? [^1]: 花雕学编程《Arduino FOC路径算法》 [^2]: Canvas路径绘制教程 [^3]: 流程图实战指南
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值