fixcore-engine 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fixcore/__init__.py +6 -0
- fixcore/application.py +47 -0
- fixcore/log/__init__.py +7 -0
- fixcore/log/base.py +27 -0
- fixcore/log/factory.py +10 -0
- fixcore/log/file_log.py +70 -0
- fixcore/log/screen.py +32 -0
- fixcore/message/__init__.py +17 -0
- fixcore/message/cracker.py +243 -0
- fixcore/message/data_dictionary.py +298 -0
- fixcore/message/exceptions.py +21 -0
- fixcore/message/field.py +147 -0
- fixcore/message/message.py +403 -0
- fixcore/session/__init__.py +8 -0
- fixcore/session/session.py +532 -0
- fixcore/session/session_id.py +32 -0
- fixcore/session/session_settings.py +146 -0
- fixcore/session/state.py +60 -0
- fixcore/store/__init__.py +11 -0
- fixcore/store/base.py +49 -0
- fixcore/store/factory.py +33 -0
- fixcore/store/file_store.py +162 -0
- fixcore/store/memory.py +50 -0
- fixcore/transport/__init__.py +7 -0
- fixcore/transport/acceptor.py +166 -0
- fixcore/transport/framer.py +107 -0
- fixcore/transport/initiator.py +146 -0
- fixcore_engine-0.1.0.dist-info/METADATA +75 -0
- fixcore_engine-0.1.0.dist-info/RECORD +32 -0
- fixcore_engine-0.1.0.dist-info/WHEEL +5 -0
- fixcore_engine-0.1.0.dist-info/licenses/LICENSE +21 -0
- fixcore_engine-0.1.0.dist-info/top_level.txt +1 -0
fixcore/__init__.py
ADDED
fixcore/application.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Application interface — users subclass this to implement their trading logic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from fixcore.message.message import Message
|
|
10
|
+
from fixcore.session.session_id import SessionID
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Application(ABC):
|
|
14
|
+
"""Abstract base for FIX application callbacks.
|
|
15
|
+
|
|
16
|
+
All methods are called from within the asyncio event loop. Blocking
|
|
17
|
+
implementations must delegate to ``asyncio.get_event_loop().run_in_executor``
|
|
18
|
+
to avoid stalling other sessions.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def on_create(self, session_id: SessionID) -> None:
|
|
23
|
+
"""Called when a session is created."""
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def on_logon(self, session_id: SessionID) -> None:
|
|
27
|
+
"""Called after a successful Logon exchange."""
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def on_logout(self, session_id: SessionID) -> None:
|
|
31
|
+
"""Called after a session logs out or disconnects."""
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def to_admin(self, message: Message, session_id: SessionID) -> None:
|
|
35
|
+
"""Called before an admin message is sent; may mutate *message* in place."""
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def to_app(self, message: Message, session_id: SessionID) -> None:
|
|
39
|
+
"""Called before an application message is sent; may mutate *message* in place."""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def from_admin(self, message: Message, session_id: SessionID) -> None:
|
|
43
|
+
"""Called when an admin message is received."""
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def from_app(self, message: Message, session_id: SessionID) -> None:
|
|
47
|
+
"""Called when an application message is received."""
|
fixcore/log/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Logging layer — session and message event logging."""
|
|
2
|
+
|
|
3
|
+
from fixcore.log.base import Log, LogFactory
|
|
4
|
+
from fixcore.log.file_log import FileLog, FileLogFactory
|
|
5
|
+
from fixcore.log.screen import ScreenLog, ScreenLogFactory
|
|
6
|
+
|
|
7
|
+
__all__ = ["Log", "LogFactory", "FileLog", "FileLogFactory", "ScreenLog", "ScreenLogFactory"]
|
fixcore/log/base.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Log / LogFactory abstract base classes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
|
|
7
|
+
from fixcore.session.session_id import SessionID
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Log(ABC):
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def on_incoming(self, message: str) -> None:
|
|
13
|
+
"""Log a raw incoming message."""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def on_outgoing(self, message: str) -> None:
|
|
17
|
+
"""Log a raw outgoing message."""
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def on_event(self, text: str) -> None:
|
|
21
|
+
"""Log a session event (connect, disconnect, error, etc.)."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LogFactory(ABC):
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def create(self, session_id: SessionID) -> Log:
|
|
27
|
+
"""Return a Log instance for *session_id*."""
|
fixcore/log/factory.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""LogFactory helpers — screen and file implementations already in their own modules.
|
|
2
|
+
|
|
3
|
+
Re-exports everything under one roof so transport code only needs one import.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from fixcore.log.base import Log, LogFactory
|
|
7
|
+
from fixcore.log.file_log import FileLogFactory
|
|
8
|
+
from fixcore.log.screen import ScreenLogFactory
|
|
9
|
+
|
|
10
|
+
__all__ = ["Log", "LogFactory", "FileLogFactory", "ScreenLogFactory"]
|
fixcore/log/file_log.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""File-backed Log implementation — appends timestamped entries to a log file."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from fixcore.log.base import Log, LogFactory
|
|
9
|
+
from fixcore.session.session_id import SessionID
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _session_filename(session_id: SessionID) -> str:
|
|
13
|
+
return (
|
|
14
|
+
f"{session_id.begin_string}-"
|
|
15
|
+
f"{session_id.sender_comp_id}-"
|
|
16
|
+
f"{session_id.target_comp_id}"
|
|
17
|
+
+ (f"-{session_id.qualifier}" if session_id.qualifier else "")
|
|
18
|
+
+ ".log"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _now() -> str:
|
|
23
|
+
return datetime.now(tz=timezone.utc).strftime("%Y%m%d-%H:%M:%S.%f")[:-3]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FileLog(Log):
|
|
27
|
+
"""Writes incoming messages, outgoing messages, and session events to a
|
|
28
|
+
single append-only log file.
|
|
29
|
+
|
|
30
|
+
Log line format::
|
|
31
|
+
|
|
32
|
+
20240101-12:00:00.000 : <incoming> 8=FIX.4.2|9=...|
|
|
33
|
+
20240101-12:00:00.001 : <outgoing> 8=FIX.4.2|9=...|
|
|
34
|
+
20240101-12:00:00.002 : --event-- Logon complete
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, log_dir: str | Path, session_id: SessionID) -> None:
|
|
38
|
+
log_path = Path(log_dir) / _session_filename(session_id)
|
|
39
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
self._fh = log_path.open("a", encoding="utf-8", buffering=1) # line-buffered
|
|
41
|
+
|
|
42
|
+
def on_incoming(self, message: str) -> None:
|
|
43
|
+
self._fh.write(f"{_now()} : <incoming> {message}\n")
|
|
44
|
+
|
|
45
|
+
def on_outgoing(self, message: str) -> None:
|
|
46
|
+
self._fh.write(f"{_now()} : <outgoing> {message}\n")
|
|
47
|
+
|
|
48
|
+
def on_event(self, text: str) -> None:
|
|
49
|
+
self._fh.write(f"{_now()} : --event-- {text}\n")
|
|
50
|
+
|
|
51
|
+
def close(self) -> None:
|
|
52
|
+
"""Flush and close the underlying file handle."""
|
|
53
|
+
self._fh.flush()
|
|
54
|
+
self._fh.close()
|
|
55
|
+
|
|
56
|
+
def __del__(self) -> None:
|
|
57
|
+
try:
|
|
58
|
+
self._fh.close()
|
|
59
|
+
except Exception:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class FileLogFactory(LogFactory):
|
|
64
|
+
"""Creates a :class:`FileLog` for each session under *log_dir*."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, log_dir: str | Path) -> None:
|
|
67
|
+
self._log_dir = Path(log_dir)
|
|
68
|
+
|
|
69
|
+
def create(self, session_id: SessionID) -> FileLog:
|
|
70
|
+
return FileLog(self._log_dir, session_id)
|
fixcore/log/screen.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Screen (stdout) log implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
|
|
8
|
+
from fixcore.log.base import Log, LogFactory
|
|
9
|
+
from fixcore.session.session_id import SessionID
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _now() -> str:
|
|
13
|
+
return datetime.now(tz=timezone.utc).strftime("%Y%m%d-%H:%M:%S.%f")[:-3]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ScreenLog(Log):
|
|
17
|
+
def __init__(self, session_id: SessionID) -> None:
|
|
18
|
+
self._prefix = str(session_id)
|
|
19
|
+
|
|
20
|
+
def on_incoming(self, message: str) -> None:
|
|
21
|
+
print(f"{_now()} {self._prefix} < {message}", file=sys.stdout)
|
|
22
|
+
|
|
23
|
+
def on_outgoing(self, message: str) -> None:
|
|
24
|
+
print(f"{_now()} {self._prefix} > {message}", file=sys.stdout)
|
|
25
|
+
|
|
26
|
+
def on_event(self, text: str) -> None:
|
|
27
|
+
print(f"{_now()} {self._prefix} -- {text}", file=sys.stdout)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ScreenLogFactory(LogFactory):
|
|
31
|
+
def create(self, session_id: SessionID) -> ScreenLog:
|
|
32
|
+
return ScreenLog(session_id)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""FIX message layer — encoding, decoding, and field definitions."""
|
|
2
|
+
|
|
3
|
+
from fixcore.message.cracker import MessageCracker
|
|
4
|
+
from fixcore.message.data_dictionary import DataDictionary, FieldDef, GroupDef, MessageDef
|
|
5
|
+
from fixcore.message.exceptions import (
|
|
6
|
+
FieldNotFound, InvalidMessage, UnsupportedMessageType, UnsupportedVersion,
|
|
7
|
+
)
|
|
8
|
+
from fixcore.message.field import Field, FieldMap, Group
|
|
9
|
+
from fixcore.message.message import Header, Message, Trailer
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"MessageCracker",
|
|
13
|
+
"DataDictionary", "FieldDef", "GroupDef", "MessageDef",
|
|
14
|
+
"FieldNotFound", "InvalidMessage", "UnsupportedMessageType", "UnsupportedVersion",
|
|
15
|
+
"Field", "FieldMap", "Group",
|
|
16
|
+
"Header", "Message", "Trailer",
|
|
17
|
+
]
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""MessageCracker — mixin that dispatches fromApp messages to typed handler methods.
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
class MyApp(Application, MessageCracker):
|
|
6
|
+
def from_app(self, message: Message, session_id: SessionID) -> None:
|
|
7
|
+
self.crack(message, session_id)
|
|
8
|
+
|
|
9
|
+
def on_new_order_single(self, message: Message, session_id: SessionID) -> None:
|
|
10
|
+
clord_id = message.get_field(11)
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
def on_execution_report(self, message: Message, session_id: SessionID) -> None:
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
Handler method names follow the convention ``on_<snake_case_message_name>``. If
|
|
17
|
+
no matching method is found, :meth:`on_message` is called, which raises
|
|
18
|
+
:exc:`~fixcore.message.exceptions.UnsupportedMessageType` by default.
|
|
19
|
+
|
|
20
|
+
Custom / venue-specific message types can be registered at class or instance
|
|
21
|
+
level::
|
|
22
|
+
|
|
23
|
+
MessageCracker.register("U1", "on_custom_order") # class-level
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from fixcore.message.exceptions import UnsupportedMessageType
|
|
29
|
+
from fixcore.message.message import Message
|
|
30
|
+
from fixcore.session.session_id import SessionID
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# MsgType → handler method name registry
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
# FIX 4.2 / 4.4 application messages
|
|
38
|
+
_REGISTRY: dict[str, str] = {
|
|
39
|
+
# Orders
|
|
40
|
+
"D": "on_new_order_single",
|
|
41
|
+
"F": "on_order_cancel_request",
|
|
42
|
+
"G": "on_order_cancel_replace_request",
|
|
43
|
+
"H": "on_order_status_request",
|
|
44
|
+
"Q": "on_dont_know_trade",
|
|
45
|
+
# Executions / responses
|
|
46
|
+
"8": "on_execution_report",
|
|
47
|
+
"9": "on_order_cancel_reject",
|
|
48
|
+
# Lists
|
|
49
|
+
"E": "on_new_order_list",
|
|
50
|
+
"K": "on_list_cancel_request",
|
|
51
|
+
"L": "on_list_execute",
|
|
52
|
+
"M": "on_list_status_request",
|
|
53
|
+
"N": "on_list_status",
|
|
54
|
+
# Market data
|
|
55
|
+
"V": "on_market_data_request",
|
|
56
|
+
"W": "on_market_data_snapshot_full_refresh",
|
|
57
|
+
"X": "on_market_data_incremental_refresh",
|
|
58
|
+
"Y": "on_market_data_request_reject",
|
|
59
|
+
# Quotes
|
|
60
|
+
"R": "on_quote_request",
|
|
61
|
+
"S": "on_quote",
|
|
62
|
+
"Z": "on_quote_cancel",
|
|
63
|
+
"a": "on_quote_status_request",
|
|
64
|
+
"b": "on_mass_quote_acknowledgement",
|
|
65
|
+
"i": "on_mass_quote",
|
|
66
|
+
# Security / reference data
|
|
67
|
+
"c": "on_security_definition_request",
|
|
68
|
+
"d": "on_security_definition",
|
|
69
|
+
"e": "on_security_status_request",
|
|
70
|
+
"f": "on_security_status",
|
|
71
|
+
"g": "on_trading_session_status_request",
|
|
72
|
+
"h": "on_trading_session_status",
|
|
73
|
+
# Allocation / settlement
|
|
74
|
+
"J": "on_allocation",
|
|
75
|
+
"P": "on_allocation_ack",
|
|
76
|
+
"T": "on_settlement_instructions",
|
|
77
|
+
# News / email
|
|
78
|
+
"B": "on_news",
|
|
79
|
+
"C": "on_email",
|
|
80
|
+
# Business / application reject
|
|
81
|
+
"j": "on_business_message_reject",
|
|
82
|
+
# FIX 4.4 / 5.0 additions
|
|
83
|
+
"AB": "on_new_order_multileg",
|
|
84
|
+
"AC": "on_multileg_order_cancel_replace",
|
|
85
|
+
"AD": "on_trade_capture_report_request",
|
|
86
|
+
"AE": "on_trade_capture_report",
|
|
87
|
+
"AF": "on_order_mass_status_request",
|
|
88
|
+
"AG": "on_quote_request_reject",
|
|
89
|
+
"AH": "on_rfq_request",
|
|
90
|
+
"AI": "on_quote_status_report",
|
|
91
|
+
"AJ": "on_quote_response",
|
|
92
|
+
"AK": "on_confirmation",
|
|
93
|
+
"AL": "on_position_maintenance_request",
|
|
94
|
+
"AM": "on_position_maintenance_report",
|
|
95
|
+
"AN": "on_request_for_positions",
|
|
96
|
+
"AO": "on_request_for_positions_ack",
|
|
97
|
+
"AP": "on_position_report",
|
|
98
|
+
"AQ": "on_trade_capture_report_request_ack",
|
|
99
|
+
"AR": "on_trade_capture_report_ack",
|
|
100
|
+
"AS": "on_allocation_report",
|
|
101
|
+
"AT": "on_allocation_report_ack",
|
|
102
|
+
"AU": "on_confirmation_ack",
|
|
103
|
+
"AV": "on_settlement_instruction_request",
|
|
104
|
+
"AW": "on_assignment_report",
|
|
105
|
+
"AX": "on_collateral_request",
|
|
106
|
+
"AY": "on_collateral_assignment",
|
|
107
|
+
"AZ": "on_collateral_response",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class MessageCracker:
|
|
112
|
+
"""Mixin providing MsgType-based dispatch for :meth:`Application.from_app`.
|
|
113
|
+
|
|
114
|
+
Combine with :class:`~fixcore.application.Application`::
|
|
115
|
+
|
|
116
|
+
class MyApp(Application, MessageCracker):
|
|
117
|
+
def from_app(self, message, session_id):
|
|
118
|
+
self.crack(message, session_id)
|
|
119
|
+
|
|
120
|
+
def on_new_order_single(self, message, session_id):
|
|
121
|
+
...
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
# Instance-level overrides (populated by register_instance or subclass __init__)
|
|
125
|
+
_instance_registry: dict[str, str]
|
|
126
|
+
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
# Dispatch
|
|
129
|
+
# ------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
def crack(self, message: Message, session_id: SessionID) -> None:
|
|
132
|
+
"""Dispatch *message* to the appropriate typed handler.
|
|
133
|
+
|
|
134
|
+
Resolution order:
|
|
135
|
+
1. Instance registry (set via :meth:`register_instance`)
|
|
136
|
+
2. Class-level registry (set via :meth:`register`)
|
|
137
|
+
3. :meth:`on_message` fallback
|
|
138
|
+
"""
|
|
139
|
+
msg_type = message.msg_type
|
|
140
|
+
|
|
141
|
+
# Instance overrides take precedence
|
|
142
|
+
instance_reg = getattr(self, "_instance_registry", {})
|
|
143
|
+
handler_name = instance_reg.get(msg_type) or _REGISTRY.get(msg_type)
|
|
144
|
+
|
|
145
|
+
if handler_name:
|
|
146
|
+
handler = getattr(self, handler_name, None)
|
|
147
|
+
if handler is not None:
|
|
148
|
+
handler(message, session_id)
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
self.on_message(message, session_id)
|
|
152
|
+
|
|
153
|
+
def on_message(self, message: Message, session_id: SessionID) -> None:
|
|
154
|
+
"""Fallback called when no specific handler is found.
|
|
155
|
+
|
|
156
|
+
Raises :exc:`~fixcore.message.exceptions.UnsupportedMessageType`
|
|
157
|
+
by default. Override to handle all unknown message types generically.
|
|
158
|
+
"""
|
|
159
|
+
raise UnsupportedMessageType(message.msg_type)
|
|
160
|
+
|
|
161
|
+
# ------------------------------------------------------------------
|
|
162
|
+
# Registration helpers
|
|
163
|
+
# ------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def register(cls, msg_type: str, handler_name: str) -> None:
|
|
167
|
+
"""Register a class-wide handler for *msg_type*.
|
|
168
|
+
|
|
169
|
+
Example::
|
|
170
|
+
|
|
171
|
+
MessageCracker.register("U1", "on_custom_order")
|
|
172
|
+
"""
|
|
173
|
+
_REGISTRY[msg_type] = handler_name
|
|
174
|
+
|
|
175
|
+
def register_instance(self, msg_type: str, handler_name: str) -> None:
|
|
176
|
+
"""Register a handler for *msg_type* on this instance only."""
|
|
177
|
+
if not hasattr(self, "_instance_registry"):
|
|
178
|
+
self._instance_registry = {}
|
|
179
|
+
self._instance_registry[msg_type] = handler_name
|
|
180
|
+
|
|
181
|
+
# ------------------------------------------------------------------
|
|
182
|
+
# Default typed handlers (all call on_message — override to handle)
|
|
183
|
+
# ------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
def on_new_order_single(self, message: Message, session_id: SessionID) -> None:
|
|
186
|
+
self.on_message(message, session_id)
|
|
187
|
+
|
|
188
|
+
def on_execution_report(self, message: Message, session_id: SessionID) -> None:
|
|
189
|
+
self.on_message(message, session_id)
|
|
190
|
+
|
|
191
|
+
def on_order_cancel_request(self, message: Message, session_id: SessionID) -> None:
|
|
192
|
+
self.on_message(message, session_id)
|
|
193
|
+
|
|
194
|
+
def on_order_cancel_reject(self, message: Message, session_id: SessionID) -> None:
|
|
195
|
+
self.on_message(message, session_id)
|
|
196
|
+
|
|
197
|
+
def on_order_cancel_replace_request(self, message: Message, session_id: SessionID) -> None:
|
|
198
|
+
self.on_message(message, session_id)
|
|
199
|
+
|
|
200
|
+
def on_order_status_request(self, message: Message, session_id: SessionID) -> None:
|
|
201
|
+
self.on_message(message, session_id)
|
|
202
|
+
|
|
203
|
+
def on_market_data_request(self, message: Message, session_id: SessionID) -> None:
|
|
204
|
+
self.on_message(message, session_id)
|
|
205
|
+
|
|
206
|
+
def on_market_data_snapshot_full_refresh(self, message: Message, session_id: SessionID) -> None:
|
|
207
|
+
self.on_message(message, session_id)
|
|
208
|
+
|
|
209
|
+
def on_market_data_incremental_refresh(self, message: Message, session_id: SessionID) -> None:
|
|
210
|
+
self.on_message(message, session_id)
|
|
211
|
+
|
|
212
|
+
def on_market_data_request_reject(self, message: Message, session_id: SessionID) -> None:
|
|
213
|
+
self.on_message(message, session_id)
|
|
214
|
+
|
|
215
|
+
def on_quote_request(self, message: Message, session_id: SessionID) -> None:
|
|
216
|
+
self.on_message(message, session_id)
|
|
217
|
+
|
|
218
|
+
def on_quote(self, message: Message, session_id: SessionID) -> None:
|
|
219
|
+
self.on_message(message, session_id)
|
|
220
|
+
|
|
221
|
+
def on_news(self, message: Message, session_id: SessionID) -> None:
|
|
222
|
+
self.on_message(message, session_id)
|
|
223
|
+
|
|
224
|
+
def on_business_message_reject(self, message: Message, session_id: SessionID) -> None:
|
|
225
|
+
self.on_message(message, session_id)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
# Auto-generate default handlers for any registered handler name that doesn't
|
|
230
|
+
# already have an explicit implementation on the class.
|
|
231
|
+
# ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
def _make_default_handler(name: str): # noqa: ANN202
|
|
234
|
+
def handler(self: MessageCracker, message: Message, session_id: SessionID) -> None:
|
|
235
|
+
self.on_message(message, session_id)
|
|
236
|
+
handler.__name__ = name
|
|
237
|
+
handler.__qualname__ = f"MessageCracker.{name}"
|
|
238
|
+
return handler
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
for _handler_name in set(_REGISTRY.values()):
|
|
242
|
+
if not hasattr(MessageCracker, _handler_name):
|
|
243
|
+
setattr(MessageCracker, _handler_name, _make_default_handler(_handler_name))
|