├── .flake8 ├── .gitignore ├── LICENSE ├── README.md ├── auto_task ├── ding.py └── run.py ├── cxy_ctpgateway ├── __init__.py ├── event.py └── gateway.py ├── local_manager ├── manager.py └── run.py ├── my_simplestrategy ├── __init__.py ├── base.py ├── engine.py ├── simple.ico ├── strategy.py ├── template.py └── ui.py ├── run.py ├── run_trader.py ├── setting.json ├── setup.cfg └── setup.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = venv,build,__pycache__,__init__.py,ib,talib,uic 3 | ignore = 4 | E501 line too long, fixed by black 5 | W503 line break before binary operator 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | *.con 131 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 vn.py 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # code_demo 2 | 源码解析小班课 3 | -------------------------------------------------------------------------------- /auto_task/ding.py: -------------------------------------------------------------------------------- 1 | import time 2 | import hmac 3 | import hashlib 4 | import base64 5 | import urllib.parse 6 | import requests 7 | 8 | 9 | url = "https://oapi.dingtalk.com/robot/send?access_token=a521d6c8d27cc04a06c8e6fcc47e3bed89ce34c1c62d4367d25115f0856feab2" 10 | secret = "SECc273f6574c4bc8abe8639d73cbdc86492158a677a178289b91f6308af8ac9379" 11 | 12 | 13 | def send_ding_msg(msg: str) -> dict: 14 | """发送钉钉消息""" 15 | # 结合当前时间戳,生成签名 16 | timestamp = str(round(time.time() * 1000)) 17 | secret_enc = secret.encode('utf-8') 18 | string_to_sign = '{}\n{}'.format(timestamp, secret) 19 | 20 | string_to_sign_enc = string_to_sign.encode('utf-8') 21 | hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest() 22 | sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) 23 | 24 | # 请求地址 25 | msg_url = f"{url}×tamp={timestamp}&sign={sign}" 26 | 27 | # 请求头部 28 | header = { 29 | "Content-Type": "application/json", 30 | "Charset": "UTF-8" 31 | } 32 | 33 | # 请求数据 34 | data = { 35 | "msgtype": "markdown", 36 | "markdown": { 37 | "title": "信息", 38 | "text": msg 39 | }, 40 | "at": { 41 | "isAtAll": False 42 | } 43 | } 44 | 45 | # 发送请求 46 | r = requests.post(msg_url, json=data, headers=header) 47 | 48 | return r.json() 49 | 50 | 51 | if __name__ == "__main__": 52 | send_ding_msg("test") 53 | -------------------------------------------------------------------------------- /auto_task/run.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import multiprocessing 3 | from datetime import time, datetime 4 | from time import sleep 5 | 6 | from vnpy.rpc import RpcClient, RpcServer 7 | from vnpy.event import EventEngine 8 | from vnpy.trader.utility import load_json 9 | from vnpy.trader.engine import MainEngine 10 | 11 | from vnpy_ctp import CtpGateway 12 | from vnpy_ctastrategy import CtaStrategyApp, CtaEngine 13 | from vnpy_ctastrategy.base import EVENT_CTA_LOG 14 | 15 | from ding import send_ding_msg 16 | 17 | 18 | # 通讯消息主题 19 | TOPIC_INIT = "init" 20 | TOPIC_START = "start" 21 | TOPIC_STOP = "stop" 22 | TOPIC_CLOSE = "close" 23 | 24 | # 策略运行时间 25 | DAY_START = time(8, 45) 26 | DAY_END = time(15, 0) 27 | 28 | NIGHT_START = time(20, 45) 29 | NIGHT_END = time(2, 45) 30 | 31 | # RPC通讯地址 32 | req_address = "tcp://localhost:9988" 33 | sub_address = "tcp://localhost:8899" 34 | rep_address = "tcp://*:9988" 35 | pub_address = "tcp://*:8899" 36 | 37 | 38 | class MyServer(RpcServer): 39 | """RPC服务器""" 40 | 41 | def __init__(self) -> None: 42 | """""" 43 | super().__init__() 44 | 45 | self.inited = False 46 | self.trading = False 47 | 48 | self.register(self.write_log) 49 | self.register(self.send_msg) 50 | self.register(self.init_done) 51 | self.register(self.start_done) 52 | self.register(self.stop_done) 53 | 54 | def write_log(self, msg: str) -> None: 55 | """输出子进程日志""" 56 | print(f"[交易子进程] {msg}") 57 | send_ding_msg(f"[交易子进程] {msg}") 58 | 59 | def parent_log(self, msg: str) -> None: 60 | """输出父进程日志""" 61 | print(f"[监控父进程] {msg}") 62 | send_ding_msg(f"[监控父进程] {msg}") 63 | 64 | def send_msg(self, msg: str) -> bool: 65 | """发送异常信息通知""" 66 | send_ding_msg(f"[异常信息] {msg}") 67 | 68 | def init_done(self) -> None: 69 | """初始化完成""" 70 | self.inited = True 71 | self.write_log("策略全部初始化完成") 72 | 73 | def start_done(self) -> None: 74 | """启动完成""" 75 | self.trading = True 76 | self.write_log("策略全部启动") 77 | 78 | def stop_done(self) -> None: 79 | """停止完成""" 80 | self.trading = False 81 | self.write_log("策略全部停止") 82 | 83 | def clear_status(self) -> None: 84 | """清空状态""" 85 | self.inited = False 86 | self.trading = False 87 | 88 | 89 | class MyClient(RpcClient): 90 | 91 | def __init__(self): 92 | """ 93 | Constructor 94 | """ 95 | super().__init__() 96 | 97 | self.cta_engine: CtaEngine = None 98 | 99 | self.active = True 100 | 101 | def register_engine(self, cta_engine: CtaEngine) -> None: 102 | """在后续传入CTA引擎""" 103 | self.cta_engine = cta_engine 104 | 105 | def callback(self, topic: str, data: object): 106 | """回调函数""" 107 | # 初始化指令 108 | if topic == TOPIC_INIT: 109 | # 调用同步函数来执行策略初始化 110 | for strategy_name in self.cta_engine.strategies.keys(): 111 | self.cta_engine._init_strategy(strategy_name) 112 | 113 | # 通知服务器初始化完成 114 | self.init_done() 115 | # 启动指令 116 | elif topic == TOPIC_START: 117 | self.cta_engine.start_all_strategies() 118 | 119 | self.start_done() 120 | # 停止指令 121 | elif topic == TOPIC_STOP: 122 | self.cta_engine.stop_all_strategies() 123 | 124 | self.stop_done() 125 | # 关闭指令 126 | elif topic == TOPIC_CLOSE: 127 | self.active = False 128 | 129 | 130 | def check_trading_period() -> bool: 131 | """检查当前是否为交易时段""" 132 | return True 133 | current_time = datetime.now().time() 134 | 135 | trading = False 136 | if ( 137 | (current_time >= DAY_START and current_time <= DAY_END) 138 | or (current_time >= NIGHT_START) 139 | or (current_time <= NIGHT_END) 140 | ): 141 | trading = True 142 | 143 | return trading 144 | 145 | 146 | def run_child(): 147 | """子进程运行""" 148 | # 创建客户端 149 | my_client = MyClient() 150 | my_client.subscribe_topic("") 151 | my_client.start(req_address, sub_address) 152 | 153 | # 创建核心引擎 154 | event_engine = EventEngine() 155 | main_engine = MainEngine(event_engine) 156 | main_engine.add_gateway(CtpGateway) 157 | 158 | cta_engine = main_engine.add_app(CtaStrategyApp) 159 | my_client.register_engine(cta_engine) 160 | 161 | my_client.write_log("核心引擎创建成功") 162 | 163 | # 注册CTA日志 164 | log_engine = main_engine.get_engine("log") 165 | event_engine.register(EVENT_CTA_LOG, log_engine.process_log_event) 166 | my_client.write_log("注册日志事件监听") 167 | 168 | # 连接CTP交易接口 169 | ctp_setting = load_json("connect_ctp.json") 170 | main_engine.connect(ctp_setting, "CTP") 171 | my_client.write_log("连接CTP接口") 172 | 173 | # 初始化CTA引擎 174 | cta_engine.init_engine() 175 | my_client.write_log("CTA引擎初始化完成") 176 | 177 | # 主线程进入循环 178 | while my_client.active: 179 | sleep(10) 180 | 181 | main_engine.close() 182 | sys.exit(0) 183 | 184 | 185 | def run_parent(): 186 | """父进程运行""" 187 | child_process = None 188 | 189 | my_server = MyServer() 190 | my_server.start(rep_address, pub_address) 191 | 192 | my_server.parent_log("启动CTA策略守护父进程") 193 | 194 | while True: 195 | trading = check_trading_period() 196 | 197 | # 交易时间 198 | if trading: 199 | # 启动子进程 200 | if child_process is None: 201 | my_server.parent_log("启动交易子进程") 202 | child_process = multiprocessing.Process(target=run_child) 203 | child_process.start() 204 | my_server.parent_log("子进程启动成功") 205 | # 启动交易策略 206 | elif child_process.is_alive(): 207 | if not my_server.inited: 208 | my_server.publish(TOPIC_INIT, None) 209 | elif not my_server.trading: 210 | my_server.publish(TOPIC_START, None) 211 | # 异常信息报警 212 | else: 213 | code = child_process.exitcode 214 | my_server.send_msg(f"子进程异常终止运行,退出码{code}") 215 | 216 | # 非交易时间则退出子进程 217 | if not trading and child_process: 218 | if my_server.trading: 219 | my_server.publish(TOPIC_STOP, None) 220 | elif child_process.is_alive(): 221 | my_server.publish(TOPIC_CLOSE, None) 222 | else: 223 | child_process = None 224 | my_server.clear_status() 225 | my_server.parent_log("子进程关闭成功") 226 | 227 | sleep(5) 228 | 229 | 230 | if __name__ == "__main__": 231 | run_parent() 232 | -------------------------------------------------------------------------------- /cxy_ctpgateway/__init__.py: -------------------------------------------------------------------------------- 1 | from .gateway import CxyGateway 2 | -------------------------------------------------------------------------------- /cxy_ctpgateway/event.py: -------------------------------------------------------------------------------- 1 | EVENT_CONTRACT_INITED = "eInited" 2 | -------------------------------------------------------------------------------- /cxy_ctpgateway/gateway.py: -------------------------------------------------------------------------------- 1 | from vnpy.event import EventEngine 2 | 3 | from vnpy_ctp import CtpGateway as OriginalGateway 4 | from vnpy_ctp.gateway.ctp_gateway import CtpTdApi, CtpMdApi, BaseGateway 5 | 6 | from .event import EVENT_CONTRACT_INITED 7 | 8 | 9 | class CxyTdApi(CtpTdApi): 10 | 11 | def onRspQryInstrument(self, data: dict, error: dict, reqid: int, last: bool) -> None: 12 | super().onRspQryInstrument(data, error, reqid, last) 13 | 14 | if last: 15 | self.gateway.on_event(EVENT_CONTRACT_INITED) 16 | 17 | 18 | class CxyGateway(OriginalGateway): 19 | 20 | default_name: str = "CXY" 21 | 22 | def __init__(self, event_engine: EventEngine, gateway_name: str) -> None: 23 | BaseGateway.__init__(self, event_engine, gateway_name) 24 | 25 | self.td_api: "CxyTdApi" = CxyTdApi(self) 26 | self.md_api: "CtpMdApi" = CtpMdApi(self) 27 | -------------------------------------------------------------------------------- /local_manager/manager.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Set 2 | 3 | from vnpy.trader.gateway import BaseGateway 4 | from vnpy.trader.constant import Direction, Offset 5 | from vnpy.trader.object import ( 6 | PositionData, TradeData, OrderData, ContractData 7 | ) 8 | 9 | 10 | class LocalManager: 11 | 12 | def __init__(self, gateway: BaseGateway): 13 | """""" 14 | self.gateway = gateway 15 | 16 | self.contracts: Dict[str, ContractData] = {} 17 | self.positions: Dict[str, PositionData] = {} 18 | self.vt_orderids: Set[str] = set() 19 | 20 | # Patch gateway callbacks 21 | self._on_position = gateway.on_position 22 | self._on_contract = gateway.on_contract 23 | self._on_trade = gateway.on_trade 24 | self._on_order = gateway.on_order 25 | 26 | gateway.on_position = self.on_position 27 | gateway.on_contract = self.on_contract 28 | gateway.on_trade = self.on_trade 29 | gateway.on_order = self.on_order 30 | 31 | def on_position(self, position: PositionData): 32 | """""" 33 | self.positions[position.vt_positionid] = position 34 | 35 | self._on_position(position) 36 | 37 | def on_trade(self, trade: TradeData): 38 | """""" 39 | self._on_trade(trade) 40 | 41 | if trade.direction == Direction.LONG: 42 | if trade.offset == Offset.OPEN: 43 | position = self.get_position(trade.vt_symbol, Direction.LONG) 44 | else: 45 | position = self.get_position(trade.vt_symbol, Direction.SHORT) 46 | else: 47 | if trade.offset == Offset.OPEN: 48 | position = self.get_position(trade.vt_symbol, Direction.SHORT) 49 | else: 50 | position = self.get_position(trade.vt_symbol, Direction.LONG) 51 | 52 | if trade.offset == Offset.OPEN: 53 | old_cost = position.price * position.volume 54 | new_cost = old_cost + trade.price * trade.volume 55 | 56 | position.volume += trade.volume 57 | 58 | position.price = new_cost / position.volume 59 | else: 60 | position.volume -= trade.volume 61 | if not position.volume: 62 | position.price = 0 63 | position.pnl = 0 64 | 65 | if trade.vt_orderid in self.vt_orderids: 66 | position.frozen -= trade.volume 67 | 68 | self._on_position(position) 69 | 70 | def on_order(self, order: OrderData): 71 | """""" 72 | self._on_order(order) 73 | 74 | if order.offset == Offset.CLOSE: 75 | if order.direction == Direction.LONG: 76 | position = self.get_position(order.vt_symbol, Direction.SHORT) 77 | else: 78 | position = self.get_position(order.vt_symbol, Direction.LONG) 79 | 80 | volume_change = order.volume - order.traded 81 | 82 | # Freeze new order volume 83 | if (order.vt_orderid not in self.vt_orderids and order.is_active()): 84 | self.vt_orderids.add(order.vt_orderid) 85 | position.frozen += volume_change 86 | # Unfreeze cancelled volume 87 | elif (order.vt_orderid in self.vt_orderids and not order.is_active()): 88 | position.frozen -= volume_change 89 | 90 | self._on_position(position) 91 | 92 | def on_contract(self, contract: ContractData): 93 | """""" 94 | self.contracts[contract.vt_symbol] = contract 95 | self._on_contract(contract) 96 | 97 | def get_position(self, vt_symbol: str, direction: Direction) -> PositionData: 98 | """""" 99 | vt_positionid = f"{vt_symbol}.{direction.value}" 100 | position = self.positions.get(vt_positionid, None) 101 | 102 | if not position: 103 | contract = self.contracts[vt_symbol] 104 | 105 | position = PositionData( 106 | symbol=contract.symbol, 107 | exchange=contract.exchange, 108 | direction=direction, 109 | gateway_name=contract.gateway_name 110 | ) 111 | 112 | return position 113 | -------------------------------------------------------------------------------- /local_manager/run.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from vnpy.event import EventEngine 3 | from vnpy.trader.engine import MainEngine 4 | from vnpy.trader.ui import MainWindow, create_qapp 5 | from vnpy.gateway.ctp import CtpGateway 6 | from vnpy.app.cta_strategy import CtaStrategyApp 7 | from vnpy.app.cta_backtester import CtaBacktesterApp 8 | 9 | from manager import LocalManager 10 | 11 | 12 | def main(): 13 | """""" 14 | qapp = create_qapp() 15 | 16 | event_engine = EventEngine() 17 | main_engine = MainEngine(event_engine) 18 | 19 | ctp_gateway = main_engine.add_gateway(CtpGateway) # 初始化CTP接口,并获取对象实例 20 | ctp_manager = LocalManager(ctp_gateway) # 给CTP接口添加本地持仓计算功能 21 | 22 | main_engine.add_app(CtaStrategyApp) 23 | main_engine.add_app(CtaBacktesterApp) 24 | 25 | main_window = MainWindow(main_engine, event_engine) 26 | main_window.showMaximized() 27 | 28 | qapp.exec() 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /my_simplestrategy/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from vnpy.trader.app import BaseApp 4 | 5 | from .engine import StrategyEngine 6 | 7 | 8 | __version__ = "0.0.1" 9 | 10 | 11 | class SimpleStrategyApp(BaseApp): 12 | """""" 13 | 14 | app_name: str = "SimpleStrategy" 15 | app_module: str = __module__ 16 | app_path: Path = Path(__file__).parent 17 | display_name: str = "简单策略" 18 | engine_class: StrategyEngine = StrategyEngine 19 | widget_name: str = "SimpleWidget" 20 | icon_name: str = str(app_path.joinpath("simple.ico")) -------------------------------------------------------------------------------- /my_simplestrategy/base.py: -------------------------------------------------------------------------------- 1 | EVENT_STRATEGY = "eMyStrategy" 2 | -------------------------------------------------------------------------------- /my_simplestrategy/engine.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from vnpy.event import EventEngine, Event 4 | from vnpy.trader.event import ( 5 | EVENT_LOG, EVENT_TICK, 6 | EVENT_ORDER, EVENT_TRADE 7 | ) 8 | from vnpy.trader.constant import ( 9 | Direction, OrderType, Offset 10 | ) 11 | from vnpy.trader.object import ( 12 | TickData, LogData, SubscribeRequest, 13 | ContractData, OrderRequest, 14 | OrderData, TradeData 15 | ) 16 | from vnpy.trader.engine import MainEngine, BaseEngine 17 | 18 | from .base import EVENT_STRATEGY 19 | from .template import StrategyTemplate 20 | from .strategy import SimpleStrategy 21 | 22 | 23 | class StrategyEngine(BaseEngine): 24 | """策略引擎""" 25 | 26 | def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None: 27 | """""" 28 | super().__init__(main_engine, event_engine, "SimpleStrategy") 29 | 30 | self.strategies: dict[str, StrategyTemplate] = {} 31 | self.subscribed: set[str] = set() 32 | 33 | self.event_engine.register(EVENT_TICK, self.process_tick_event) 34 | self.event_engine.register(EVENT_ORDER, self.process_order_event) 35 | self.event_engine.register(EVENT_TRADE, self.process_trade_event) 36 | 37 | self.load_setting() 38 | 39 | def load_setting(self) -> None: 40 | """加载交易代码配置""" 41 | with open("c:\\Github\\code_demo\\setting.json") as f: 42 | vt_symbols = json.load(f) 43 | for s in vt_symbols: 44 | strategy = SimpleStrategy(self, s) 45 | self.strategies[s] = strategy 46 | 47 | def process_tick_event(self, event: Event) -> None: 48 | """行情事件""" 49 | tick: TickData = event.data 50 | 51 | strategy = self.strategies.get(tick.vt_symbol, None) 52 | 53 | if strategy: 54 | strategy.on_tick(tick) 55 | 56 | def process_order_event(self, event: Event) -> None: 57 | """委托事件""" 58 | order: OrderData = event.data 59 | 60 | strategy = self.strategies.get(order.vt_symbol, None) 61 | 62 | if strategy: 63 | strategy.on_order(order) 64 | 65 | def process_trade_event(self, event: Event) -> None: 66 | """成交事件""" 67 | trade: TradeData = event.data 68 | 69 | strategy = self.strategies.get(trade.vt_symbol, None) 70 | 71 | if strategy: 72 | strategy.on_trade(trade) 73 | 74 | def process_contract_event(self, event: Event) -> None: 75 | """合约事件""" 76 | contract: ContractData = event.data 77 | 78 | # 订阅策略合约行情 79 | if contract.vt_symbol in self.strategies: 80 | req = SubscribeRequest(contract.symbol, contract.exchange) 81 | self.main_engine.subscribe(req, contract.gateway_name) 82 | 83 | def send_order( 84 | self, 85 | vt_symbol: str, 86 | price: float, 87 | volume: int, 88 | direction: Direction, 89 | offset: Offset 90 | ) -> None: 91 | """发送委托""" 92 | contract = self.main_engine.get_contract(vt_symbol) 93 | 94 | req = OrderRequest( 95 | symbol=contract.symbol, 96 | exchange=contract.exchange, 97 | direction=direction, 98 | type=OrderType.LIMIT, 99 | price=price, 100 | volume=volume, 101 | offset=offset 102 | ) 103 | self.main_engine.send_order(req, contract.gateway_name) 104 | 105 | def write_log(self, msg: str) -> None: 106 | """输出日志""" 107 | log = LogData(msg=msg, gateway_name="STRATEGY") 108 | event = Event(EVENT_LOG, log) 109 | self.event_engine.put(event) 110 | 111 | def put_event(self, data: dict) -> None: 112 | """推送事件""" 113 | event = Event(EVENT_STRATEGY, data) 114 | self.event_engine.put(event) 115 | -------------------------------------------------------------------------------- /my_simplestrategy/simple.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vnpy/code_demo/0fb7aebaa8d6504cc322fb25751d770aa9eb83ac/my_simplestrategy/simple.ico -------------------------------------------------------------------------------- /my_simplestrategy/strategy.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from vnpy.trader.object import TickData 4 | 5 | from .template import StrategyTemplate 6 | 7 | if TYPE_CHECKING: # 只在编辑代码时导入 8 | from .engine import StrategyEngine 9 | 10 | 11 | class SimpleStrategy(StrategyTemplate): 12 | """简单策略""" 13 | 14 | def __init__(self, strategy_engine: "StrategyEngine", vt_symbol: str) -> None: 15 | """""" 16 | super().__init__(strategy_engine, vt_symbol) 17 | 18 | self.tick_history: list[TickData] = [] 19 | self.trading_target: int = 0 20 | 21 | def on_tick(self, tick: TickData) -> None: 22 | """行情推送""" 23 | # 检查至少要3个Tick 24 | history = self.tick_history 25 | if len(history) < 3: 26 | return 27 | 28 | # 提取行情和合约 29 | tick1 = history[-1] 30 | tick2 = history[-2] 31 | tick3 = history[-3] 32 | 33 | # 多头检查 34 | if tick1.last_price > tick2.last_price > tick3.last_price: 35 | if not self.trading_targets: 36 | self.buy(tick1.last_price + 10, 1) 37 | self.trading_target = 1 38 | self.write_log(f"{self.vt_symbol}买入开仓1手 {tick1.datetime}") 39 | 40 | # 空头检查 41 | if tick1.last_price < tick2.last_price < tick3.last_price: 42 | if self.trading_target > 0: 43 | self.sell(tick1.last_price - 10, 1) 44 | self.trading_target = 0 45 | self.write_log(f"{self.vt_symbol}卖出平仓1手 {tick1.datetime}") 46 | 47 | # 推送事件 48 | data = { 49 | "vt_symbol": self.vt_symbol, 50 | "datetime": tick1.datetime, 51 | "last_price": tick1.last_price, 52 | "trading_target": self.trading_target 53 | } 54 | self.put_event(data) 55 | -------------------------------------------------------------------------------- /my_simplestrategy/template.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from vnpy.trader.object import TickData, TradeData, OrderData 4 | from vnpy.trader.constant import Direction, Offset 5 | 6 | if TYPE_CHECKING: # 只在编辑代码时导入 7 | from .engine import StrategyEngine 8 | 9 | 10 | class StrategyTemplate: 11 | """策略开发模板""" 12 | 13 | def __init__( 14 | self, 15 | strategy_engine: "StrategyEngine", 16 | vt_symbol: str 17 | ) -> None: 18 | """""" 19 | self.strategy_engine: "StrategyEngine" = strategy_engine 20 | self.trading_symbol: str = vt_symbol 21 | 22 | def on_tick(self, tick: TickData) -> None: 23 | """行情推送""" 24 | pass 25 | 26 | def on_order(self, order: OrderData) -> None: 27 | """委托推送""" 28 | pass 29 | 30 | def on_trade(self, trade: TradeData) -> None: 31 | """成交推送""" 32 | pass 33 | 34 | def buy(self, price: float, volume: int) -> None: 35 | """买入开仓""" 36 | self.strategy_engine.send_order( 37 | self.trading_symbol, 38 | price, 39 | volume, 40 | Direction.LONG, 41 | Offset.OPEN 42 | ) 43 | 44 | def sell(self, price: float, volume: int) -> None: 45 | """卖出平仓""" 46 | self.strategy_engine.send_order( 47 | self.trading_symbol, 48 | price, 49 | volume, 50 | Direction.SHORT, 51 | Offset.CLOSE 52 | ) 53 | 54 | def short(self, price: float, volume: int) -> None: 55 | """卖出开仓""" 56 | self.strategy_engine.send_order( 57 | self.trading_symbol, 58 | price, 59 | volume, 60 | Direction.SHORT, 61 | Offset.OPEN 62 | ) 63 | 64 | def cover(self, price: float, volume: int) -> None: 65 | """买入平仓""" 66 | self.strategy_engine.send_order( 67 | self.trading_symbol, 68 | price, 69 | volume, 70 | Direction.LONG, 71 | Offset.CLOSE 72 | ) 73 | 74 | def write_log(self, msg: str) -> None: 75 | """输出日志""" 76 | self.strategy_engine.write_log(msg) 77 | 78 | def put_event(self, data: dict) -> None: 79 | """推送事件""" 80 | self.strategy_engine.put_event(data) 81 | -------------------------------------------------------------------------------- /my_simplestrategy/ui.py: -------------------------------------------------------------------------------- 1 | from PySide6 import QtWidgets, QtCore 2 | 3 | from vnpy.event import EventEngine, Event 4 | from vnpy.trader.event import ( 5 | EVENT_LOG 6 | ) 7 | from vnpy.trader.constant import ( 8 | Exchange 9 | ) 10 | from vnpy.trader.object import ( 11 | TickData, LogData, SubscribeRequest 12 | ) 13 | from vnpy.trader.engine import MainEngine 14 | 15 | from .base import EVENT_STRATEGY 16 | 17 | 18 | class SimpleWidget(QtWidgets.QWidget): 19 | """简单图形控件""" 20 | 21 | signal_log = QtCore.Signal(Event) 22 | signal_tick = QtCore.Signal(Event) 23 | 24 | def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None: 25 | """构造函数""" 26 | super().__init__() # 这里要首先调用Qt对象C++中的构造函数 27 | 28 | self.main_engine: MainEngine = main_engine 29 | self.event_engine: EventEngine = event_engine 30 | 31 | # 连接信号槽 32 | self.signal_log.connect(self.process_log_event) 33 | self.signal_tick.connect(self.process_tick_event) 34 | 35 | self.event_engine.register(EVENT_LOG, self.signal_log.emit) 36 | # self.event_engine.register(EVENT_TICK, self.signal_tick.emit) 37 | 38 | # 基础图形控件 39 | self.log_monitor: QtWidgets.QTextEdit = QtWidgets.QTextEdit() 40 | self.subscribe_button: QtWidgets.QPushButton = QtWidgets.QPushButton("订阅") 41 | self.symbol_line: QtWidgets.QLineEdit = QtWidgets.QLineEdit() 42 | 43 | self.strategy_monitor: StrategyMonitor = StrategyMonitor(event_engine) 44 | 45 | # 连接按钮函数 46 | self.subscribe_button.clicked.connect(self.subscribe_symbol) 47 | 48 | # 设置布局组合 49 | vbox: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout() 50 | vbox.addWidget(self.log_monitor) 51 | vbox.addWidget(self.symbol_line) 52 | vbox.addWidget(self.subscribe_button) 53 | vbox.addWidget(self.strategy_monitor) 54 | 55 | self.setLayout(vbox) 56 | 57 | def subscribe_symbol(self) -> None: 58 | """订阅行情""" 59 | vt_symbol: str = self.symbol_line.text() 60 | symbol, exchange_str = vt_symbol.split(".") 61 | 62 | req = SubscribeRequest( 63 | symbol=symbol, 64 | exchange=Exchange(exchange_str) 65 | ) 66 | self.gateway.subscribe(req) 67 | 68 | def process_log_event(self, event: Event) -> None: 69 | """更新日志""" 70 | log: LogData = event.data 71 | self.log_monitor.append(log.msg) 72 | 73 | def process_tick_event(self, event: Event) -> None: 74 | """更新行情""" 75 | tick: TickData = event.data 76 | self.log_monitor.append(str(tick)) 77 | 78 | 79 | class StrategyMonitor(QtWidgets.QTableWidget): 80 | """策略监控表格""" 81 | 82 | signal = QtCore.Signal(Event) 83 | 84 | def __init__(self, event_engine: EventEngine) -> None: 85 | """""" 86 | super().__init__() 87 | 88 | self.signal.connect(self.process_strategy_event) 89 | self.event_engine: EventEngine = event_engine 90 | self.event_engine.register(EVENT_STRATEGY, self.signal.emit) 91 | 92 | labels = ["代码", "最新价", "时间", "目标仓位"] 93 | self.setColumnCount(len(labels)) 94 | self.setHorizontalHeaderLabels(labels) # 水平表头文字 95 | self.verticalHeader().setVisible(False) # 垂直表头隐藏 96 | self.setEditTriggers(self.NoEditTriggers) # 禁止内容编辑 97 | 98 | self.rows: dict[str, dict[str, QtWidgets.QTableWidgetItem]] = {} 99 | 100 | def process_strategy_event(self, event: Event) -> None: 101 | """处理策略事件""" 102 | data: dict = event.data 103 | 104 | # 如果是新收到的策略数据,则插入一行 105 | if data["vt_symbol"] not in self.rows: 106 | self.insertRow(0) 107 | 108 | symbol_cell = QtWidgets.QTableWidgetItem(str(data["vt_symbol"])) 109 | datetime_cell = QtWidgets.QTableWidgetItem(str(data["datetime"])) 110 | price_cell = QtWidgets.QTableWidgetItem(str(data["last_price"])) 111 | target_cell = QtWidgets.QTableWidgetItem(str(data["trading_target"])) 112 | 113 | self.setItem(0, 0, symbol_cell) 114 | self.setItem(0, 1, datetime_cell) 115 | self.setItem(0, 2, price_cell) 116 | self.setItem(0, 3, target_cell) 117 | 118 | self.rows[data["vt_symbol"]] = { 119 | "vt_symbol": symbol_cell, 120 | "datetime": datetime_cell, 121 | "last_price": price_cell, 122 | "trading_target": target_cell 123 | } 124 | # 如果是已有的策略数据,则直接更新 125 | else: 126 | row = self.rows[data["vt_symbol"]] 127 | 128 | row["vt_symbol"].setText(str(data["vt_symbol"])) 129 | row["datetime"].setText(str(data["datetime"])) 130 | row["last_price"].setText(str(data["last_price"])) 131 | row["trading_target"].setText(str(data["trading_target"])) 132 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | from PySide6 import QtWidgets 2 | 3 | from vnpy.event import EventEngine 4 | from vnpy.trader.engine import MainEngine 5 | from vnpy_ctp import CtpGateway 6 | from vnpy_ib import IbGateway 7 | 8 | from ui import SimpleWidget 9 | from engine import StrategyEngine 10 | 11 | 12 | def main(): 13 | """主函数""" 14 | # 创建Qt应用 15 | app: QtWidgets.QApplication = QtWidgets.QApplication() 16 | 17 | # 创建并启动事件引擎 18 | event_engine: EventEngine = EventEngine() 19 | 20 | # 创建主引擎 21 | main_engine: MainEngine = MainEngine(event_engine) 22 | main_engine.add_gateway(CtpGateway) 23 | main_engine.add_gateway(IbGateway) 24 | 25 | # 创建策略引擎 26 | engine: StrategyEngine = StrategyEngine(main_engine, event_engine) # noqa 27 | 28 | # 创建图形控件 29 | widget: SimpleWidget = SimpleWidget(main_engine, event_engine) 30 | widget.show() 31 | 32 | # CTP交易接口 33 | ctp_setting = { 34 | "用户名": "000300", 35 | "密码": "vnpy1234", 36 | "经纪商代码": "9999", 37 | "交易服务器": "180.168.146.187:10130", 38 | "行情服务器": "180.168.146.187:10131", 39 | "产品名称": "simnow_client_test", 40 | "授权编码": "0000000000000000" 41 | } 42 | main_engine.connect(ctp_setting, "CTP") 43 | 44 | # 启动主线程UI循环 45 | app.exec() 46 | 47 | # 关闭主引擎 48 | main_engine.close() 49 | 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /run_trader.py: -------------------------------------------------------------------------------- 1 | from vnpy.event import EventEngine 2 | from vnpy.trader.engine import MainEngine 3 | from vnpy.trader.ui import MainWindow, create_qapp 4 | 5 | from vnpy_ctp import CtpGateway 6 | 7 | from my_simplestrategy import SimpleStrategyApp 8 | 9 | 10 | def main(): 11 | """Start VeighNa Trader""" 12 | qapp = create_qapp() 13 | 14 | event_engine = EventEngine() 15 | main_engine = MainEngine(event_engine) 16 | 17 | main_engine.add_gateway(CtpGateway) 18 | main_engine.add_app(SimpleStrategyApp) 19 | 20 | main_window = MainWindow(main_engine, event_engine) 21 | main_window.showMaximized() 22 | 23 | qapp.exec() 24 | 25 | 26 | if __name__ == "__main__": 27 | main() -------------------------------------------------------------------------------- /setting.json: -------------------------------------------------------------------------------- 1 | [ 2 | "IF2209.CFFEX", 3 | "IH2209.CFFEX", 4 | "IC2209.CFFEX" 5 | ] -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = my_simplestrategy 3 | version = attr: my_simplestrategy.__version__ 4 | author = Xiaoyou Chen 5 | long_description = file: README.md 6 | long_description_content_type = text/markdown 7 | keywords = 8 | quant 9 | quantitative 10 | investment 11 | trading 12 | algotrading 13 | classifiers = 14 | Development Status :: 5 - Production/Stable 15 | Operating System :: OS Independent 16 | Programming Language :: Python :: 3 17 | Programming Language :: Python :: 3.7 18 | Programming Language :: Python :: 3.8 19 | Programming Language :: Python :: 3.9 20 | Programming Language :: Python :: 3.10 21 | Topic :: Office/Business :: Financial :: Investment 22 | Programming Language :: Python :: Implementation :: CPython 23 | License :: OSI Approved :: MIT License 24 | Natural Language :: Chinese (Simplified) 25 | 26 | [options] 27 | packages = find: 28 | zip_safe = False 29 | install_requires = 30 | vnpy 31 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup() 5 | --------------------------------------------------------------------------------