├── 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 |
--------------------------------------------------------------------------------