├── setup.py ├── .flake8 ├── script └── run.py ├── setup.cfg ├── LICENSE ├── vnpy_deribit ├── __init__.py └── deribit_gateway.py ├── README.md └── .gitignore /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup() 5 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /script/run.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_deribit import DeribitGateway 6 | 7 | 8 | def main(): 9 | """主入口函数""" 10 | qapp = create_qapp() 11 | 12 | event_engine = EventEngine() 13 | main_engine = MainEngine(event_engine) 14 | main_engine.add_gateway(DeribitGateway) 15 | 16 | main_window = MainWindow(main_engine, event_engine) 17 | main_window.showMaximized() 18 | 19 | qapp.exec() 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = vnpy_deribit 3 | version = 2.0.1.1 4 | url = https://www.vnpy.com 5 | license = MIT 6 | author = vn-crypto 7 | author_email = vn.crypto@outlook.com 8 | description = Deribit gateway for vn.py quant trading framework. 9 | long_description = file: README.md 10 | long_description_content_type = text/markdown 11 | keywords = 12 | quant 13 | quantitative 14 | investment 15 | trading 16 | algotrading 17 | classifiers = 18 | Development Status :: 5 - Production/Stable 19 | Operating System :: OS Independent 20 | Programming Language :: Python :: 3 21 | Programming Language :: Python :: 3.7 22 | Programming Language :: Python :: 3.8 23 | Programming Language :: Python :: 3.9 24 | Programming Language :: Python :: 3.10 25 | Topic :: Office/Business :: Financial :: Investment 26 | Programming Language :: Python :: Implementation :: CPython 27 | License :: OSI Approved :: MIT License 28 | Natural Language :: Chinese (Simplified) 29 | 30 | [options] 31 | packages = find: 32 | install_requires = 33 | vnpy_websocket -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 vn-crypto 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 | -------------------------------------------------------------------------------- /vnpy_deribit/__init__.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2015-present, vn-crypto 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 | 23 | from .deribit_gateway import DeribitGateway 24 | 25 | import importlib_metadata 26 | 27 | 28 | __version__ = importlib_metadata.version("vnpy_deribit") -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vn.py框架的Deribit底层接口 2 | 3 |

4 | 5 |

6 | 7 |

8 | 9 | 10 | 11 | 12 |

