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.
Files changed (53) hide show
  1. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/PKG-INFO +1 -1
  2. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/PyAlgoEngine.egg-info/PKG-INFO +1 -1
  3. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/PyAlgoEngine.egg-info/SOURCES.txt +1 -0
  4. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/__init__.py +1 -1
  5. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/sim_input/__init__.py +2 -5
  6. pyalgoengine-0.7.6.post2/algo_engine/apps/sim_input/client.py +253 -0
  7. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/sim_input/sim_keyboard.py +2 -1
  8. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/sim_input/sim_mouse.py +2 -1
  9. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/LICENSE +0 -0
  10. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/PyAlgoEngine.egg-info/dependency_links.txt +0 -0
  11. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/PyAlgoEngine.egg-info/requires.txt +0 -0
  12. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/PyAlgoEngine.egg-info/top_level.txt +0 -0
  13. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/README.md +0 -0
  14. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/__init__.py +0 -0
  15. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/backtest/__init__.py +0 -0
  16. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/backtest/doc_server.py +0 -0
  17. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/backtest/tester.py +0 -0
  18. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/backtest/web_app.py +0 -0
  19. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/bokeh_server.py +0 -0
  20. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/demo/__init__.py +0 -0
  21. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/demo/test.py +0 -0
  22. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/apps/sim_input/window.py +0 -0
  23. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/backtest/__init__.py +0 -0
  24. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/backtest/__main__.py +0 -0
  25. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/backtest/metrics.py +0 -0
  26. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/backtest/replay.py +0 -0
  27. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/backtest/sim_match.py +0 -0
  28. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/__init__.py +0 -0
  29. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/console_utils.py +0 -0
  30. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/finance_decimal.py +0 -0
  31. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/market_buffer.py +0 -0
  32. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/market_utils.py +0 -0
  33. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/market_utils_nt.py +0 -0
  34. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/market_utils_posix.py +0 -0
  35. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/technical_analysis.py +0 -0
  36. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/telemetrics.py +0 -0
  37. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/base/trade_utils.py +0 -0
  38. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/engine/__init__.py +0 -0
  39. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/engine/algo_engine.py +0 -0
  40. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/engine/event_engine.py +0 -0
  41. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/engine/market_engine.py +0 -0
  42. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/engine/trade_engine.py +0 -0
  43. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/monitor/__init__.py +0 -0
  44. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/monitor/advanced_data_interface.py +0 -0
  45. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/profile/__init__.py +0 -0
  46. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/profile/cn.py +0 -0
  47. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/strategy/__init__.py +0 -0
  48. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/strategy/strategy_engine.py +0 -0
  49. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/utils/__init__.py +0 -0
  50. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/utils/commit_regularizer.py +0 -0
  51. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/algo_engine/utils/data_utils.py +0 -0
  52. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/setup.cfg +0 -0
  53. {pyalgoengine-0.7.6 → pyalgoengine-0.7.6.post2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyAlgoEngine
3
- Version: 0.7.6
3
+ Version: 0.7.6.post2
4
4
  Summary: Basic algo engine
5
5
  Home-page: https://github.com/BolunHan/PyAlgoEngine
6
6
  Author: Bolun.Han
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyAlgoEngine
3
- Version: 0.7.6
3
+ Version: 0.7.6.post2
4
4
  Summary: Basic algo engine
5
5
  Home-page: https://github.com/BolunHan/PyAlgoEngine
6
6
  Author: Bolun.Han
@@ -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,4 +1,4 @@
1
- __version__ = "0.7.6"
1
+ __version__ = "0.7.6.post2"
2
2
 
3
3
  import logging
4
4
  import os
@@ -1,7 +1,6 @@
1
- from .. import LOGGER
2
-
3
1
  import platform
4
- import sys
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()
@@ -4,8 +4,9 @@ import ctypes
4
4
  import enum
5
5
  import time
6
6
 
7
- from . import LOGGER
7
+ from . import LOGGER, check_windows_version
8
8
 
9
+ check_windows_version((6, 1))
9
10
  LOGGER.getChild('Keyboard')
10
11
 
11
12
 
@@ -6,8 +6,9 @@ import time
6
6
  from ctypes import wintypes
7
7
  from typing import Literal
8
8
 
9
- from . import LOGGER
9
+ from . import LOGGER, check_windows_version
10
10
 
11
+ check_windows_version((6, 1))
11
12
  LOGGER.getChild('Mouse')
12
13
 
13
14
 
File without changes