bbstrader 0.3.6__tar.gz → 0.3.7__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.
Potentially problematic release.
This version of bbstrader might be problematic. Click here for more details.
- {bbstrader-0.3.6/bbstrader.egg-info → bbstrader-0.3.7}/PKG-INFO +3 -3
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/__init__.py +1 -1
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/__main__.py +2 -2
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/apps/_copier.py +40 -37
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/btengine/backtest.py +33 -28
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/btengine/data.py +105 -81
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/btengine/event.py +21 -22
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/btengine/execution.py +51 -24
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/btengine/performance.py +23 -12
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/btengine/portfolio.py +40 -30
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/btengine/scripts.py +13 -12
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/btengine/strategy.py +288 -101
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/compat.py +4 -3
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/config.py +20 -36
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/core/data.py +76 -48
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/core/scripts.py +22 -21
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/core/utils.py +13 -12
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/trading/execution.py +6 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/tseries.py +55 -39
- {bbstrader-0.3.6 → bbstrader-0.3.7/bbstrader.egg-info}/PKG-INFO +3 -3
- {bbstrader-0.3.6 → bbstrader-0.3.7}/pyproject.toml +3 -3
- {bbstrader-0.3.6 → bbstrader-0.3.7}/tests/engine/test_portfolio.py +3 -2
- {bbstrader-0.3.6 → bbstrader-0.3.7}/LICENSE +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/MANIFEST.in +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/README.md +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/apps/__init__.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/btengine/__init__.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/core/__init__.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/ibkr/__init__.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/ibkr/utils.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/metatrader/__init__.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/metatrader/account.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/metatrader/analysis.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/metatrader/copier.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/metatrader/rates.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/metatrader/risk.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/metatrader/scripts.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/metatrader/trade.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/metatrader/utils.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/models/__init__.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/models/factors.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/models/ml.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/models/nlp.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/models/optimization.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/models/portfolio.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/models/risk.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/trading/__init__.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/trading/scripts.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/trading/strategies.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader/trading/utils.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader.egg-info/SOURCES.txt +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader.egg-info/dependency_links.txt +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader.egg-info/entry_points.txt +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader.egg-info/requires.txt +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/bbstrader.egg-info/top_level.txt +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/docs/conf.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/requirements.txt +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/setup.cfg +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/setup.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/tests/__init__.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/tests/engine/__init__.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/tests/engine/test_backtest.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/tests/engine/test_data.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/tests/engine/test_events.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/tests/engine/test_execution.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/tests/metatrader/__init__.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/tests/metatrader/test_account.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/tests/metatrader/test_rates.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/tests/metatrader/test_risk_management.py +0 -0
- {bbstrader-0.3.6 → bbstrader-0.3.7}/tests/metatrader/test_trade.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bbstrader
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: Simplified Investment & Trading Toolkit
|
|
5
|
-
Author-email: Bertin Balouki SIMYELI <bertin@
|
|
6
|
-
Maintainer-email: Bertin Balouki SIMYELI <bertin@
|
|
5
|
+
Author-email: Bertin Balouki SIMYELI <bertin@bbs-trading.com>
|
|
6
|
+
Maintainer-email: Bertin Balouki SIMYELI <bertin@bbs-trading.com>
|
|
7
7
|
License-Expression: MIT
|
|
8
8
|
Project-URL: Homepage, https://github.com/bbalouki/bbstrader
|
|
9
9
|
Project-URL: Download, https://pypi.org/project/bbstrader/
|
|
@@ -5,7 +5,7 @@ Simplified Investment & Trading Toolkit
|
|
|
5
5
|
|
|
6
6
|
__author__ = "Bertin Balouki SIMYELI"
|
|
7
7
|
__copyright__ = "2023-2025 Bertin Balouki SIMYELI"
|
|
8
|
-
__email__ = "bertin@
|
|
8
|
+
__email__ = "bertin@bbs-trading.com"
|
|
9
9
|
__license__ = "MIT"
|
|
10
10
|
|
|
11
11
|
from importlib.metadata import version, PackageNotFoundError
|
|
@@ -23,7 +23,7 @@ class _Module(Enum):
|
|
|
23
23
|
FONT = pyfiglet.figlet_format("BBSTRADER", font="big")
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def main():
|
|
26
|
+
def main() -> None:
|
|
27
27
|
DESCRIPTION = "BBSTRADER"
|
|
28
28
|
USAGE_TEXT = """
|
|
29
29
|
Usage:
|
|
@@ -51,7 +51,7 @@ def main():
|
|
|
51
51
|
if ("-h" in sys.argv or "--help" in sys.argv) and args.run is None:
|
|
52
52
|
print(Fore.WHITE + USAGE_TEXT)
|
|
53
53
|
sys.exit(0)
|
|
54
|
-
if
|
|
54
|
+
if "-v" in sys.argv or "--version" in sys.argv:
|
|
55
55
|
print(Fore.GREEN + f"bbstrader version {__version__}")
|
|
56
56
|
print(Fore.WHITE + f"bbstrader maintained and supported by {__author__}")
|
|
57
57
|
sys.exit(0)
|
|
@@ -12,10 +12,13 @@ from PIL import Image, ImageTk
|
|
|
12
12
|
from bbstrader.metatrader.copier import copier_worker_process, get_symbols_from_string
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
from typing import Any, Dict, Union
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def resource_path(relative_path: str) -> Path:
|
|
16
19
|
"""Get absolute path to resource"""
|
|
17
20
|
try:
|
|
18
|
-
base_path = Path(sys._MEIPASS)
|
|
21
|
+
base_path = Path(sys._MEIPASS) # type: ignore
|
|
19
22
|
except AttributeError:
|
|
20
23
|
base_path = Path(__file__).resolve().parent.parent.parent
|
|
21
24
|
|
|
@@ -27,10 +30,10 @@ ICON_PATH = resource_path("assets/bbstrader.ico")
|
|
|
27
30
|
LOGO_PATH = resource_path("assets/bbstrader.png")
|
|
28
31
|
|
|
29
32
|
|
|
30
|
-
class TradeCopierApp
|
|
33
|
+
class TradeCopierApp:
|
|
31
34
|
copier_processes: List[multiprocessing.Process]
|
|
32
35
|
|
|
33
|
-
def __init__(self, root: tk.Tk):
|
|
36
|
+
def __init__(self, root: tk.Tk) -> None:
|
|
34
37
|
root.title(TITLE)
|
|
35
38
|
root.geometry("1600x900")
|
|
36
39
|
self.root = root
|
|
@@ -48,13 +51,13 @@ class TradeCopierApp(object):
|
|
|
48
51
|
self.add_destination_accounts_frame(self.main_frame)
|
|
49
52
|
self.add_copier_settings(self.main_frame)
|
|
50
53
|
|
|
51
|
-
def set_style(self):
|
|
54
|
+
def set_style(self) -> None:
|
|
52
55
|
self.style = ttk.Style()
|
|
53
56
|
self.style.configure("Bold.TLabelframe.Label", font=("Segoe UI", 15, "bold"))
|
|
54
57
|
|
|
55
|
-
def add_main_frame(self):
|
|
58
|
+
def add_main_frame(self) -> ttk.Frame:
|
|
56
59
|
main_frame = ttk.Frame(self.root, padding="10")
|
|
57
|
-
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
60
|
+
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # type: ignore
|
|
58
61
|
|
|
59
62
|
# Configure the layout
|
|
60
63
|
main_frame.columnconfigure(0, weight=2)
|
|
@@ -63,7 +66,7 @@ class TradeCopierApp(object):
|
|
|
63
66
|
|
|
64
67
|
# --- visual/logo frame ---
|
|
65
68
|
self.visual_frame = ttk.Frame(main_frame)
|
|
66
|
-
self.visual_frame.grid(row=0, column=1, padx=5, pady=5, sticky=(tk.W, tk.E))
|
|
69
|
+
self.visual_frame.grid(row=0, column=1, padx=5, pady=5, sticky=(tk.W, tk.E)) # type: ignore
|
|
67
70
|
|
|
68
71
|
# --- opier settings---
|
|
69
72
|
self.right_panel_frame = ttk.Frame(main_frame)
|
|
@@ -76,12 +79,12 @@ class TradeCopierApp(object):
|
|
|
76
79
|
|
|
77
80
|
return main_frame
|
|
78
81
|
|
|
79
|
-
def add_source_account_frame(self, main_frame):
|
|
82
|
+
def add_source_account_frame(self, main_frame: ttk.Frame) -> None:
|
|
80
83
|
# --- Source Account ---
|
|
81
84
|
source_frame = ttk.LabelFrame(
|
|
82
85
|
main_frame, text="Source Account", style="Bold.TLabelframe"
|
|
83
86
|
)
|
|
84
|
-
source_frame.grid(row=0, column=0, padx=5, pady=5, sticky=(tk.W, tk.E, tk.N))
|
|
87
|
+
source_frame.grid(row=0, column=0, padx=5, pady=5, sticky=(tk.W, tk.E, tk.N)) # type: ignore
|
|
85
88
|
source_frame.columnconfigure(1, weight=1)
|
|
86
89
|
source_frame.columnconfigure(3, weight=1)
|
|
87
90
|
|
|
@@ -131,13 +134,13 @@ class TradeCopierApp(object):
|
|
|
131
134
|
)
|
|
132
135
|
self.allow_copy_check.grid(row=1, column=3, sticky=tk.W, padx=5, pady=2)
|
|
133
136
|
|
|
134
|
-
def add_destination_accounts_frame(self, main_frame):
|
|
137
|
+
def add_destination_accounts_frame(self, main_frame: ttk.Frame) -> None:
|
|
135
138
|
# --- Destination Accounts Scrollable Area ---
|
|
136
139
|
self.destinations_outer_frame = ttk.LabelFrame(
|
|
137
140
|
main_frame, text="Destination Accounts", style="Bold.TLabelframe"
|
|
138
141
|
)
|
|
139
142
|
self.destinations_outer_frame.grid(
|
|
140
|
-
row=1, column=0, padx=5, pady=5, sticky=(tk.W, tk.E, tk.N, tk.S)
|
|
143
|
+
row=1, column=0, padx=5, pady=5, sticky=(tk.W, tk.E, tk.N, tk.S) # type: ignore
|
|
141
144
|
)
|
|
142
145
|
self.destinations_outer_frame.rowconfigure(0, weight=1)
|
|
143
146
|
self.destinations_outer_frame.columnconfigure(0, weight=1)
|
|
@@ -159,10 +162,10 @@ class TradeCopierApp(object):
|
|
|
159
162
|
|
|
160
163
|
self.scrollable_frame_for_destinations.columnconfigure(0, weight=1)
|
|
161
164
|
|
|
162
|
-
def configure_scroll_region(event):
|
|
165
|
+
def configure_scroll_region(event: tk.Event) -> None:
|
|
163
166
|
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
|
|
164
167
|
|
|
165
|
-
def configure_canvas_window(event):
|
|
168
|
+
def configure_canvas_window(event: tk.Event) -> None:
|
|
166
169
|
self.canvas.itemconfig(self.canvas_window, width=event.width)
|
|
167
170
|
|
|
168
171
|
self.scrollable_frame_for_destinations.bind(
|
|
@@ -170,7 +173,7 @@ class TradeCopierApp(object):
|
|
|
170
173
|
)
|
|
171
174
|
self.canvas.bind("<Configure>", configure_canvas_window)
|
|
172
175
|
|
|
173
|
-
def _on_mousewheel(event):
|
|
176
|
+
def _on_mousewheel(event: tk.Event) -> None:
|
|
174
177
|
scroll_val = -1 * (event.delta // 120)
|
|
175
178
|
self.canvas.yview_scroll(scroll_val, "units")
|
|
176
179
|
|
|
@@ -189,12 +192,12 @@ class TradeCopierApp(object):
|
|
|
189
192
|
|
|
190
193
|
self.add_destination_account()
|
|
191
194
|
|
|
192
|
-
def add_copier_settings(self, main_frame):
|
|
195
|
+
def add_copier_settings(self, main_frame: ttk.Frame) -> None:
|
|
193
196
|
# --- Copier Settings ---
|
|
194
197
|
settings_frame = ttk.LabelFrame(
|
|
195
198
|
self.right_panel_frame, text="Copier Settings", style="Bold.TLabelframe"
|
|
196
199
|
)
|
|
197
|
-
settings_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=5, pady=5)
|
|
200
|
+
settings_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=5, pady=5) # type: ignore
|
|
198
201
|
|
|
199
202
|
ttk.Label(settings_frame, text="Sleep Time (s)").grid(
|
|
200
203
|
row=0, column=0, sticky=tk.W, padx=5, pady=2
|
|
@@ -240,14 +243,14 @@ class TradeCopierApp(object):
|
|
|
240
243
|
columnspan=2,
|
|
241
244
|
padx=5,
|
|
242
245
|
pady=5,
|
|
243
|
-
sticky=(tk.W, tk.E, tk.N, tk.S),
|
|
246
|
+
sticky=(tk.W, tk.E, tk.N, tk.S), # type: ignore
|
|
244
247
|
)
|
|
245
248
|
|
|
246
249
|
self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=10)
|
|
247
250
|
self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
|
248
251
|
self.log_text.configure(state="disabled")
|
|
249
252
|
|
|
250
|
-
def add_logo_and_description(self):
|
|
253
|
+
def add_logo_and_description(self) -> None:
|
|
251
254
|
image = Image.open(LOGO_PATH)
|
|
252
255
|
image = image.resize((120, 120))
|
|
253
256
|
self.logo_img = ImageTk.PhotoImage(image)
|
|
@@ -262,9 +265,9 @@ class TradeCopierApp(object):
|
|
|
262
265
|
font=("Segoe UI", 10, "bold"),
|
|
263
266
|
).pack()
|
|
264
267
|
|
|
265
|
-
def add_destination_account(self):
|
|
268
|
+
def add_destination_account(self) -> None:
|
|
266
269
|
dest_row = len(self.destination_widgets)
|
|
267
|
-
dest_widgets = {}
|
|
270
|
+
dest_widgets: Dict[str, Union[ttk.Entry, ttk.Combobox]] = {}
|
|
268
271
|
|
|
269
272
|
if dest_row > 0:
|
|
270
273
|
sep = ttk.Separator(
|
|
@@ -385,13 +388,13 @@ class TradeCopierApp(object):
|
|
|
385
388
|
self.add_dest_button.grid_forget()
|
|
386
389
|
self.add_dest_button.grid(row=1, column=0, columnspan=2, pady=10, sticky=tk.EW)
|
|
387
390
|
|
|
388
|
-
def log_message(self, message):
|
|
391
|
+
def log_message(self, message: str) -> None:
|
|
389
392
|
self.log_text.configure(state="normal")
|
|
390
393
|
self.log_text.insert(tk.END, message + "\n")
|
|
391
394
|
self.log_text.configure(state="disabled")
|
|
392
395
|
self.log_text.see(tk.END)
|
|
393
396
|
|
|
394
|
-
def check_log_queue(self):
|
|
397
|
+
def check_log_queue(self) -> None:
|
|
395
398
|
try:
|
|
396
399
|
while True:
|
|
397
400
|
message = self.log_queue.get_nowait()
|
|
@@ -413,7 +416,7 @@ class TradeCopierApp(object):
|
|
|
413
416
|
else:
|
|
414
417
|
return get_symbols_from_string(symbols)
|
|
415
418
|
|
|
416
|
-
def _validate_inputs(self):
|
|
419
|
+
def _validate_inputs(self) -> bool:
|
|
417
420
|
if (
|
|
418
421
|
not self.source_login_entry.get()
|
|
419
422
|
or not self.source_password_entry.get()
|
|
@@ -463,11 +466,11 @@ class TradeCopierApp(object):
|
|
|
463
466
|
# Add more validation for time formats if needed
|
|
464
467
|
return True
|
|
465
468
|
|
|
466
|
-
def start_copier(self):
|
|
469
|
+
def start_copier(self) -> None:
|
|
467
470
|
if not self._validate_inputs():
|
|
468
471
|
return
|
|
469
472
|
|
|
470
|
-
source_config = {
|
|
473
|
+
source_config: Dict[str, Any] = {
|
|
471
474
|
"login": int(self.source_login_entry.get().strip()),
|
|
472
475
|
"password": self.source_password_entry.get().strip(),
|
|
473
476
|
"server": self.source_server_entry.get().strip(),
|
|
@@ -476,10 +479,10 @@ class TradeCopierApp(object):
|
|
|
476
479
|
"unique": not self.allow_copy_var.get(),
|
|
477
480
|
}
|
|
478
481
|
|
|
479
|
-
destinations_config = []
|
|
482
|
+
destinations_config: List[Dict[str, Any]] = []
|
|
480
483
|
for dest_widget_map in self.destination_widgets:
|
|
481
484
|
symbols_str = dest_widget_map["symbols"].get().strip()
|
|
482
|
-
dest = {
|
|
485
|
+
dest: Dict[str, Any] = {
|
|
483
486
|
"login": int(dest_widget_map["login"].get().strip()),
|
|
484
487
|
"password": dest_widget_map["password"].get().strip(),
|
|
485
488
|
"server": dest_widget_map["server"].get().strip(),
|
|
@@ -510,7 +513,7 @@ class TradeCopierApp(object):
|
|
|
510
513
|
try:
|
|
511
514
|
# Create shared shutdown event and log queue
|
|
512
515
|
self.shutdown_event = multiprocessing.Event()
|
|
513
|
-
self.log_queue = multiprocessing.Queue()
|
|
516
|
+
self.log_queue: "multiprocessing.Queue[str]" = multiprocessing.Queue()
|
|
514
517
|
self.copier_processes = []
|
|
515
518
|
|
|
516
519
|
# Spawn one process for each destination
|
|
@@ -546,7 +549,7 @@ class TradeCopierApp(object):
|
|
|
546
549
|
self.start_button.config(state=tk.NORMAL)
|
|
547
550
|
self.stop_button.config(state=tk.DISABLED)
|
|
548
551
|
|
|
549
|
-
def stop_copier(self):
|
|
552
|
+
def stop_copier(self) -> None:
|
|
550
553
|
self.log_message("Attempting to stop all Trade Copier processes...")
|
|
551
554
|
|
|
552
555
|
if not hasattr(self, "copier_processes") or not self.copier_processes:
|
|
@@ -581,12 +584,12 @@ class TradeCopierApp(object):
|
|
|
581
584
|
# Cleanup references
|
|
582
585
|
self.copier_processes = []
|
|
583
586
|
self.shutdown_event = None
|
|
584
|
-
self.log_queue = None
|
|
587
|
+
self.log_queue = None # type: ignore
|
|
585
588
|
|
|
586
589
|
self.start_button.config(state=tk.NORMAL)
|
|
587
590
|
self.stop_button.config(state=tk.DISABLED)
|
|
588
591
|
|
|
589
|
-
def browse_path(self, path_entry_widget):
|
|
592
|
+
def browse_path(self, path_entry_widget: ttk.Entry) -> None:
|
|
590
593
|
filetypes = (("Executable files", "*.exe"), ("All files", "*.*"))
|
|
591
594
|
filepath = filedialog.askopenfilename(
|
|
592
595
|
title="Select MetaTrader Terminal Executable", filetypes=filetypes
|
|
@@ -595,7 +598,7 @@ class TradeCopierApp(object):
|
|
|
595
598
|
path_entry_widget.delete(0, tk.END)
|
|
596
599
|
path_entry_widget.insert(0, filepath)
|
|
597
600
|
|
|
598
|
-
def browse_symbols_file(self, symbols_entry_widget):
|
|
601
|
+
def browse_symbols_file(self, symbols_entry_widget: ttk.Entry) -> None:
|
|
599
602
|
"""
|
|
600
603
|
Opens a file dialog to select a .txt file, reads the content,
|
|
601
604
|
and populates the given symbols_entry_widget with a comma-separated list.
|
|
@@ -623,7 +626,7 @@ class TradeCopierApp(object):
|
|
|
623
626
|
self.log_message(f"Error loading symbols: {e}")
|
|
624
627
|
|
|
625
628
|
|
|
626
|
-
def main():
|
|
629
|
+
def main() -> None:
|
|
627
630
|
"""
|
|
628
631
|
Main function to initialize and run the Trade Copier GUI.
|
|
629
632
|
"""
|
|
@@ -632,12 +635,12 @@ def main():
|
|
|
632
635
|
root.iconbitmap(os.path.abspath(ICON_PATH))
|
|
633
636
|
app = TradeCopierApp(root)
|
|
634
637
|
|
|
635
|
-
def on_closing():
|
|
638
|
+
def on_closing() -> None:
|
|
636
639
|
try:
|
|
637
640
|
if (
|
|
638
641
|
hasattr(app, "copier_process")
|
|
639
|
-
and app.copier_process
|
|
640
|
-
and app.copier_process.is_alive()
|
|
642
|
+
and app.copier_process # type: ignore
|
|
643
|
+
and app.copier_process.is_alive() # type: ignore
|
|
641
644
|
):
|
|
642
645
|
app.log_message("Window closed, stopping Trade Copier...")
|
|
643
646
|
app.stop_copier()
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import queue
|
|
2
2
|
import time
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import List, Literal, Optional
|
|
4
|
+
from typing import Any, List, Literal, Optional, Type
|
|
5
5
|
|
|
6
|
+
import pandas as pd
|
|
6
7
|
from tabulate import tabulate
|
|
7
|
-
|
|
8
|
+
|
|
8
9
|
from bbstrader.btengine.data import DataHandler
|
|
10
|
+
from bbstrader.btengine.event import Events
|
|
9
11
|
from bbstrader.btengine.execution import ExecutionHandler, SimExecutionHandler
|
|
10
12
|
from bbstrader.btengine.portfolio import Portfolio
|
|
11
13
|
from bbstrader.btengine.strategy import Strategy
|
|
@@ -13,7 +15,7 @@ from bbstrader.btengine.strategy import Strategy
|
|
|
13
15
|
__all__ = ["Backtest", "BacktestEngine", "run_backtest"]
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
class Backtest
|
|
18
|
+
class Backtest:
|
|
17
19
|
"""
|
|
18
20
|
The `Backtest()` object encapsulates the event-handling logic and essentially
|
|
19
21
|
ties together all of the other classes.
|
|
@@ -63,12 +65,12 @@ class BacktestEngine(Backtest):
|
|
|
63
65
|
initial_capital: float,
|
|
64
66
|
heartbeat: float,
|
|
65
67
|
start_date: datetime,
|
|
66
|
-
data_handler: DataHandler,
|
|
67
|
-
execution_handler: ExecutionHandler,
|
|
68
|
-
strategy: Strategy,
|
|
68
|
+
data_handler: Type[DataHandler],
|
|
69
|
+
execution_handler: Type[ExecutionHandler],
|
|
70
|
+
strategy: Type[Strategy],
|
|
69
71
|
/,
|
|
70
|
-
**kwargs,
|
|
71
|
-
):
|
|
72
|
+
**kwargs: Any,
|
|
73
|
+
) -> None:
|
|
72
74
|
"""
|
|
73
75
|
Initialises the backtest.
|
|
74
76
|
|
|
@@ -95,7 +97,7 @@ class BacktestEngine(Backtest):
|
|
|
95
97
|
self.strategy_cls = strategy
|
|
96
98
|
self.kwargs = kwargs
|
|
97
99
|
|
|
98
|
-
self.events = queue.Queue()
|
|
100
|
+
self.events: "queue.Queue[Events]" = queue.Queue()
|
|
99
101
|
|
|
100
102
|
self.signals = 0
|
|
101
103
|
self.orders = 0
|
|
@@ -105,7 +107,7 @@ class BacktestEngine(Backtest):
|
|
|
105
107
|
self.show_equity = kwargs.get("show_equity", False)
|
|
106
108
|
self.stats_file = kwargs.get("stats_file", None)
|
|
107
109
|
|
|
108
|
-
def _generate_trading_instances(self):
|
|
110
|
+
def _generate_trading_instances(self) -> None:
|
|
109
111
|
"""
|
|
110
112
|
Generates the trading instance objects from
|
|
111
113
|
their class types.
|
|
@@ -121,7 +123,7 @@ class BacktestEngine(Backtest):
|
|
|
121
123
|
self.strategy: Strategy = self.strategy_cls(
|
|
122
124
|
bars=self.data_handler, events=self.events, **self.kwargs
|
|
123
125
|
)
|
|
124
|
-
self.portfolio = Portfolio(
|
|
126
|
+
self.portfolio: Portfolio = Portfolio(
|
|
125
127
|
self.data_handler,
|
|
126
128
|
self.events,
|
|
127
129
|
self.start_date,
|
|
@@ -132,7 +134,7 @@ class BacktestEngine(Backtest):
|
|
|
132
134
|
self.events, self.data_handler, **self.kwargs
|
|
133
135
|
)
|
|
134
136
|
|
|
135
|
-
def _run_backtest(self):
|
|
137
|
+
def _run_backtest(self) -> None:
|
|
136
138
|
"""
|
|
137
139
|
Executes the backtest.
|
|
138
140
|
"""
|
|
@@ -150,9 +152,9 @@ class BacktestEngine(Backtest):
|
|
|
150
152
|
self.strategy.cash = value
|
|
151
153
|
else:
|
|
152
154
|
print("\n[======= BACKTEST COMPLETED =======]")
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
dt = self.data_handler.get_latest_bar_datetime(self.symbol_list[0])
|
|
156
|
+
if dt:
|
|
157
|
+
print(f"END DATE: {dt}")
|
|
156
158
|
print(f"TOTAL BARS: {i} ")
|
|
157
159
|
print(f"PORFOLIO VALUE: {round(value, 2)}")
|
|
158
160
|
break
|
|
@@ -184,7 +186,7 @@ class BacktestEngine(Backtest):
|
|
|
184
186
|
|
|
185
187
|
time.sleep(self.heartbeat)
|
|
186
188
|
|
|
187
|
-
def _output_performance(self):
|
|
189
|
+
def _output_performance(self) -> None:
|
|
188
190
|
"""
|
|
189
191
|
Outputs the strategy performance from the backtest.
|
|
190
192
|
"""
|
|
@@ -218,7 +220,7 @@ class BacktestEngine(Backtest):
|
|
|
218
220
|
"\n",
|
|
219
221
|
)
|
|
220
222
|
|
|
221
|
-
def simulate_trading(self):
|
|
223
|
+
def simulate_trading(self) -> pd.DataFrame:
|
|
222
224
|
"""
|
|
223
225
|
Simulates the backtest and outputs portfolio performance.
|
|
224
226
|
|
|
@@ -233,13 +235,13 @@ class BacktestEngine(Backtest):
|
|
|
233
235
|
def run_backtest(
|
|
234
236
|
symbol_list: List[str],
|
|
235
237
|
start_date: datetime,
|
|
236
|
-
data_handler: DataHandler,
|
|
237
|
-
strategy: Strategy,
|
|
238
|
-
exc_handler: Optional[ExecutionHandler] = None,
|
|
238
|
+
data_handler: Type[DataHandler],
|
|
239
|
+
strategy: Type[Strategy],
|
|
240
|
+
exc_handler: Optional[Type[ExecutionHandler]] = None,
|
|
239
241
|
initial_capital: float = 100000.0,
|
|
240
242
|
heartbeat: float = 0.0,
|
|
241
|
-
**kwargs,
|
|
242
|
-
):
|
|
243
|
+
**kwargs: Any,
|
|
244
|
+
) -> pd.DataFrame:
|
|
243
245
|
"""
|
|
244
246
|
Runs a backtest simulation based on a `DataHandler`, `Strategy`, and `ExecutionHandler`.
|
|
245
247
|
|
|
@@ -315,7 +317,7 @@ def run_backtest(
|
|
|
315
317
|
... )
|
|
316
318
|
"""
|
|
317
319
|
if exc_handler is None:
|
|
318
|
-
execution_handler = SimExecutionHandler
|
|
320
|
+
execution_handler: Type[ExecutionHandler] = SimExecutionHandler
|
|
319
321
|
else:
|
|
320
322
|
execution_handler = exc_handler
|
|
321
323
|
engine = BacktestEngine(
|
|
@@ -338,14 +340,16 @@ class CerebroEngine: ...
|
|
|
338
340
|
class ZiplineEngine: ...
|
|
339
341
|
|
|
340
342
|
|
|
341
|
-
def run_backtest_with(
|
|
343
|
+
def run_backtest_with(
|
|
344
|
+
engine: Literal["bbstrader", "cerebro", "zipline"], **kwargs: Any
|
|
345
|
+
) -> Optional[pd.DataFrame]:
|
|
342
346
|
""" """
|
|
343
347
|
if engine == "bbstrader":
|
|
344
348
|
return run_backtest(
|
|
345
|
-
symbol_list=kwargs.get("symbol_list"),
|
|
346
|
-
start_date=kwargs.get("start_date"),
|
|
347
|
-
data_handler=kwargs.get("data_handler"),
|
|
348
|
-
strategy=kwargs.get("strategy"),
|
|
349
|
+
symbol_list=kwargs.get("symbol_list"), # type: ignore
|
|
350
|
+
start_date=kwargs.get("start_date"), # type: ignore
|
|
351
|
+
data_handler=kwargs.get("data_handler"), # type: ignore
|
|
352
|
+
strategy=kwargs.get("strategy"), # type: ignore
|
|
349
353
|
exc_handler=kwargs.get("exc_handler"),
|
|
350
354
|
initial_capital=kwargs.get("initial_capital", 100000.0),
|
|
351
355
|
heartbeat=kwargs.get("heartbeat", 0.0),
|
|
@@ -357,3 +361,4 @@ def run_backtest_with(engine: Literal["bbstrader", "cerebro", "zipline"], **kwar
|
|
|
357
361
|
elif engine == "zipline":
|
|
358
362
|
# TODO:
|
|
359
363
|
raise NotImplementedError("zipline engine is not supported yet")
|
|
364
|
+
return None
|