13 | 14 | 关于使用VeighNa框架进行Crypto交易的话题,新开了一个[Github Discussions论坛](https://github.com/vn-crypto/vnpy_crypto/discussions),欢迎通过这里来进行讨论交流。 15 | 16 | ## 说明 17 | 18 | 基于DERIBIT交易所的接口开发,支持期货、永续、期权交易。 19 | 20 | ## 安装 21 | 22 | 下载解压后在cmd中运行 23 | 24 | ``` 25 | python setup.py install 26 | ``` 27 | 28 | ## 使用 29 | 30 | 以脚本方式启动(script/run.py): 31 | 32 | ``` 33 | from vnpy.event import EventEngine 34 | from vnpy.trader.engine import MainEngine 35 | from vnpy.trader.ui import MainWindow, create_qapp 36 | 37 | from vnpy_deribit import DeribitGateway 38 | 39 | 40 | def main(): 41 | """主入口函数""" 42 | qapp = create_qapp() 43 | 44 | event_engine = EventEngine() 45 | main_engine = MainEngine(event_engine) 46 | main_engine.add_gateway(DeribitGateway) 47 | 48 | main_window = MainWindow(main_engine, event_engine) 49 | main_window.showMaximized() 50 | 51 | qapp.exec() 52 | 53 | 54 | if __name__ == "__main__": 55 | main() 56 | ``` 57 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /vnpy_deribit/deribit_gateway.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from copy import copy 3 | from typing import Callable, Dict, Set 4 | 5 | from tzlocal import get_localzone 6 | 7 | from vnpy.event import Event, EventEngine 8 | from vnpy.trader.object import ( 9 | TickData, 10 | OrderData, 11 | TradeData, 12 | PositionData, 13 | AccountData, 14 | ContractData, 15 | OrderRequest, 16 | CancelRequest, 17 | SubscribeRequest, 18 | HistoryRequest, 19 | ) 20 | from vnpy.trader.constant import ( 21 | Direction, 22 | Exchange, 23 | OrderType, 24 | Product, 25 | Status, 26 | OptionType 27 | ) 28 | from vnpy.trader.event import EVENT_TIMER 29 | from vnpy.trader.gateway import BaseGateway 30 | from vnpy.trader.utility import ZoneInfo 31 | from vnpy_websocket import WebsocketClient 32 | 33 | 34 | # 本地时区 35 | LOCAL_TZ: ZoneInfo = get_localzone() 36 | 37 | # 实盘和模拟盘Websocket API地址 38 | REAL_WEBSOCKET_HOST: str = "wss://www.deribit.com/ws/api/v2" 39 | TEST_WEBSOCKET_HOST: str = "wss://test.deribit.com/ws/api/v2" 40 | 41 | # 委托状态映射 42 | STATUS_DERIBIT2VT: Dict[str, Status] = { 43 | "open": Status.NOTTRADED, 44 | "filled": Status.ALLTRADED, 45 | "rejected": Status.REJECTED, 46 | "cancelled": Status.CANCELLED, 47 | } 48 | 49 | # 委托类型映射 50 | ORDERTYPE_VT2DERIBIT: Dict[OrderType, str] = { 51 | OrderType.LIMIT: "limit", 52 | OrderType.MARKET: "market", 53 | OrderType.STOP: "stop_market" 54 | } 55 | ORDERTYPE_DERIBIT2VT: Dict[str, OrderType] = {v: k for k, v in ORDERTYPE_VT2DERIBIT.items()} 56 | 57 | # 买卖方向映射 58 | DIRECTION_VT2DERIBIT: Dict[Direction, str] = { 59 | Direction.LONG: "buy", 60 | Direction.SHORT: "sell" 61 | } 62 | DIRECTION_DERIBIT2VT: Dict[str, Direction] = {v: k for k, v in DIRECTION_VT2DERIBIT.items()} 63 | 64 | # 产品类型映射 65 | PRODUCT_DERIBIT2VT: Dict[str, Product] = { 66 | "spot": Product.SPOT, 67 | "future": Product.FUTURES, 68 | "future_combo": Product.FUTURES, 69 | "option": Product.OPTION, 70 | "strike": Product.OPTION, 71 | "option_combo": Product.OPTION 72 | } 73 | 74 | # 期权类型映射 75 | OPTIONTYPE_DERIBIT2VT: Dict[str, OptionType] = { 76 | "call": OptionType.CALL, 77 | "put": OptionType.PUT 78 | } 79 | 80 | 81 | class DeribitGateway(BaseGateway): 82 | """ 83 | vn.py用于对接Deribit交易所的交易接口。 84 | """ 85 | 86 | default_name = "DERIBIT" 87 | 88 | default_setting = { 89 | "key": "", 90 | "secret": "", 91 | "代理地址": "", 92 | "代理端口": "", 93 | "服务器": ["REAL", "TEST"] 94 | } 95 | 96 | exchanges = [Exchange.DERIBIT] 97 | 98 | def __init__(self, event_engine: EventEngine, gateway_name: str = "DERIBIT") -> None: 99 | """构造函数""" 100 | super().__init__(event_engine, gateway_name) 101 | 102 | self.ws_api: "DeribitWebsocketApi" = DeribitWebsocketApi(self) 103 | 104 | def connect(self, setting: dict) -> None: 105 | """连接交易接口""" 106 | key: str = setting["key"] 107 | secret: str = setting["secret"] 108 | proxy_host: str = setting["代理地址"] 109 | proxy_port: str = setting["代理端口"] 110 | server: str = setting["服务器"] 111 | 112 | if proxy_port.isdigit(): 113 | proxy_port = int(proxy_port) 114 | else: 115 | proxy_port = 0 116 | 117 | self.ws_api.connect( 118 | key, 119 | secret, 120 | proxy_host, 121 | proxy_port, 122 | server 123 | ) 124 | 125 | self.event_engine.register(EVENT_TIMER, self.process_timer_event) 126 | 127 | def subscribe(self, req: SubscribeRequest) -> None: 128 | """订阅行情""" 129 | self.ws_api.subscribe(req) 130 | 131 | def send_order(self, req: OrderRequest) -> str: 132 | """委托下单""" 133 | return self.ws_api.send_order(req) 134 | 135 | def cancel_order(self, req: CancelRequest) -> None: 136 | """委托撤单""" 137 | return self.ws_api.cancel_order(req) 138 | 139 | def query_account(self) -> None: 140 | """查询资金""" 141 | self.ws_api.query_account() 142 | 143 | def query_position(self) -> None: 144 | """查询持仓""" 145 | pass 146 | 147 | def query_history(self, req: HistoryRequest) -> None: 148 | """查询历史数据""" 149 | pass 150 | 151 | def close(self) -> None: 152 | """关闭连接""" 153 | self.ws_api.stop() 154 | 155 | def process_timer_event(self, event: Event) -> None: 156 | """处理定时事件""" 157 | self.query_account() 158 | 159 | 160 | class DeribitWebsocketApi(WebsocketClient): 161 | """""" 162 | 163 | def __init__(self, gateway: DeribitGateway) -> None: 164 | """构造函数""" 165 | super().__init__() 166 | 167 | self.gateway: DeribitGateway = gateway 168 | self.gateway_name: str = gateway.gateway_name 169 | 170 | self.key: str = "" 171 | self.secret: str = "" 172 | 173 | self.reqid: int = 0 174 | self.reqid_callback_map: Dict[str, callable] = {} 175 | self.reqid_currency_map: Dict[str, str] = {} 176 | self.reqid_order_map: Dict[int, OrderData] = {} 177 | 178 | self.connect_time: int = 0 179 | self.order_count: int = 1000000 180 | self.localids: Set[str] = set() 181 | 182 | self.ticks: Dict[str, TickData] = {} 183 | 184 | self.callbacks: Dict[str, callable] = { 185 | "ticker": self.on_ticker, 186 | "book": self.on_orderbook, 187 | "user": self.on_user_update, 188 | } 189 | 190 | def connect( 191 | self, 192 | key: str, 193 | secret: str, 194 | proxy_host: str, 195 | proxy_port: int, 196 | server: str 197 | ) -> None: 198 | """连接服务器""" 199 | self.key = key 200 | self.secret = secret 201 | 202 | self.connect_time = ( 203 | int(datetime.now().strftime("%y%m%d%H%M%S")) * self.order_count 204 | ) 205 | 206 | if server == "REAL": 207 | self.init(REAL_WEBSOCKET_HOST, proxy_host, proxy_port) 208 | else: 209 | self.init(TEST_WEBSOCKET_HOST, proxy_host, proxy_port) 210 | 211 | self.start() 212 | 213 | def subscribe(self, req: SubscribeRequest) -> None: 214 | """订阅行情""" 215 | symbol: str = req.symbol 216 | 217 | # 过滤重复的订阅 218 | if symbol in self.ticks: 219 | return 220 | 221 | # 创建TICK对象 222 | tick: TickData = TickData( 223 | gateway_name=self.gateway_name, 224 | symbol=symbol, 225 | name=symbol, 226 | exchange=Exchange.DERIBIT, 227 | datetime=datetime.now(LOCAL_TZ), 228 | ) 229 | self.ticks[symbol] = tick 230 | 231 | # 发出订阅请求 232 | params: dict = { 233 | "channels": [ 234 | f"ticker.{symbol}.100ms", 235 | f"book.{symbol}.none.10.100ms" 236 | ] 237 | } 238 | 239 | self.send_request("public/subscribe", params) 240 | 241 | def send_order(self, req: OrderRequest) -> str: 242 | """委托下单""" 243 | # 检查委托类型是否正确 244 | if req.type not in ORDERTYPE_VT2DERIBIT: 245 | self.gateway.write_log(f"委托失败,不支持的委托类型:{req.type.value}") 246 | return 247 | 248 | # 生成本地委托号 249 | self.order_count += 1 250 | orderid: str = str(self.connect_time + self.order_count) 251 | self.localids.add(orderid) 252 | 253 | side: str = DIRECTION_VT2DERIBIT[req.direction] 254 | method: str = "private/" + side 255 | 256 | # 生成委托请求 257 | params: dict = { 258 | "instrument_name": req.symbol, 259 | "amount": req.volume, 260 | "type": ORDERTYPE_VT2DERIBIT[req.type], 261 | "label": orderid, 262 | "price": req.price 263 | } 264 | 265 | reqid: str = self.send_request( 266 | method, 267 | params, 268 | self.on_send_order 269 | ) 270 | 271 | # 推送委托提交中状态 272 | order: OrderData = req.create_order_data(orderid, self.gateway_name) 273 | self.gateway.on_order(order) 274 | 275 | self.reqid_order_map[reqid] = order 276 | 277 | return order.vt_orderid 278 | 279 | def cancel_order(self, req: CancelRequest) -> None: 280 | """委托撤单""" 281 | # 如果委托号为本地委托号 282 | if req.orderid in self.localids: 283 | self.send_request( 284 | "private/cancel_by_label", 285 | {"label": req.orderid}, 286 | self.on_cancel_order 287 | ) 288 | # 如果委托号为系统委托号 289 | else: 290 | self.send_request( 291 | "private/cancel", 292 | {"order_id": req.orderid}, 293 | self.on_cancel_order 294 | ) 295 | 296 | def set_heartbeat(self) -> None: 297 | """设置心跳周期""" 298 | self.send_request("public/set_heartbeat", {"interval": 10}) 299 | 300 | def test_request(self) -> None: 301 | """测试联通性的请求""" 302 | self.send_request("/public/test", {}) 303 | 304 | def get_access_token(self) -> None: 305 | """获取访问令牌""" 306 | params: dict = { 307 | "grant_type": "client_credentials", 308 | "client_id": self.key, 309 | "client_secret": self.secret 310 | } 311 | 312 | self.send_request( 313 | "public/auth", 314 | params, 315 | self.on_access_token 316 | ) 317 | 318 | def query_instrument(self) -> None: 319 | """查询合约信息""" 320 | for currency in ["BTC", "ETH"]: 321 | params: dict = { 322 | "currency": currency, 323 | "expired": False, 324 | } 325 | 326 | self.send_request( 327 | "public/get_instruments", 328 | params, 329 | self.on_query_instrument 330 | ) 331 | 332 | def query_account(self) -> None: 333 | """查询资金""" 334 | for currency in ["BTC", "ETH", "USDC"]: 335 | params: dict = {"currency": currency} 336 | 337 | self.send_request( 338 | "private/get_account_summary", 339 | params, 340 | self.on_query_account 341 | ) 342 | 343 | def subscribe_topic(self) -> None: 344 | """订阅委托和成交回报""" 345 | params: dict = {"channels": ["user.changes.any.any.raw"]} 346 | self.send_request("private/subscribe", params) 347 | 348 | def query_position(self) -> None: 349 | """查询持仓""" 350 | for currency in ["BTC", "ETH"]: 351 | params: dict = {"currency": currency} 352 | 353 | self.send_request( 354 | "private/get_positions", 355 | params, 356 | self.on_query_position 357 | ) 358 | 359 | def query_order(self) -> None: 360 | """查询未成交委托""" 361 | for currency in ["BTC", "ETH"]: 362 | params: dict = {"currency": currency} 363 | 364 | self.send_request( 365 | "private/get_open_orders_by_currency", 366 | params, 367 | self.on_query_order 368 | ) 369 | 370 | def on_connected(self) -> None: 371 | """连接成功回报""" 372 | self.gateway.write_log("服务器连接成功") 373 | 374 | self.get_access_token() 375 | self.query_instrument() 376 | 377 | # 重新订阅之前已订阅的行情 378 | channels: list = [] 379 | for symbol in self.ticks.keys(): 380 | channels.append(f"ticker.{symbol}.100ms") 381 | channels.append(f"book.{symbol}.none.10.100ms") 382 | 383 | params: dict = {"channels": channels} 384 | self.send_request("public/subscribe", params) 385 | 386 | def on_disconnected(self) -> None: 387 | """连接断开回报""" 388 | self.gateway.write_log("服务器连接断开") 389 | 390 | def on_packet(self, packet: dict) -> None: 391 | """推送数据回报""" 392 | # 请求回报 393 | if "id" in packet: 394 | packet_id: int = packet["id"] 395 | 396 | if packet_id in self.reqid_callback_map.keys(): 397 | callback: callable = self.reqid_callback_map[packet_id] 398 | callback(packet) 399 | # 订阅推送 400 | elif "params" in packet: 401 | params: dict = packet["params"] 402 | 403 | # 真实数据推送 404 | if "channel" in params: 405 | channel: str = params["channel"] 406 | kind: str = channel.split(".")[0] 407 | callback: callable = self.callbacks[kind] 408 | callback(packet) 409 | # 连接心跳推送 410 | elif "type" in params: 411 | type_: str = params["type"] 412 | 413 | # 响应心跳回复 414 | if type_ == "test_request": 415 | self.test_request() 416 | 417 | def on_error(self, exception_type: type, exception_value: Exception, tb): 418 | """触发异常回调""" 419 | msg: str = self.exception_detail(exception_type, exception_value, tb) 420 | self.gateway.write_log(f"触发异常:{msg}") 421 | print(msg) 422 | 423 | def on_access_token(self, packet: dict) -> None: 424 | """登录请求回报""" 425 | error: dict = packet.get("error", None) 426 | if error: 427 | code: int = error["code"] 428 | msg: str = error["message"] 429 | self.gateway.write_log(f"服务器登录失败,状态码:{code}, 信息:{msg}") 430 | return 431 | 432 | self.gateway.write_log("服务器登录成功") 433 | 434 | self.set_heartbeat() 435 | self.subscribe_topic() 436 | self.query_position() 437 | self.query_account() 438 | self.query_order() 439 | 440 | def on_query_instrument(self, packet: dict) -> None: 441 | """合约查询回报""" 442 | error: dict = packet.get("error", None) 443 | if error: 444 | msg: str = error["message"] 445 | self.gateway.write_log(f"合约查询失败,信息:{msg}") 446 | return 447 | 448 | currency: str = self.reqid_currency_map[packet["id"]] 449 | 450 | for d in packet["result"]: 451 | contract: ContractData = ContractData( 452 | symbol=d["instrument_name"], 453 | exchange=Exchange.DERIBIT, 454 | name=d["instrument_name"], 455 | product=PRODUCT_DERIBIT2VT[d["kind"]], 456 | pricetick=d["tick_size"], 457 | size=d["contract_size"], 458 | min_volume=d["min_trade_amount"], 459 | net_position=True, 460 | history_data=False, 461 | gateway_name=self.gateway_name, 462 | ) 463 | 464 | if contract.product == Product.OPTION: 465 | option_expiry = datetime.fromtimestamp( 466 | d["expiration_timestamp"] / 1000 467 | ) 468 | option_underlying = "_".join([ 469 | d["base_currency"], 470 | option_expiry.strftime("%Y%m%d") 471 | ]) 472 | 473 | contract.option_portfolio = d["base_currency"] 474 | if "strike" in d: 475 | contract.option_strike = d["strike"] 476 | contract.option_index = str(d["strike"]) 477 | contract.option_underlying = option_underlying 478 | if "option_type" in d: 479 | contract.option_type = OPTIONTYPE_DERIBIT2VT[d["option_type"]] 480 | contract.option_expiry = option_expiry 481 | 482 | self.gateway.on_contract(contract) 483 | 484 | self.gateway.write_log(f"{currency}合约信息查询成功") 485 | 486 | def on_query_position(self, packet: dict) -> None: 487 | """持仓查询回报""" 488 | error: dict = packet.get("error", None) 489 | if error: 490 | msg: str = error["message"] 491 | code: int = error["code"] 492 | self.gateway.write_log(f"持仓查询失败,状态码:{code}, 信息:{msg}") 493 | return 494 | 495 | data: list = packet["result"] 496 | currency: str = self.reqid_currency_map[packet["id"]] 497 | 498 | for pos in data: 499 | position: PositionData = PositionData( 500 | symbol=pos["instrument_name"], 501 | exchange=Exchange.DERIBIT, 502 | direction=Direction.NET, 503 | volume=pos["size"], 504 | pnl=pos["total_profit_loss"], 505 | gateway_name=self.gateway_name, 506 | ) 507 | self.gateway.on_position(position) 508 | 509 | self.gateway.write_log(f"{currency}持仓查询成功") 510 | 511 | def on_query_account(self, packet: dict) -> None: 512 | """资金查询回报""" 513 | error: dict = packet.get("error", None) 514 | if error: 515 | code: int = error["code"] 516 | msg: str = error["message"] 517 | self.gateway.write_log(f"资金查询失败,状态码:{code}, 信息:{msg}") 518 | return 519 | 520 | data: dict = packet["result"] 521 | currency: str = data["currency"] 522 | 523 | account: AccountData = AccountData( 524 | accountid=currency, 525 | balance=data["balance"], 526 | frozen=data["balance"] - data["available_funds"], 527 | gateway_name=self.gateway_name, 528 | ) 529 | self.gateway.on_account(account) 530 | 531 | def on_query_order(self, packet: dict) -> None: 532 | """未成交委托查询回报""" 533 | error: dict = packet.get("error", None) 534 | if error: 535 | msg: str = error["message"] 536 | code: int = error["code"] 537 | self.gateway.write_log(f"未成交委托查询失败,状态码:{code}, 信息:{msg}") 538 | return 539 | 540 | data: list = packet["result"] 541 | currency: str = self.reqid_currency_map[packet["id"]] 542 | 543 | for d in data: 544 | self.on_order(d) 545 | 546 | self.gateway.write_log(f"{currency}委托查询成功") 547 | 548 | def on_send_order(self, packet: dict) -> None: 549 | """委托下单回报""" 550 | error: dict = packet.get("error", None) 551 | if not error: 552 | return 553 | 554 | msg: str = error["message"] 555 | code: int = error["code"] 556 | 557 | self.gateway.write_log( 558 | f"委托失败,代码:{code},信息:{msg}" 559 | ) 560 | 561 | order: OrderData = self.reqid_order_map[packet["id"]] 562 | order.status = Status.REJECTED 563 | self.gateway.on_order(order) 564 | 565 | def on_cancel_order(self, packet: dict) -> None: 566 | """委托撤单回报""" 567 | error: dict = packet.get("error", None) 568 | if error: 569 | msg: str = error["message"] 570 | self.gateway.write_log(f"撤单失败,信息:{msg}") 571 | 572 | def on_user_update(self, packet: dict) -> None: 573 | """用户更新推送""" 574 | data: dict = packet["params"]["data"] 575 | 576 | for order in data["orders"]: 577 | self.on_order(order) 578 | 579 | for trade in data["trades"]: 580 | self.on_trade(trade) 581 | 582 | for position in data["positions"]: 583 | self.on_position(position) 584 | 585 | def on_order(self, data: dict) -> None: 586 | """委托更新推送""" 587 | if data["order_type"] not in ORDERTYPE_DERIBIT2VT: 588 | self.gateway.write_log(f"收到不支持的类型委托推送{data}") 589 | return 590 | 591 | if data["label"]: 592 | orderid: str = data["label"] 593 | self.localids.add(orderid) 594 | else: 595 | orderid: str = data["order_id"] 596 | 597 | price: float = data.get("price", 0) 598 | 599 | order: OrderData = OrderData( 600 | symbol=data["instrument_name"], 601 | exchange=Exchange.DERIBIT, 602 | type=ORDERTYPE_DERIBIT2VT[data["order_type"]], 603 | orderid=orderid, 604 | direction=DIRECTION_DERIBIT2VT[data["direction"]], 605 | price=price, 606 | volume=data["amount"], 607 | traded=data["filled_amount"], 608 | datetime=generate_datetime(data["last_update_timestamp"]), 609 | status=STATUS_DERIBIT2VT[data["order_state"]], 610 | gateway_name=self.gateway_name 611 | ) 612 | 613 | self.gateway.on_order(order) 614 | 615 | def on_trade(self, data: dict) -> None: 616 | """成交更新推送""" 617 | sys_id: str = data["order_id"] 618 | local_id: str = sys_id 619 | 620 | trade: TradeData = TradeData( 621 | symbol=data["instrument_name"], 622 | exchange=Exchange.DERIBIT, 623 | orderid=local_id, 624 | tradeid=data["trade_id"], 625 | direction=DIRECTION_DERIBIT2VT[data["direction"]], 626 | price=data["price"], 627 | volume=data["amount"], 628 | datetime=generate_datetime(data["timestamp"]), 629 | gateway_name=self.gateway_name, 630 | ) 631 | 632 | self.gateway.on_trade(trade) 633 | 634 | def on_position(self, data: dict) -> None: 635 | """持仓更新推送""" 636 | pos: PositionData = PositionData( 637 | symbol=data["instrument_name"], 638 | exchange=Exchange.DERIBIT, 639 | direction=Direction.NET, 640 | volume=data["size"], 641 | price=data["average_price"], 642 | pnl=data["floating_profit_loss"], 643 | gateway_name=self.gateway_name, 644 | ) 645 | 646 | self.gateway.on_position(pos) 647 | 648 | def on_ticker(self, packet: dict) -> None: 649 | """行情推送回报""" 650 | data: dict = packet["params"]["data"] 651 | 652 | symbol: str = data["instrument_name"] 653 | tick: TickData = self.ticks[symbol] 654 | 655 | tick.last_price = get_float(data["last_price"]) 656 | tick.high_price = get_float(data["stats"]["high"]) 657 | tick.low_price = get_float(data["stats"]["low"]) 658 | tick.volume = get_float(data["stats"]["volume"]) 659 | tick.open_interest = get_float(data.get("open_interest", 0)) 660 | tick.datetime = generate_datetime(data["timestamp"]) 661 | tick.localtime = datetime.now() 662 | 663 | if 'mark_iv' in data: 664 | tick.extra = { 665 | "implied_volatility": get_float(data["mark_iv"]), 666 | "und_price": get_float(data["underlying_price"]), 667 | "option_price": get_float(data["mark_price"]), 668 | "delta": get_float(data["greeks"]["delta"]), 669 | "gamma": get_float(data["greeks"]["gamma"]), 670 | "vega": get_float(data["greeks"]["vega"]), 671 | "theta": get_float(data["greeks"]["theta"]), 672 | } 673 | 674 | if tick.last_price: 675 | self.gateway.on_tick(copy(tick)) 676 | 677 | def on_orderbook(self, packet: dict) -> None: 678 | """盘口推送回报""" 679 | data: dict = packet["params"]["data"] 680 | 681 | symbol: str = data["instrument_name"] 682 | bids: list = data["bids"] 683 | asks: list = data["asks"] 684 | tick: TickData = self.ticks[symbol] 685 | 686 | for i in range(min(len(bids), 5)): 687 | ix = i + 1 688 | bp, bv = bids[i] 689 | setattr(tick, f"bid_price_{ix}", bp) 690 | setattr(tick, f"bid_volume_{ix}", bv) 691 | 692 | for i in range(min(len(asks), 5)): 693 | ix = i + 1 694 | ap, av = asks[i] 695 | setattr(tick, f"ask_price_{ix}", ap) 696 | setattr(tick, f"ask_volume_{ix}", av) 697 | 698 | tick.datetime = generate_datetime(data["timestamp"]) 699 | tick.localtime = datetime.now() 700 | 701 | self.gateway.on_tick(copy(tick)) 702 | 703 | def send_request( 704 | self, 705 | method: str, 706 | params: dict, 707 | callback: Callable = None 708 | ) -> int: 709 | """发送请求""" 710 | self.reqid += 1 711 | 712 | msg: dict = { 713 | "jsonrpc": "2.0", 714 | "id": self.reqid, 715 | "method": method, 716 | "params": params 717 | } 718 | 719 | self.send_packet(msg) 720 | 721 | if callback: 722 | self.reqid_callback_map[self.reqid] = callback 723 | 724 | if "currency" in params: 725 | self.reqid_currency_map[self.reqid] = params["currency"] 726 | 727 | return self.reqid 728 | 729 | 730 | def generate_datetime(timestamp: int) -> datetime: 731 | """生成时间戳""" 732 | dt: datetime = datetime.fromtimestamp(timestamp / 1000) 733 | return dt.replace(tzinfo=LOCAL_TZ) 734 | 735 | 736 | def get_float(value: any) -> float: 737 | """获取浮点数""" 738 | if isinstance(value, float): 739 | return value 740 | return 0 741 | --------------------------------------------------------------------------------