PyAlgoEngine 0.7.6__tar.gz → 0.7.6.post2__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 → pyalgoengine-0.7.6.post2}/PKG-INFO +1 -1
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/PyAlgoEngine.egg-info/PKG-INFO +1 -1
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/PyAlgoEngine.egg-info/SOURCES.txt +1 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/__init__.py +1 -1
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/sim_input/__init__.py +2 -5
- pyalgoengine-0.7.6.post2/algo_engine/apps/sim_input/client.py +253 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/sim_input/sim_keyboard.py +2 -1
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/sim_input/sim_mouse.py +2 -1
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/LICENSE +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/PyAlgoEngine.egg-info/dependency_links.txt +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/PyAlgoEngine.egg-info/requires.txt +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/PyAlgoEngine.egg-info/top_level.txt +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/README.md +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/__init__.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/backtest/__init__.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/backtest/doc_server.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/backtest/tester.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/backtest/web_app.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/bokeh_server.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/demo/__init__.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/demo/test.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/sim_input/window.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/backtest/__init__.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/backtest/__main__.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/backtest/metrics.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/backtest/replay.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/backtest/sim_match.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/__init__.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/console_utils.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/finance_decimal.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/market_buffer.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/market_utils.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/market_utils_nt.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/market_utils_posix.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/technical_analysis.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/telemetrics.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/trade_utils.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/engine/__init__.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/engine/algo_engine.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/engine/event_engine.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/engine/market_engine.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/engine/trade_engine.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/monitor/__init__.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/monitor/advanced_data_interface.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/profile/__init__.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/profile/cn.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/strategy/__init__.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/strategy/strategy_engine.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/utils/__init__.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/utils/commit_regularizer.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/utils/data_utils.py +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/setup.cfg +0 -0
- {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/setup.py +0 -0
|
@@ -16,6 +16,7 @@ algo_engine/apps/backtest/web_app.py
|
|
|
16
16
|
algo_engine/apps/demo/__init__.py
|
|
17
17
|
algo_engine/apps/demo/test.py
|
|
18
18
|
algo_engine/apps/sim_input/__init__.py
|
|
19
|
+
algo_engine/apps/sim_input/client.py
|
|
19
20
|
algo_engine/apps/sim_input/sim_keyboard.py
|
|
20
21
|
algo_engine/apps/sim_input/sim_mouse.py
|
|
21
22
|
algo_engine/apps/sim_input/window.py
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
from .. import LOGGER
|
|
2
|
-
|
|
3
1
|
import platform
|
|
4
|
-
|
|
2
|
+
|
|
3
|
+
from .. import LOGGER
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
def check_windows_version(min_version=(6, 1)):
|
|
@@ -21,6 +20,4 @@ def check_windows_version(min_version=(6, 1)):
|
|
|
21
20
|
raise EnvironmentError(f"Unsupported Windows version: {version_str}. Minimum required is {min_version[0]}.{min_version[1]}.")
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
check_windows_version((6, 1))
|
|
25
|
-
|
|
26
23
|
LOGGER = LOGGER.getChild('SimInput')
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
__package__ = 'algo_engine.apps.sim_input'
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import tkinter
|
|
7
|
+
from collections.abc import Callable, Iterable
|
|
8
|
+
from tkinter import ttk
|
|
9
|
+
from typing import TypedDict, NotRequired, Any
|
|
10
|
+
|
|
11
|
+
from . import LOGGER
|
|
12
|
+
|
|
13
|
+
LOGGER.getChild('Client')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Action(object):
|
|
17
|
+
class Step(TypedDict):
|
|
18
|
+
name: str
|
|
19
|
+
procedure: Callable
|
|
20
|
+
args: NotRequired[Iterable]
|
|
21
|
+
kwargs: NotRequired[dict[str, Any]]
|
|
22
|
+
comments: NotRequired[str]
|
|
23
|
+
|
|
24
|
+
def __init__(self, name: str):
|
|
25
|
+
self.name = name
|
|
26
|
+
self.steps: list[Action.Step] = []
|
|
27
|
+
|
|
28
|
+
def append(self, name: str, action: Callable[[..., ...], None], args=None, kwargs=None, comments: str = None) -> None:
|
|
29
|
+
step = Action.Step(name=name, procedure=action)
|
|
30
|
+
|
|
31
|
+
if args is not None:
|
|
32
|
+
step['args'] = args
|
|
33
|
+
|
|
34
|
+
if kwargs is not None:
|
|
35
|
+
step['kwargs'] = kwargs
|
|
36
|
+
|
|
37
|
+
if comments is not None:
|
|
38
|
+
step['comments'] = comments
|
|
39
|
+
|
|
40
|
+
self.steps.append(step)
|
|
41
|
+
|
|
42
|
+
def __call__(self, ignore_error: bool = False) -> None:
|
|
43
|
+
n = len(self.steps)
|
|
44
|
+
for i, step in enumerate(self.steps, start=1):
|
|
45
|
+
procedure = step['procedure']
|
|
46
|
+
args = step.get('args', [])
|
|
47
|
+
kwargs = step.get('kwargs', {})
|
|
48
|
+
comments = step.get('comments', '')
|
|
49
|
+
try:
|
|
50
|
+
procedure(*args, **kwargs)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
LOGGER.error(f'<Action {self.name}> <step {comments}>({i} / {n}) Failed!')
|
|
53
|
+
|
|
54
|
+
if not ignore_error:
|
|
55
|
+
raise e
|
|
56
|
+
|
|
57
|
+
LOGGER.debug(f'<Action {self.name}> <step {comments}>({i} / {n}) Completed!')
|
|
58
|
+
|
|
59
|
+
LOGGER.info(f'<Action {self.name}> completed!')
|
|
60
|
+
|
|
61
|
+
def __len__(self):
|
|
62
|
+
return len(self.steps)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class AutoWorkClient(object, metaclass=abc.ABCMeta):
|
|
66
|
+
def __init__(self, root=None):
|
|
67
|
+
self.root = root if root is not None else tkinter.Tk()
|
|
68
|
+
self.root.title("Structured GUI Client")
|
|
69
|
+
self.root.geometry("600x500")
|
|
70
|
+
|
|
71
|
+
# State variables
|
|
72
|
+
self.worker_thread = None
|
|
73
|
+
self.running = False
|
|
74
|
+
|
|
75
|
+
# Create button
|
|
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)
|
|
90
|
+
|
|
91
|
+
self.actions: dict[int, list[Action]] = {}
|
|
92
|
+
|
|
93
|
+
@abc.abstractmethod
|
|
94
|
+
def listen_signal(self) -> int:
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
def register_action(self, action: Action, signal: int):
|
|
98
|
+
if signal in self.actions:
|
|
99
|
+
self.actions[signal].append(action)
|
|
100
|
+
else:
|
|
101
|
+
self.actions[signal] = [action]
|
|
102
|
+
|
|
103
|
+
def toggle_auto_work(self):
|
|
104
|
+
"""Toggle the daemon thread to start or stop auto work."""
|
|
105
|
+
if not self.running:
|
|
106
|
+
self.running = True
|
|
107
|
+
self.button.config(text="Release Control")
|
|
108
|
+
self.worker_thread = threading.Thread(target=self.auto_work, daemon=True)
|
|
109
|
+
self.worker_thread.start()
|
|
110
|
+
else:
|
|
111
|
+
self.running = False
|
|
112
|
+
self.button.config(text="Takeover Input")
|
|
113
|
+
|
|
114
|
+
def auto_work(self):
|
|
115
|
+
"""Generate data for the table in a loop."""
|
|
116
|
+
while self.running:
|
|
117
|
+
signal = self.listen_signal()
|
|
118
|
+
actions = self.actions.get(signal, [])
|
|
119
|
+
|
|
120
|
+
# Update table for the current signal
|
|
121
|
+
self.update_table(actions)
|
|
122
|
+
|
|
123
|
+
for action_idx, action in enumerate(actions):
|
|
124
|
+
if not self.running:
|
|
125
|
+
break
|
|
126
|
+
|
|
127
|
+
# Update action status to "Executing"
|
|
128
|
+
self.update_status(parent_id=action_idx, status="Executing")
|
|
129
|
+
action_done = False
|
|
130
|
+
|
|
131
|
+
for step_idx, step in enumerate(action.steps):
|
|
132
|
+
if not self.running:
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
# Update step status to "Executing"
|
|
136
|
+
self.update_status(parent_id=action_idx, child_id=step_idx, status="Executing")
|
|
137
|
+
|
|
138
|
+
# Execute the step
|
|
139
|
+
step["procedure"](*step.get("args", []), **step.get("kwargs", {}))
|
|
140
|
+
|
|
141
|
+
# Mark step as "Done"
|
|
142
|
+
self.update_status(parent_id=action_idx, child_id=step_idx, status="Done")
|
|
143
|
+
else:
|
|
144
|
+
# Mark action as "Done" after all steps
|
|
145
|
+
self.update_status(parent_id=action_idx, status="Done")
|
|
146
|
+
action_done = True
|
|
147
|
+
|
|
148
|
+
if not action_done:
|
|
149
|
+
self.update_status(parent_id=action_idx, status="Stopped")
|
|
150
|
+
|
|
151
|
+
def update_table(self, actions: list[Action]):
|
|
152
|
+
"""Render the table based on the provided actions."""
|
|
153
|
+
self.table.delete(*self.table.get_children()) # Clear existing rows
|
|
154
|
+
|
|
155
|
+
for action_idx, action in enumerate(actions):
|
|
156
|
+
prefix = chr(0x250C)
|
|
157
|
+
# Insert the parent row
|
|
158
|
+
parent_id = self.table.insert(
|
|
159
|
+
"", "end", iid=action_idx, values=(f"{prefix} {action.name}", "Pending", "")
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Insert child rows for steps
|
|
163
|
+
for step_idx, step in enumerate(action.steps):
|
|
164
|
+
prefix = f'{chr(0x251C) if step_idx < len(action.steps) - 1 else chr(0x2514)}{chr(0x2500)}'
|
|
165
|
+
self.table.insert(
|
|
166
|
+
parent_id,
|
|
167
|
+
"end",
|
|
168
|
+
iid=f"{action_idx}-{step_idx}",
|
|
169
|
+
text=f"Step {step_idx + 1}",
|
|
170
|
+
values=(f"{prefix} Step {step_idx + 1}", "Pending", step.get("comments", "")),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Expand parent row by default
|
|
174
|
+
self.table.item(parent_id, open=True)
|
|
175
|
+
|
|
176
|
+
# Auto-adjust column widths
|
|
177
|
+
self.adjust_column_widths()
|
|
178
|
+
|
|
179
|
+
def update_status(self, parent_id: int, child_id: int = None, status: str = "Pending"):
|
|
180
|
+
"""Update the status of the specified row."""
|
|
181
|
+
row_id = f"{parent_id}" if child_id is None else f"{parent_id}-{child_id}"
|
|
182
|
+
values = self.table.item(row_id, "values")
|
|
183
|
+
|
|
184
|
+
# Select the row if the status is "Executing"
|
|
185
|
+
match status:
|
|
186
|
+
case "Executing":
|
|
187
|
+
self.table.selection_set(row_id)
|
|
188
|
+
self.table.see(row_id) # Ensure the row is visible
|
|
189
|
+
|
|
190
|
+
self.table.item(row_id, values=(values[0], status, values[2]))
|
|
191
|
+
|
|
192
|
+
def adjust_column_widths(self):
|
|
193
|
+
"""Automatically adjust column widths based on content."""
|
|
194
|
+
|
|
195
|
+
self.table.column("#0", width=30, minwidth=30, stretch=False)
|
|
196
|
+
|
|
197
|
+
for col in self.table["columns"]:
|
|
198
|
+
max_width = max(
|
|
199
|
+
[len(str(self.table.set(item, col))) for item in self.table.get_children()] + [len(col)]
|
|
200
|
+
)
|
|
201
|
+
self.table.column(col, width=max_width * 10, minwidth=30, stretch=True) # Scale width for readability
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class ExampleClient(AutoWorkClient):
|
|
205
|
+
"""An example implementation of the AutoWorkClient."""
|
|
206
|
+
dummy_signal = 0
|
|
207
|
+
is_init = False
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
def dummy_action(name: str, msg: str) -> None:
|
|
211
|
+
LOGGER.info(f'start working on {name}, {msg}')
|
|
212
|
+
time.sleep(2)
|
|
213
|
+
LOGGER.info('working completed!')
|
|
214
|
+
|
|
215
|
+
def listen_signal(self) -> int:
|
|
216
|
+
"""Simulate listening for a signal (e.g., return random signal)."""
|
|
217
|
+
import random
|
|
218
|
+
if self.is_init:
|
|
219
|
+
delay = 5 + 5 * random.random()
|
|
220
|
+
time.sleep(delay)
|
|
221
|
+
self.dummy_signal += 1
|
|
222
|
+
self.is_init = True
|
|
223
|
+
return 1 + self.dummy_signal % 2
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def main():
|
|
227
|
+
client = ExampleClient()
|
|
228
|
+
|
|
229
|
+
# Example usage: Register actions for signal 1
|
|
230
|
+
action1 = Action("Example Action 1")
|
|
231
|
+
action1.append(name='S1A1s1', action=ExampleClient.dummy_action, args=("Signal 1 Action 1", "Step 1"))
|
|
232
|
+
action1.append(name='S1A1s2', action=ExampleClient.dummy_action, args=("Signal 1 Action 1", "Step 2"))
|
|
233
|
+
|
|
234
|
+
action2 = Action("Example Action 2")
|
|
235
|
+
action2.append(name='S1A2s1', action=ExampleClient.dummy_action, args=("Signal 1 Action 2", "Step 1"))
|
|
236
|
+
action2.append(name='S1A2s2', action=ExampleClient.dummy_action, args=("Signal 1 Action 2", "Step 2"))
|
|
237
|
+
|
|
238
|
+
client.register_action(action1, signal=1)
|
|
239
|
+
client.register_action(action2, signal=1)
|
|
240
|
+
|
|
241
|
+
action3 = Action("Example Action 3")
|
|
242
|
+
action3.append(name='S2A1s1', action=ExampleClient.dummy_action, args=("Signal 2 Action 1", "Step 1"))
|
|
243
|
+
action3.append(name='S2A1s2', action=ExampleClient.dummy_action, args=("Signal 2 Action 1", "Step 2"))
|
|
244
|
+
action3.append(name='S2A1s3', action=ExampleClient.dummy_action, args=("Signal 2 Action 1", "Step 3"))
|
|
245
|
+
|
|
246
|
+
client.register_action(action3, signal=2)
|
|
247
|
+
|
|
248
|
+
client.root.mainloop()
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
if __name__ == "__main__":
|
|
252
|
+
t = threading.Thread(target=main, daemon=False)
|
|
253
|
+
t.start()
|
|
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
|
|
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 → pyalgoengine-0.7.6.post2}/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
|
|
File without changes
|