PyAlgoEngine 0.7.6.post8__tar.gz → 0.7.7a2__tar.gz
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.
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/PKG-INFO +1 -1
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/PyAlgoEngine.egg-info/PKG-INFO +1 -1
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/__init__.py +1 -1
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/sim_input/client.py +133 -41
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/backtest/sim_match.py +50 -12
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/setup.py +35 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/LICENSE +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/PyAlgoEngine.egg-info/SOURCES.txt +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/PyAlgoEngine.egg-info/dependency_links.txt +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/PyAlgoEngine.egg-info/requires.txt +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/PyAlgoEngine.egg-info/top_level.txt +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/README.md +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/__init__.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/backtest/__init__.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/backtest/doc_server.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/backtest/tester.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/backtest/web_app.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/bokeh_server.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/demo/__init__.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/demo/test.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/sim_input/__init__.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/sim_input/sim_keyboard.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/sim_input/sim_mouse.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/sim_input/window.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/backtest/__init__.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/backtest/__main__.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/backtest/metrics.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/backtest/replay.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/base/__init__.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/base/console_utils.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/base/finance_decimal.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/base/market_buffer.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/base/market_utils.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/base/market_utils_nt.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/base/market_utils_posix.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/base/technical_analysis.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/base/telemetrics.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/base/trade_utils.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/engine/__init__.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/engine/algo_engine.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/engine/event_engine.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/engine/market_engine.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/engine/trade_engine.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/monitor/__init__.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/monitor/advanced_data_interface.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/profile/__init__.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/profile/cn.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/strategy/__init__.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/strategy/strategy_engine.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/utils/__init__.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/utils/commit_regularizer.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/utils/data_utils.py +0 -0
- {pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/setup.cfg +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
__package__ = 'algo_engine.apps.sim_input'
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
|
-
import
|
|
4
|
+
from threading import Thread
|
|
5
5
|
import time
|
|
6
6
|
import tkinter
|
|
7
7
|
from collections.abc import Callable, Iterable
|
|
@@ -63,53 +63,143 @@ class Action(object):
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
class AutoWorkClient(object, metaclass=abc.ABCMeta):
|
|
66
|
-
def __init__(self, root=None):
|
|
66
|
+
def __init__(self, root=None, **kwargs):
|
|
67
67
|
self.root = root if root is not None else tkinter.Tk()
|
|
68
|
-
self.root.title("Structured GUI Client")
|
|
69
|
-
|
|
68
|
+
self.root.title(kwargs.get('title', "Structured GUI Client"))
|
|
69
|
+
|
|
70
|
+
self.actions: dict[int, list[Action]] = {}
|
|
71
|
+
self.layout = {}
|
|
70
72
|
|
|
71
73
|
# State variables
|
|
72
74
|
self.worker_thread = None
|
|
73
75
|
self.running = False
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
self.button = ttk.Button(root, text="Takeover Input", command=self.toggle_auto_work)
|
|
77
|
-
self.button.pack(pady=10)
|
|
78
|
-
|
|
79
|
-
# Create table
|
|
80
|
-
self.table = ttk.Treeview(
|
|
81
|
-
root,
|
|
82
|
-
columns=("Action", "Status", "Comments"),
|
|
83
|
-
show="tree headings",
|
|
84
|
-
height=20
|
|
85
|
-
)
|
|
86
|
-
self.table.heading("Action", text="Action")
|
|
87
|
-
self.table.heading("Status", text="Status")
|
|
88
|
-
self.table.heading("Comments", text="Comments")
|
|
89
|
-
self.table.pack(padx=10, pady=10, fill="both", expand=True)
|
|
77
|
+
self.render_layout()
|
|
90
78
|
|
|
91
|
-
|
|
79
|
+
def render_layout(self):
|
|
80
|
+
# No fixed geometry, letting it resize
|
|
81
|
+
# self.root.geometry("600x500")
|
|
82
|
+
|
|
83
|
+
# Grid(0, 0): Create button
|
|
84
|
+
button_takeover = self.layout['button_takeover'] = ttk.Button(self.root, text="Takeover Input", command=self.toggle_auto_work)
|
|
85
|
+
button_takeover.grid(row=0, column=0, columnspan=2, pady=10, sticky="ew")
|
|
86
|
+
|
|
87
|
+
# Grid(1, 0): Mock button
|
|
88
|
+
button_mock = self.layout['button_mock'] = ttk.Button(self.root, text="Mock Action", command=self.toggle_mock)
|
|
89
|
+
button_mock.grid(row=1, column=0, padx=10, pady=10, sticky="ew")
|
|
90
|
+
|
|
91
|
+
# Grid(1, 1): Dropdown for action selection
|
|
92
|
+
action_selected = self.layout['action_selected'] = tkinter.StringVar()
|
|
93
|
+
action_dropdown = self.layout['action_dropdown'] = ttk.Combobox(self.root, textvariable=action_selected, state="readonly")
|
|
94
|
+
action_dropdown.grid(row=1, column=1, padx=10, pady=10, sticky="ew")
|
|
95
|
+
|
|
96
|
+
# Grid(2, 0): Create table
|
|
97
|
+
action_table = self.layout['action_table'] = ttk.Treeview(self.root, columns=("Action", "Status", "Comments"), show="tree headings")
|
|
98
|
+
action_table.heading("Action", text="Action")
|
|
99
|
+
action_table.heading("Status", text="Status")
|
|
100
|
+
action_table.heading("Comments", text="Comments")
|
|
101
|
+
action_table.grid(row=2, column=0, columnspan=2, padx=10, pady=10, sticky="nsew")
|
|
102
|
+
|
|
103
|
+
# Make the window resize properly by configuring grid weights
|
|
104
|
+
self.root.grid_columnconfigure(0, weight=1, minsize=200) # Adjust minsize for column 0
|
|
105
|
+
self.root.grid_columnconfigure(1, weight=1, minsize=200) # Adjust minsize for column 1
|
|
106
|
+
self.root.grid_rowconfigure(2, weight=1) # Make row 2 expand with the window
|
|
92
107
|
|
|
93
108
|
@abc.abstractmethod
|
|
94
109
|
def listen_signal(self) -> int:
|
|
95
110
|
...
|
|
96
111
|
|
|
97
112
|
def register_action(self, action: Action, signal: int):
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
113
|
+
self.actions.setdefault(signal, []).append(action)
|
|
114
|
+
|
|
115
|
+
# Update the dropdown with all registered actions.
|
|
116
|
+
all_actions = [f"Signal {signal} - {action.name}" for signal, actions in self.actions.items() for action in actions]
|
|
117
|
+
action_dropdown = self.layout['action_dropdown']
|
|
118
|
+
action_dropdown["values"] = all_actions
|
|
119
|
+
|
|
120
|
+
def mock_action(self, action: Action, timeout: float = 3.):
|
|
121
|
+
"""Mock the execution of an action on a transparent test ground."""
|
|
122
|
+
self.root.iconify()
|
|
123
|
+
|
|
124
|
+
# Create a borderless, transparent test ground window
|
|
125
|
+
testground = tkinter.Toplevel(self.root)
|
|
126
|
+
# testground.overrideredirect(True) # Remove all window borders and decorations
|
|
127
|
+
|
|
128
|
+
# Get screen dimensions and set the testground window to cover the entire screen
|
|
129
|
+
# screen_width = self.root.winfo_screenwidth()
|
|
130
|
+
# screen_height = self.root.winfo_screenheight()
|
|
131
|
+
# testground.geometry(f"{screen_width}x{screen_height}+0+0")
|
|
132
|
+
|
|
133
|
+
# Make the window semi-transparent and ensure it stays on top
|
|
134
|
+
testground.attributes("-alpha", 0.5) # Set transparency to 50%
|
|
135
|
+
testground.attributes("-fullscreen", True)
|
|
136
|
+
testground.attributes("-topmost", True) # Ensure it stays above other windows
|
|
137
|
+
|
|
138
|
+
# Force the window manager to render the window completely
|
|
139
|
+
testground.lift() # Bring it to the front
|
|
140
|
+
testground.focus_force() # Force focus to this window
|
|
141
|
+
|
|
142
|
+
# Add a label to display input history
|
|
143
|
+
input_history = tkinter.Listbox(testground, font=("Courier", 14))
|
|
144
|
+
input_history.pack(fill="both", expand=True, padx=20, pady=20)
|
|
145
|
+
input_history.insert("active", f"Mocking {action.name}")
|
|
146
|
+
testground.update_idletasks()
|
|
147
|
+
|
|
148
|
+
# Simulate action execution
|
|
149
|
+
for step in action.steps:
|
|
150
|
+
# Display the step name in the input history
|
|
151
|
+
input_history.insert("end", f"Executing Step: {step['name']}")
|
|
152
|
+
input_history.insert("end", f"Comments: {step.get('comments', 'No comments')}")
|
|
153
|
+
input_history.insert("end", "-" * 50)
|
|
154
|
+
input_history.see("end")
|
|
155
|
+
testground.update_idletasks()
|
|
156
|
+
|
|
157
|
+
# Simulate mouse and keyboard inputs
|
|
158
|
+
procedure = step['procedure']
|
|
159
|
+
args = step.get('args', [])
|
|
160
|
+
kwargs = step.get('kwargs', {})
|
|
161
|
+
try:
|
|
162
|
+
procedure(*args, **kwargs)
|
|
163
|
+
except Exception as e:
|
|
164
|
+
input_history.insert("end", f"Error: {str(e)}")
|
|
165
|
+
input_history.see("end")
|
|
166
|
+
|
|
167
|
+
# Simulate delay between steps
|
|
168
|
+
|
|
169
|
+
# Wait 3 seconds, then close the test ground and restore the client window
|
|
170
|
+
time.sleep(timeout)
|
|
171
|
+
testground.destroy()
|
|
102
172
|
|
|
103
173
|
def toggle_auto_work(self):
|
|
104
174
|
"""Toggle the daemon thread to start or stop auto work."""
|
|
105
175
|
if not self.running:
|
|
106
176
|
self.running = True
|
|
107
|
-
self.
|
|
108
|
-
self.worker_thread =
|
|
177
|
+
self.layout['button_takeover'].config(text="Release Control")
|
|
178
|
+
self.worker_thread = Thread(target=self.auto_work, daemon=True)
|
|
109
179
|
self.worker_thread.start()
|
|
110
180
|
else:
|
|
111
181
|
self.running = False
|
|
112
|
-
self.
|
|
182
|
+
self.layout['button_takeover'].config(text="Takeover Input")
|
|
183
|
+
|
|
184
|
+
def toggle_mock(self):
|
|
185
|
+
"""Mock the action selected in the dropdown."""
|
|
186
|
+
selected_name = self.layout['action_selected'].get()
|
|
187
|
+
if not selected_name:
|
|
188
|
+
LOGGER.warning("No action selected to mock!")
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
selected_action = None
|
|
192
|
+
for signal, actions in self.actions.items():
|
|
193
|
+
for action in actions:
|
|
194
|
+
if f"Signal {signal} - {action.name}" == selected_name:
|
|
195
|
+
selected_action = action
|
|
196
|
+
break
|
|
197
|
+
|
|
198
|
+
if selected_action is None:
|
|
199
|
+
LOGGER.warning(f"Action '{selected_name}' not found!")
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
self.mock_action(action=selected_action)
|
|
113
203
|
|
|
114
204
|
def auto_work(self):
|
|
115
205
|
"""Generate data for the table in a loop."""
|
|
@@ -150,19 +240,20 @@ class AutoWorkClient(object, metaclass=abc.ABCMeta):
|
|
|
150
240
|
|
|
151
241
|
def update_table(self, actions: list[Action]):
|
|
152
242
|
"""Render the table based on the provided actions."""
|
|
153
|
-
self.
|
|
243
|
+
action_table = self.layout['action_table']
|
|
244
|
+
action_table.delete(*action_table.get_children()) # Clear existing rows
|
|
154
245
|
|
|
155
246
|
for action_idx, action in enumerate(actions):
|
|
156
247
|
prefix = chr(0x250C)
|
|
157
248
|
# Insert the parent row
|
|
158
|
-
parent_id =
|
|
249
|
+
parent_id = action_table.insert(
|
|
159
250
|
"", "end", iid=action_idx, values=(f"{prefix} {action.name}", "Pending", "")
|
|
160
251
|
)
|
|
161
252
|
|
|
162
253
|
# Insert child rows for steps
|
|
163
254
|
for step_idx, step in enumerate(action.steps):
|
|
164
255
|
prefix = f'{chr(0x251C) if step_idx < len(action.steps) - 1 else chr(0x2514)}{chr(0x2500)}'
|
|
165
|
-
|
|
256
|
+
action_table.insert(
|
|
166
257
|
parent_id,
|
|
167
258
|
"end",
|
|
168
259
|
iid=f"{action_idx}-{step_idx}",
|
|
@@ -171,7 +262,7 @@ class AutoWorkClient(object, metaclass=abc.ABCMeta):
|
|
|
171
262
|
)
|
|
172
263
|
|
|
173
264
|
# Expand parent row by default
|
|
174
|
-
|
|
265
|
+
action_table.item(parent_id, open=True)
|
|
175
266
|
|
|
176
267
|
# Auto-adjust column widths
|
|
177
268
|
self.adjust_column_widths()
|
|
@@ -179,26 +270,27 @@ class AutoWorkClient(object, metaclass=abc.ABCMeta):
|
|
|
179
270
|
def update_status(self, parent_id: int, child_id: int = None, status: str = "Pending"):
|
|
180
271
|
"""Update the status of the specified row."""
|
|
181
272
|
row_id = f"{parent_id}" if child_id is None else f"{parent_id}-{child_id}"
|
|
182
|
-
|
|
273
|
+
action_table = self.layout['action_table']
|
|
274
|
+
values = action_table.item(row_id, "values")
|
|
183
275
|
|
|
184
276
|
# Select the row if the status is "Executing"
|
|
185
277
|
match status:
|
|
186
278
|
case "Executing":
|
|
187
|
-
|
|
188
|
-
|
|
279
|
+
action_table.selection_set(row_id)
|
|
280
|
+
action_table.see(row_id) # Ensure the row is visible
|
|
189
281
|
|
|
190
|
-
|
|
282
|
+
action_table.item(row_id, values=(values[0], status, values[2]))
|
|
191
283
|
|
|
192
284
|
def adjust_column_widths(self):
|
|
193
285
|
"""Automatically adjust column widths based on content."""
|
|
286
|
+
action_table = self.layout['action_table']
|
|
287
|
+
action_table.column("#0", width=30, minwidth=30, stretch=False)
|
|
194
288
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
for col in self.table["columns"]:
|
|
289
|
+
for col in action_table["columns"]:
|
|
198
290
|
max_width = max(
|
|
199
|
-
[len(str(
|
|
291
|
+
[len(str(action_table.set(item, col))) for item in action_table.get_children()] + [len(col)]
|
|
200
292
|
)
|
|
201
|
-
|
|
293
|
+
action_table.column(col, width=max_width * 10, minwidth=30, stretch=True) # Scale width for readability
|
|
202
294
|
|
|
203
295
|
|
|
204
296
|
class ExampleClient(AutoWorkClient):
|
|
@@ -249,5 +341,5 @@ def main():
|
|
|
249
341
|
|
|
250
342
|
|
|
251
343
|
if __name__ == "__main__":
|
|
252
|
-
t =
|
|
344
|
+
t = Thread(target=main, daemon=False)
|
|
253
345
|
t.start()
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
3
5
|
from . import LOGGER
|
|
4
|
-
from ..base import OrderType, MarketData, BarData, TradeData, TickData, OrderState, OrderBook, TradeReport, TradeInstruction
|
|
6
|
+
from ..base import OrderType, MarketData, BarData, TradeData, TickData, OrderState, OrderBook, TradeReport, TradeInstruction, TransactionSide
|
|
5
7
|
from ..engine.event_engine import TOPIC, EVENT_ENGINE
|
|
6
8
|
from ..profile import PROFILE
|
|
7
9
|
|
|
@@ -9,17 +11,26 @@ LOGGER = LOGGER.getChild('SimMatch')
|
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class SimMatch(object):
|
|
12
|
-
def __init__(self, ticker,
|
|
14
|
+
def __init__(self, ticker, event_engine=None, topic_set=None, **kwargs):
|
|
13
15
|
self.ticker = ticker
|
|
14
|
-
self.instant_fill = instant_fill
|
|
15
16
|
self.event_engine = event_engine if event_engine is not None else EVENT_ENGINE
|
|
16
17
|
self.topic_set = topic_set if topic_set is not None else TOPIC
|
|
17
|
-
self.fee_rate = fee_rate
|
|
18
|
+
self.fee_rate = kwargs.get('fee_rate', 0.)
|
|
18
19
|
|
|
19
20
|
self.working: dict[str, TradeInstruction] = {}
|
|
20
21
|
self.history: dict[str, TradeInstruction] = {}
|
|
21
22
|
|
|
22
23
|
self.timestamp = 0.
|
|
24
|
+
self.last_price = None
|
|
25
|
+
self.matching_config = {
|
|
26
|
+
'instant_fill': kwargs.get('instant_fill', False),
|
|
27
|
+
'lag': {
|
|
28
|
+
'ts': kwargs.get('lag_ts', 0.),
|
|
29
|
+
'n_transaction': kwargs.get('lag_n_transaction', 0)
|
|
30
|
+
},
|
|
31
|
+
'hit_prob': kwargs.get('hit_prob', 1.), # affecting FoK
|
|
32
|
+
'slippery_rate': kwargs.get('slippery_rate', 0.0001)
|
|
33
|
+
}
|
|
23
34
|
|
|
24
35
|
def __call__(self, **kwargs):
|
|
25
36
|
order: TradeInstruction | None = kwargs.pop('order', None)
|
|
@@ -35,6 +46,7 @@ class SimMatch(object):
|
|
|
35
46
|
|
|
36
47
|
if market_data is not None:
|
|
37
48
|
self.timestamp = market_data.timestamp
|
|
49
|
+
self.last_price = market_data.market_price
|
|
38
50
|
|
|
39
51
|
if isinstance(market_data, BarData):
|
|
40
52
|
self._check_bar_data(market_data=market_data)
|
|
@@ -69,17 +81,33 @@ class SimMatch(object):
|
|
|
69
81
|
# raise ValueError(f'Invalid instruction {order}, instruction must have a LimitPrice')
|
|
70
82
|
|
|
71
83
|
order.set_order_state(order_state=OrderState.Placed, timestamp=self.timestamp)
|
|
84
|
+
short_circuit = self._check_short_circuit(order=order)
|
|
72
85
|
|
|
73
|
-
if
|
|
74
|
-
self.
|
|
86
|
+
if short_circuit:
|
|
87
|
+
self.on_order(order=order, **kwargs)
|
|
88
|
+
# in short circuit mode, the worst price will be applied.
|
|
89
|
+
self._match(order=order, match_price=self.worst_price(order.limit_price, self.last_price, side=order.side))
|
|
75
90
|
|
|
91
|
+
self.working[order.order_id] = order
|
|
76
92
|
self.on_order(order=order, **kwargs)
|
|
77
93
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
@classmethod
|
|
95
|
+
def best_price(cls, *price, side: TransactionSide | int):
|
|
96
|
+
if side > 0:
|
|
97
|
+
return min(_ for _ in price if _ is not None and np.isfinite(_))
|
|
98
|
+
elif side < 0:
|
|
99
|
+
return max(_ for _ in price if _ is not None and np.isfinite(_))
|
|
100
|
+
|
|
101
|
+
raise ValueError(f'Invalid side {side}!')
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def worst_price(cls, *price, side: TransactionSide | int):
|
|
105
|
+
if side > 0:
|
|
106
|
+
return max(_ for _ in price if _ is not None and np.isfinite(_))
|
|
107
|
+
elif side < 0:
|
|
108
|
+
return min(_ for _ in price if _ is not None and np.isfinite(_))
|
|
109
|
+
|
|
110
|
+
raise ValueError(f'Invalid side {side}!')
|
|
83
111
|
|
|
84
112
|
def cancel_order(self, order: TradeInstruction = None, order_id: str = None, **kwargs):
|
|
85
113
|
if order is None and order_id is None:
|
|
@@ -104,6 +132,15 @@ class SimMatch(object):
|
|
|
104
132
|
self.history[order_id] = order
|
|
105
133
|
self.on_order(order=order, **kwargs)
|
|
106
134
|
|
|
135
|
+
def _check_short_circuit(self, order: TradeInstruction, **kwargs):
|
|
136
|
+
if order.limit_price is None and self.last_price is None:
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
if self.matching_config['instant_fill'] and all(not _ for _ in self.matching_config['lag'].values()):
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
return False
|
|
143
|
+
|
|
107
144
|
def _check_bar_data(self, market_data: BarData):
|
|
108
145
|
for order_id in list(self.working):
|
|
109
146
|
order = self.working.get(order_id)
|
|
@@ -285,10 +322,11 @@ class SimMatch(object):
|
|
|
285
322
|
self.cancel_order(order_id=order_id)
|
|
286
323
|
|
|
287
324
|
def clear(self):
|
|
288
|
-
# self.fee_rate = 0.
|
|
289
325
|
self.working.clear()
|
|
290
326
|
self.history.clear()
|
|
327
|
+
|
|
291
328
|
self.timestamp = 0.
|
|
329
|
+
self.last_price = None
|
|
292
330
|
|
|
293
331
|
@property
|
|
294
332
|
def market_time(self) -> datetime.datetime:
|
|
@@ -2,6 +2,8 @@ import codecs
|
|
|
2
2
|
import os
|
|
3
3
|
|
|
4
4
|
import setuptools
|
|
5
|
+
from setuptools import Extension
|
|
6
|
+
from setuptools.command.build_ext import build_ext
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
def read(rel_path):
|
|
@@ -21,6 +23,32 @@ def get_version(rel_path):
|
|
|
21
23
|
raise RuntimeError("Unable to find version string.")
|
|
22
24
|
|
|
23
25
|
|
|
26
|
+
class BuildExtWithFallback(build_ext):
|
|
27
|
+
"""Custom build_ext to handle Cython compilation with fallback."""
|
|
28
|
+
|
|
29
|
+
def run(self):
|
|
30
|
+
try:
|
|
31
|
+
print("Attempting to compile Cython modules...")
|
|
32
|
+
super().run()
|
|
33
|
+
except Exception as e:
|
|
34
|
+
print("Cython compilation failed:", e)
|
|
35
|
+
print("Falling back to pure Python implementation.")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Skip building ext_modules in CI
|
|
39
|
+
if os.getenv('GITHUB_ACTIONS') == 'true':
|
|
40
|
+
print("Skipping ext_modules as we're in a CI environment.")
|
|
41
|
+
ext_modules = []
|
|
42
|
+
else:
|
|
43
|
+
# Define Cython extension (use the .pyx file)
|
|
44
|
+
ext_modules = [
|
|
45
|
+
Extension(
|
|
46
|
+
"algo_engine.base.market_utils_posix",
|
|
47
|
+
["algo_engine/base/market_utils_posix.pyx"],
|
|
48
|
+
)
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
24
52
|
long_description = read("README.md")
|
|
25
53
|
|
|
26
54
|
setuptools.setup(
|
|
@@ -58,6 +86,8 @@ setuptools.setup(
|
|
|
58
86
|
"bokeh"
|
|
59
87
|
],
|
|
60
88
|
},
|
|
89
|
+
ext_modules=ext_modules,
|
|
90
|
+
cmdclass={"build_ext": BuildExtWithFallback},
|
|
61
91
|
command_options={
|
|
62
92
|
'nuitka': {
|
|
63
93
|
# boolean option, e.g. if you cared for C compilation commands
|
|
@@ -68,6 +98,11 @@ setuptools.setup(
|
|
|
68
98
|
# '--enable-plugin': ("setup.py", "pyside2"),
|
|
69
99
|
# options with several values, e.g. avoiding including modules
|
|
70
100
|
# '--nofollow-import-to': ("setup.py", ["*.tests", "*.distutils"]),
|
|
101
|
+
# disable LTO
|
|
102
|
+
'--lto': ("setup.py", 'yes'),
|
|
103
|
+
# include some common 3rd party packages
|
|
104
|
+
'--include-package': ("setup.py", ['ctypes', 'datetime', 'typing', 'multiprocessing']),
|
|
105
|
+
# '--mode': ("setup.py", 'standalone')
|
|
71
106
|
}
|
|
72
107
|
}
|
|
73
108
|
)
|
|
File without changes
|
|
File without changes
|
{pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/PyAlgoEngine.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/apps/sim_input/sim_keyboard.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyalgoengine-0.7.6.post8 → pyalgoengine-0.7.7a2}/algo_engine/monitor/advanced_data_interface.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|