bbstrader 0.2.6__tar.gz → 0.2.8__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.2.6/bbstrader.egg-info → bbstrader-0.2.8}/PKG-INFO +4 -4
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/btengine/data.py +13 -10
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/btengine/portfolio.py +3 -5
- bbstrader-0.2.8/bbstrader/compat.py +23 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/metatrader/account.py +1 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/metatrader/rates.py +3 -1
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/metatrader/risk.py +7 -2
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/metatrader/trade.py +69 -40
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/metatrader/utils.py +1 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/trading/execution.py +24 -21
- {bbstrader-0.2.6 → bbstrader-0.2.8/bbstrader.egg-info}/PKG-INFO +4 -4
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader.egg-info/SOURCES.txt +1 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader.egg-info/requires.txt +0 -2
- {bbstrader-0.2.6 → bbstrader-0.2.8}/requirements.txt +2 -1
- {bbstrader-0.2.6 → bbstrader-0.2.8}/setup.py +7 -2
- {bbstrader-0.2.6 → bbstrader-0.2.8}/LICENSE +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/MANIFEST.in +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/README.md +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/__ini__.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/btengine/__init__.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/btengine/backtest.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/btengine/event.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/btengine/execution.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/btengine/performance.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/btengine/strategy.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/config.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/core/__init__.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/core/data.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/core/utils.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/ibkr/__init__.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/ibkr/utils.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/metatrader/__init__.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/models/__init__.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/models/factors.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/models/ml.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/models/optimization.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/models/portfolio.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/models/risk.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/trading/__init__.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/trading/scripts.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/trading/strategies.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader/tseries.py +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader.egg-info/dependency_links.txt +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/bbstrader.egg-info/top_level.txt +0 -0
- {bbstrader-0.2.6 → bbstrader-0.2.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: bbstrader
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Simplified Investment & Trading Toolkit
|
|
5
5
|
Home-page: https://github.com/bbalouki/bbstrader
|
|
6
6
|
Download-URL: https://pypi.org/project/bbstrader/
|
|
@@ -19,6 +19,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
21
|
Classifier: Operating System :: Microsoft :: Windows
|
|
22
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
23
|
+
Classifier: Operating System :: MacOS
|
|
22
24
|
Classifier: License :: OSI Approved :: MIT License
|
|
23
25
|
Description-Content-Type: text/markdown
|
|
24
26
|
License-File: LICENSE
|
|
@@ -53,8 +55,7 @@ Requires-Dist: financetoolkit
|
|
|
53
55
|
Requires-Dist: tables
|
|
54
56
|
Requires-Dist: lightgbm
|
|
55
57
|
Requires-Dist: alphalens-reloaded
|
|
56
|
-
|
|
57
|
-
Requires-Dist: MetaTrader5; extra == "mt5"
|
|
58
|
+
Requires-Dist: MetaTrader5
|
|
58
59
|
Dynamic: author
|
|
59
60
|
Dynamic: author-email
|
|
60
61
|
Dynamic: classifier
|
|
@@ -66,7 +67,6 @@ Dynamic: keywords
|
|
|
66
67
|
Dynamic: license
|
|
67
68
|
Dynamic: maintainer
|
|
68
69
|
Dynamic: project-url
|
|
69
|
-
Dynamic: provides-extra
|
|
70
70
|
Dynamic: requires-dist
|
|
71
71
|
Dynamic: summary
|
|
72
72
|
|
|
@@ -203,10 +203,11 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
203
203
|
self.symbol_data[s] = self.symbol_data[s].reindex(
|
|
204
204
|
index=comb_index, method="pad"
|
|
205
205
|
)
|
|
206
|
+
if "adj_close" not in new_names:
|
|
207
|
+
self.columns.append("adj_close")
|
|
208
|
+
self.symbol_data[s]["adj_close"] = self.symbol_data[s]["close"]
|
|
206
209
|
self.symbol_data[s]["returns"] = (
|
|
207
|
-
self.symbol_data[s][
|
|
208
|
-
"adj_close" if "adj_close" in new_names else "close"
|
|
209
|
-
]
|
|
210
|
+
self.symbol_data[s]["adj_close" if "adj_close" in new_names else "close"]
|
|
210
211
|
.pct_change()
|
|
211
212
|
.dropna()
|
|
212
213
|
)
|
|
@@ -229,7 +230,7 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
229
230
|
try:
|
|
230
231
|
bars_list = self.latest_symbol_data[symbol]
|
|
231
232
|
except KeyError:
|
|
232
|
-
print("
|
|
233
|
+
print(f"{symbol} not available in the historical data set.")
|
|
233
234
|
raise
|
|
234
235
|
else:
|
|
235
236
|
return bars_list[-1]
|
|
@@ -244,7 +245,7 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
244
245
|
try:
|
|
245
246
|
bars_list = self.latest_symbol_data[symbol]
|
|
246
247
|
except KeyError:
|
|
247
|
-
print("
|
|
248
|
+
print(f"{symbol} not available in the historical data set.")
|
|
248
249
|
raise
|
|
249
250
|
else:
|
|
250
251
|
if df:
|
|
@@ -260,7 +261,7 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
260
261
|
try:
|
|
261
262
|
bars_list = self.latest_symbol_data[symbol]
|
|
262
263
|
except KeyError:
|
|
263
|
-
print("
|
|
264
|
+
print(f"{symbol} not available in the historical data set.")
|
|
264
265
|
raise
|
|
265
266
|
else:
|
|
266
267
|
return bars_list[-1][0]
|
|
@@ -274,7 +275,7 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
274
275
|
try:
|
|
275
276
|
bars_list = self.get_latest_bars(symbol, N)
|
|
276
277
|
except KeyError:
|
|
277
|
-
print("
|
|
278
|
+
print(f"{symbol} not available in the historical data set for .")
|
|
278
279
|
raise
|
|
279
280
|
else:
|
|
280
281
|
return [b[0] for b in bars_list]
|
|
@@ -287,14 +288,14 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
287
288
|
try:
|
|
288
289
|
bars_list = self.latest_symbol_data[symbol]
|
|
289
290
|
except KeyError:
|
|
290
|
-
print("
|
|
291
|
+
print(f"{symbol} not available in the historical data set.")
|
|
291
292
|
raise
|
|
292
293
|
else:
|
|
293
294
|
try:
|
|
294
295
|
return getattr(bars_list[-1][1], val_type)
|
|
295
296
|
except AttributeError:
|
|
296
297
|
print(
|
|
297
|
-
f"Value type {val_type} not available in the historical data set."
|
|
298
|
+
f"Value type {val_type} not available in the historical data set for {symbol}."
|
|
298
299
|
)
|
|
299
300
|
raise
|
|
300
301
|
|
|
@@ -306,7 +307,7 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
306
307
|
try:
|
|
307
308
|
bars_list = self.get_latest_bars(symbol, N, df=False)
|
|
308
309
|
except KeyError:
|
|
309
|
-
print("
|
|
310
|
+
print(f"{symbol} not available in the historical data set.")
|
|
310
311
|
raise
|
|
311
312
|
else:
|
|
312
313
|
try:
|
|
@@ -497,6 +498,8 @@ class YFDataHandler(BaseCSVDataHandler):
|
|
|
497
498
|
multi_level_index=False,
|
|
498
499
|
progress=False,
|
|
499
500
|
)
|
|
501
|
+
if "Adj Close" not in data.columns:
|
|
502
|
+
data["Adj Close"] = data["Close"]
|
|
500
503
|
if data.empty:
|
|
501
504
|
raise ValueError(f"No data found for {symbol}")
|
|
502
505
|
data.to_csv(filepath)
|
|
@@ -195,14 +195,12 @@ class Portfolio(object):
|
|
|
195
195
|
try:
|
|
196
196
|
price = self.bars.get_latest_bar_value(symbol, "adj_close")
|
|
197
197
|
return price
|
|
198
|
-
except AttributeError:
|
|
198
|
+
except (AttributeError, KeyError, ValueError):
|
|
199
199
|
try:
|
|
200
200
|
price = self.bars.get_latest_bar_value(symbol, "close")
|
|
201
201
|
return price
|
|
202
|
-
except AttributeError:
|
|
203
|
-
|
|
204
|
-
"Bars object must have 'adj_close' or 'close' prices"
|
|
205
|
-
)
|
|
202
|
+
except (AttributeError, KeyError, ValueError):
|
|
203
|
+
return 0.0
|
|
206
204
|
|
|
207
205
|
def update_timeindex(self, event: MarketEvent):
|
|
208
206
|
"""
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def setup_mock_metatrader():
|
|
6
|
+
"""Mock MetaTrader5 on Linux to prevent import errors."""
|
|
7
|
+
if platform.system() == "Linux":
|
|
8
|
+
from unittest.mock import MagicMock
|
|
9
|
+
|
|
10
|
+
class Mock(MagicMock):
|
|
11
|
+
@classmethod
|
|
12
|
+
def __getattr__(cls, name):
|
|
13
|
+
return MagicMock()
|
|
14
|
+
|
|
15
|
+
MOCK_MODULES = ["MetaTrader5"]
|
|
16
|
+
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
|
|
17
|
+
|
|
18
|
+
print(
|
|
19
|
+
"Warning: MetaTrader5 is not available on Linux. A mock version is being used."
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
setup_mock_metatrader()
|
|
@@ -4,6 +4,7 @@ import urllib.request
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
|
|
6
6
|
|
|
7
|
+
from bbstrader import compat # noqa: F401
|
|
7
8
|
import MetaTrader5 as mt5
|
|
8
9
|
import pandas as pd
|
|
9
10
|
from currency_converter import SINGLE_DAY_ECB_URL, CurrencyConverter
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from typing import Optional, Union
|
|
3
3
|
|
|
4
|
+
from bbstrader import compat # noqa: F401
|
|
4
5
|
import MetaTrader5 as Mt5
|
|
5
6
|
import pandas as pd
|
|
6
7
|
from exchange_calendars import get_calendar, get_calendar_names
|
|
7
8
|
from pandas.tseries.holiday import USFederalHolidayCalendar
|
|
8
9
|
from pandas.tseries.offsets import CustomBusinessDay
|
|
9
10
|
|
|
11
|
+
|
|
10
12
|
from bbstrader.metatrader.account import AMG_EXCHANGES, Account, check_mt5_connection
|
|
11
13
|
from bbstrader.metatrader.utils import TIMEFRAMES, TimeFrame, raise_mt5_error
|
|
12
14
|
|
|
@@ -172,7 +174,7 @@ class Rates(object):
|
|
|
172
174
|
480,
|
|
173
175
|
720,
|
|
174
176
|
]:
|
|
175
|
-
key = f"{minutes//60}h" if minutes >= 60 else f"{minutes}m"
|
|
177
|
+
key = f"{minutes // 60}h" if minutes >= 60 else f"{minutes}m"
|
|
176
178
|
time_frame_mapping[key] = int(td * (60 / minutes) * sd)
|
|
177
179
|
time_frame_mapping["D1"] = int(td)
|
|
178
180
|
|
|
@@ -2,6 +2,7 @@ import random
|
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from typing import Any, Dict, Optional, Union
|
|
4
4
|
|
|
5
|
+
from bbstrader import compat # noqa: F401
|
|
5
6
|
import MetaTrader5 as Mt5
|
|
6
7
|
from scipy.stats import norm
|
|
7
8
|
|
|
@@ -329,7 +330,9 @@ class RiskManagement(Account):
|
|
|
329
330
|
tf_int = self._convert_time_frame(self._tf)
|
|
330
331
|
interval = round((minutes / tf_int) * 252)
|
|
331
332
|
|
|
332
|
-
rate = Rates(
|
|
333
|
+
rate = Rates(
|
|
334
|
+
self.symbol, timeframe=self._tf, start_pos=0, count=interval, **self.kwargs
|
|
335
|
+
)
|
|
333
336
|
returns = rate.returns * 100
|
|
334
337
|
std = returns.std()
|
|
335
338
|
point = self.get_symbol_info(self.symbol).point
|
|
@@ -381,7 +384,9 @@ class RiskManagement(Account):
|
|
|
381
384
|
tf_int = self._convert_time_frame(tf)
|
|
382
385
|
interval = round((minutes / tf_int) * 252)
|
|
383
386
|
|
|
384
|
-
rate = Rates(
|
|
387
|
+
rate = Rates(
|
|
388
|
+
self.symbol, timeframe=tf, start_pos=0, count=interval, **self.kwargs
|
|
389
|
+
)
|
|
385
390
|
returns = rate.returns * 100
|
|
386
391
|
p = self.get_account_info().margin_free
|
|
387
392
|
mu = returns.mean()
|
|
@@ -4,6 +4,7 @@ from datetime import datetime
|
|
|
4
4
|
from logging import Logger
|
|
5
5
|
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple
|
|
6
6
|
|
|
7
|
+
from bbstrader import compat # noqa: F401
|
|
7
8
|
import MetaTrader5 as Mt5
|
|
8
9
|
import pandas as pd
|
|
9
10
|
from tabulate import tabulate
|
|
@@ -89,7 +90,7 @@ class Trade(RiskManagement):
|
|
|
89
90
|
symbol: str = "EURUSD",
|
|
90
91
|
expert_name: str = "bbstrader",
|
|
91
92
|
expert_id: int = 9818,
|
|
92
|
-
version: str = "
|
|
93
|
+
version: str = "2.0",
|
|
93
94
|
target: float = 5.0,
|
|
94
95
|
start_time: str = "1:00",
|
|
95
96
|
finishing_time: str = "23:00",
|
|
@@ -129,7 +130,7 @@ class Trade(RiskManagement):
|
|
|
129
130
|
- sl
|
|
130
131
|
- tp
|
|
131
132
|
- be
|
|
132
|
-
See the RiskManagement class for more details on these parameters.
|
|
133
|
+
See the ``bbstrader.metatrader.risk.RiskManagement`` class for more details on these parameters.
|
|
133
134
|
See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
|
|
134
135
|
"""
|
|
135
136
|
# Call the parent class constructor first
|
|
@@ -604,7 +605,7 @@ class Trade(RiskManagement):
|
|
|
604
605
|
result = self.send_order(request)
|
|
605
606
|
if result.retcode == Mt5.TRADE_RETCODE_DONE:
|
|
606
607
|
break
|
|
607
|
-
elif result.retcode == Mt5.TRADE_RETCODE_INVALID_VOLUME:
|
|
608
|
+
elif result.retcode == Mt5.TRADE_RETCODE_INVALID_VOLUME: # 10014
|
|
608
609
|
new_volume = int(request["volume"])
|
|
609
610
|
if new_volume >= 1:
|
|
610
611
|
request["volume"] = new_volume
|
|
@@ -649,25 +650,26 @@ class Trade(RiskManagement):
|
|
|
649
650
|
if type == "BMKT" or type == "SMKT":
|
|
650
651
|
self.opened_positions.append(result.order)
|
|
651
652
|
positions = self.get_positions(symbol=self.symbol)
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if position.
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
653
|
+
if positions is not None:
|
|
654
|
+
for position in positions:
|
|
655
|
+
if position.ticket == result.order:
|
|
656
|
+
if position.type == 0:
|
|
657
|
+
order_type = "BUY"
|
|
658
|
+
self.buy_positions.append(position.ticket)
|
|
659
|
+
else:
|
|
660
|
+
order_type = "SELL"
|
|
661
|
+
self.sell_positions.append(position.ticket)
|
|
662
|
+
profit = round(self.get_account_info().profit, 5)
|
|
663
|
+
order_info = (
|
|
664
|
+
f"2. {order_type} Position Opened, Symbol: {self.symbol}, Price: @{round(position.price_open, 5)}, "
|
|
665
|
+
f"Sl: @{position.sl} Tp: @{position.tp}"
|
|
666
|
+
)
|
|
667
|
+
self.logger.info(order_info)
|
|
668
|
+
pos_info = (
|
|
669
|
+
f"3. [OPEN POSITIONS ON {self.symbol} = {len(positions)}, ACCOUNT OPEN PnL = {profit} "
|
|
670
|
+
f"{self.get_account_info().currency}]\n"
|
|
671
|
+
)
|
|
672
|
+
self.logger.info(pos_info)
|
|
671
673
|
else:
|
|
672
674
|
msg = trade_retcode_message(result.retcode)
|
|
673
675
|
self.logger.error(
|
|
@@ -921,21 +923,35 @@ class Trade(RiskManagement):
|
|
|
921
923
|
return current_profit >= th_profit
|
|
922
924
|
return False
|
|
923
925
|
|
|
926
|
+
def _get_trail_after_points(self, trail_after_points: int | str) -> int:
|
|
927
|
+
if isinstance(trail_after_points, str):
|
|
928
|
+
if trail_after_points == "SL":
|
|
929
|
+
return self.get_stop_loss()
|
|
930
|
+
elif trail_after_points == "TP":
|
|
931
|
+
return self.get_take_profit()
|
|
932
|
+
elif trail_after_points == "BE":
|
|
933
|
+
return self.get_break_even()
|
|
934
|
+
# TODO: Add other combinations (e.g. "SL+TP", "SL+BE", "TP+BE", "SL*N", etc.)
|
|
935
|
+
return trail_after_points
|
|
936
|
+
|
|
924
937
|
def break_even(
|
|
925
938
|
self,
|
|
926
939
|
mm=True,
|
|
927
940
|
id: Optional[int] = None,
|
|
928
941
|
trail: Optional[bool] = True,
|
|
929
942
|
stop_trail: Optional[int] = None,
|
|
930
|
-
trail_after_points:
|
|
943
|
+
trail_after_points: int | str = None,
|
|
931
944
|
be_plus_points: Optional[int] = None,
|
|
932
945
|
):
|
|
933
946
|
"""
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
If
|
|
938
|
-
|
|
947
|
+
Manages the break-even level of a trading position.
|
|
948
|
+
|
|
949
|
+
This function checks whether it is time to set a break-even stop loss for an open position.
|
|
950
|
+
If the break-even level is already set, it monitors price movement and updates the stop loss
|
|
951
|
+
accordingly if the `trail` parameter is enabled.
|
|
952
|
+
|
|
953
|
+
When `trail` is enabled, the function dynamically adjusts the break-even level based on the
|
|
954
|
+
`trail_after_points` and `stop_trail` parameters.
|
|
939
955
|
|
|
940
956
|
Args:
|
|
941
957
|
id (int): The strategy ID or expert ID.
|
|
@@ -943,8 +959,12 @@ class Trade(RiskManagement):
|
|
|
943
959
|
trail (bool): Whether to trail the stop loss or not.
|
|
944
960
|
stop_trail (int): Number of points to trail the stop loss by.
|
|
945
961
|
It represent the distance from the current price to the stop loss.
|
|
946
|
-
trail_after_points (int): Number of points in profit
|
|
962
|
+
trail_after_points (int, str): Number of points in profit
|
|
947
963
|
from where the strategy will start to trail the stop loss.
|
|
964
|
+
If set to str, it must be one of the following values:
|
|
965
|
+
- 'SL' to trail the stop loss after the profit reaches the stop loss level in points.
|
|
966
|
+
- 'TP' to trail the stop loss after the profit reaches the take profit level in points.
|
|
967
|
+
- 'BE' to trail the stop loss after the profit reaches the break-even level in points.
|
|
948
968
|
be_plus_points (int): Number of points to add to the break-even level.
|
|
949
969
|
Represents the minimum profit to secure.
|
|
950
970
|
"""
|
|
@@ -955,16 +975,19 @@ class Trade(RiskManagement):
|
|
|
955
975
|
positions = self.get_positions(symbol=self.symbol)
|
|
956
976
|
be = self.get_break_even()
|
|
957
977
|
if trail_after_points is not None:
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
978
|
+
if isinstance(trail_after_points, int):
|
|
979
|
+
assert trail_after_points > be, (
|
|
980
|
+
"trail_after_points must be greater than break even or set to None"
|
|
981
|
+
)
|
|
982
|
+
trail_after_points = self._get_trail_after_points(trail_after_points)
|
|
961
983
|
if positions is not None:
|
|
962
984
|
for position in positions:
|
|
963
985
|
if position.magic == Id:
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
986
|
+
symbol_info = self.get_symbol_info(self.symbol)
|
|
987
|
+
size = symbol_info.trade_tick_size
|
|
988
|
+
value = symbol_info.trade_tick_value
|
|
989
|
+
point = symbol_info.point
|
|
990
|
+
digits = symbol_info.digits
|
|
968
991
|
points = position.profit * (size / value / position.volume)
|
|
969
992
|
break_even = float(points / point) >= be
|
|
970
993
|
if break_even:
|
|
@@ -1054,7 +1077,10 @@ class Trade(RiskManagement):
|
|
|
1054
1077
|
spread = self.get_symbol_info(self.symbol).spread
|
|
1055
1078
|
fees = self.get_stats()[0]["average_fee"] * -1
|
|
1056
1079
|
risk = self.currency_risk()["trade_profit"]
|
|
1057
|
-
|
|
1080
|
+
try:
|
|
1081
|
+
fees_points = round((fees / risk), 3)
|
|
1082
|
+
except ZeroDivisionError:
|
|
1083
|
+
fees_points = 0
|
|
1058
1084
|
# If Buy
|
|
1059
1085
|
if position.type == 0 and position.price_current > position.price_open:
|
|
1060
1086
|
# Calculate the break-even level and price
|
|
@@ -1161,7 +1187,10 @@ class Trade(RiskManagement):
|
|
|
1161
1187
|
point = self.get_symbol_info(self.symbol).point
|
|
1162
1188
|
fees = self.get_stats()[0]["average_fee"] * -1
|
|
1163
1189
|
risk = self.currency_risk()["trade_profit"]
|
|
1164
|
-
|
|
1190
|
+
try:
|
|
1191
|
+
min_be = round((fees / risk)) + 2
|
|
1192
|
+
except ZeroDivisionError:
|
|
1193
|
+
min_be = self.symbol_info(self.symbol).spread
|
|
1165
1194
|
be = self.get_break_even()
|
|
1166
1195
|
if th is not None:
|
|
1167
1196
|
win_be = th
|
|
@@ -1189,7 +1218,7 @@ class Trade(RiskManagement):
|
|
|
1189
1218
|
# The first one is the opening order
|
|
1190
1219
|
# The second is the closing order
|
|
1191
1220
|
history = self.get_trades_history(position=position, to_df=False)
|
|
1192
|
-
if len(history) == 2:
|
|
1221
|
+
if history is not None and len(history) == 2:
|
|
1193
1222
|
profit += history[1].profit
|
|
1194
1223
|
commission += history[0].commission
|
|
1195
1224
|
swap += history[0].swap
|
|
@@ -1460,7 +1489,7 @@ class Trade(RiskManagement):
|
|
|
1460
1489
|
for position in self.opened_positions:
|
|
1461
1490
|
time.sleep(0.1)
|
|
1462
1491
|
history = self.get_trades_history(position=position, to_df=False)
|
|
1463
|
-
if len(history) == 2:
|
|
1492
|
+
if history is not None and len(history) == 2:
|
|
1464
1493
|
result = history[1].profit
|
|
1465
1494
|
comm = history[0].commission
|
|
1466
1495
|
swap = history[0].swap
|
|
@@ -110,6 +110,7 @@ def _mt5_execution(
|
|
|
110
110
|
notify = kwargs.get("notify", False)
|
|
111
111
|
signal_tickers = kwargs.get("signal_tickers", symbols)
|
|
112
112
|
debug_mode = kwargs.get("debug_mode", False)
|
|
113
|
+
delay = kwargs.get("delay", 0)
|
|
113
114
|
if notify:
|
|
114
115
|
telegram = kwargs.get("telegram", False)
|
|
115
116
|
bot_token = kwargs.get("bot_token")
|
|
@@ -192,8 +193,9 @@ def _mt5_execution(
|
|
|
192
193
|
symbol_type = account.get_symbol_type(symbol)
|
|
193
194
|
desc = account.get_symbol_info(symbol).description
|
|
194
195
|
sigmsg = (
|
|
195
|
-
f"SIGNAL = {signal},
|
|
196
|
-
f"
|
|
196
|
+
f"SIGNAL = {signal}, \nSYMBOL={symbol}, \nTYPE={symbol_type}, \nDESCRIPTION={desc}, "
|
|
197
|
+
f"\nPRICE={price}, \nSTOPLIMIT={stoplimit}, \nSTRATEGY={STRATEGY}, \nTIMEFRAME={time_frame}"
|
|
198
|
+
f"\nBROKER={account.broker.name}, \nTIMESTAMP={datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
197
199
|
)
|
|
198
200
|
msg = f"Sending {signal} Order ... SYMBOL={symbol}, STRATEGY={STRATEGY}"
|
|
199
201
|
tfmsg = f"Time Frame Not completed !!! SYMBOL={symbol}, STRATEGY={STRATEGY}"
|
|
@@ -399,10 +401,11 @@ def _mt5_execution(
|
|
|
399
401
|
elif trade_time % iter_time == 0:
|
|
400
402
|
time_intervals += iter_time
|
|
401
403
|
else:
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
404
|
+
if use_trade_time:
|
|
405
|
+
raise ValueError(
|
|
406
|
+
f"iter_time must be a multiple of the {time_frame} !!!"
|
|
407
|
+
f"(e.g., if time_frame is 15m, iter_time must be 1.5, 3, 5, 15 etc)"
|
|
408
|
+
)
|
|
406
409
|
try:
|
|
407
410
|
FRIDAY = "friday"
|
|
408
411
|
check_mt5_connection(**kwargs)
|
|
@@ -449,15 +452,15 @@ def _mt5_execution(
|
|
|
449
452
|
period_end_action == "sleep" and today != FRIDAY or not closing
|
|
450
453
|
):
|
|
451
454
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
452
|
-
sleepmsg(sleep_time)
|
|
453
|
-
time.sleep(60 * sleep_time)
|
|
455
|
+
sleepmsg(sleep_time + delay)
|
|
456
|
+
time.sleep(60 * sleep_time + delay)
|
|
454
457
|
logger.info(sessionmsg)
|
|
455
458
|
elif period_end_action == "sleep" and today == FRIDAY:
|
|
456
459
|
sleep_time = trades_instances[symbols[-1]].sleep_time(
|
|
457
460
|
weekend=True
|
|
458
461
|
)
|
|
459
|
-
sleepmsg(sleep_time)
|
|
460
|
-
time.sleep(60 * sleep_time)
|
|
462
|
+
sleepmsg(sleep_time + delay)
|
|
463
|
+
time.sleep(60 * sleep_time + delay)
|
|
461
464
|
logger.info(sessionmsg)
|
|
462
465
|
|
|
463
466
|
elif period.lower() == "week":
|
|
@@ -476,8 +479,8 @@ def _mt5_execution(
|
|
|
476
479
|
|
|
477
480
|
if day_end and today != FRIDAY:
|
|
478
481
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
479
|
-
sleepmsg(sleep_time)
|
|
480
|
-
time.sleep(60 * sleep_time)
|
|
482
|
+
sleepmsg(sleep_time + delay)
|
|
483
|
+
time.sleep(60 * sleep_time + delay)
|
|
481
484
|
logger.info(sessionmsg)
|
|
482
485
|
elif day_end and today == FRIDAY:
|
|
483
486
|
strategy.perform_period_end_checks()
|
|
@@ -487,8 +490,8 @@ def _mt5_execution(
|
|
|
487
490
|
sleep_time = trades_instances[symbols[-1]].sleep_time(
|
|
488
491
|
weekend=True
|
|
489
492
|
)
|
|
490
|
-
sleepmsg(sleep_time)
|
|
491
|
-
time.sleep(60 * sleep_time)
|
|
493
|
+
sleepmsg(sleep_time + delay)
|
|
494
|
+
time.sleep(60 * sleep_time + delay)
|
|
492
495
|
logger.info(sessionmsg)
|
|
493
496
|
|
|
494
497
|
elif period.lower() == "month":
|
|
@@ -501,7 +504,7 @@ def _mt5_execution(
|
|
|
501
504
|
elif (
|
|
502
505
|
trade.days_end()
|
|
503
506
|
and today == FRIDAY
|
|
504
|
-
and num_days
|
|
507
|
+
and num_days >= 20
|
|
505
508
|
) and closing:
|
|
506
509
|
for id in expert_ids:
|
|
507
510
|
trade.close_positions(
|
|
@@ -511,17 +514,17 @@ def _mt5_execution(
|
|
|
511
514
|
trade.statistics(save=True)
|
|
512
515
|
if day_end and today != FRIDAY:
|
|
513
516
|
sleep_time = trades_instances[symbols[-1]].sleep_time()
|
|
514
|
-
sleepmsg(sleep_time)
|
|
515
|
-
time.sleep(60 * sleep_time)
|
|
517
|
+
sleepmsg(sleep_time + delay)
|
|
518
|
+
time.sleep(60 * sleep_time + delay)
|
|
516
519
|
logger.info(sessionmsg)
|
|
517
520
|
num_days += 1
|
|
518
521
|
elif day_end and today == FRIDAY:
|
|
519
522
|
sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
|
|
520
|
-
sleepmsg(sleep_time)
|
|
521
|
-
time.sleep(60 * sleep_time)
|
|
523
|
+
sleepmsg(sleep_time + delay)
|
|
524
|
+
time.sleep(60 * sleep_time + delay)
|
|
522
525
|
logger.info(sessionmsg)
|
|
523
526
|
num_days += 1
|
|
524
|
-
elif day_end and today == FRIDAY and num_days
|
|
527
|
+
elif day_end and today == FRIDAY and num_days >= 20:
|
|
525
528
|
strategy.perform_period_end_checks()
|
|
526
529
|
break
|
|
527
530
|
except Exception:
|
|
@@ -618,7 +621,7 @@ class MT5ExecutionEngine:
|
|
|
618
621
|
optimizer: str = "equal",
|
|
619
622
|
trail: bool = True,
|
|
620
623
|
stop_trail: Optional[int] = None,
|
|
621
|
-
trail_after_points:
|
|
624
|
+
trail_after_points: int | str = None,
|
|
622
625
|
be_plus_points: Optional[int] = None,
|
|
623
626
|
show_positions_orders: bool = False,
|
|
624
627
|
iter_time: int | float = 5,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: bbstrader
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Simplified Investment & Trading Toolkit
|
|
5
5
|
Home-page: https://github.com/bbalouki/bbstrader
|
|
6
6
|
Download-URL: https://pypi.org/project/bbstrader/
|
|
@@ -19,6 +19,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
21
|
Classifier: Operating System :: Microsoft :: Windows
|
|
22
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
23
|
+
Classifier: Operating System :: MacOS
|
|
22
24
|
Classifier: License :: OSI Approved :: MIT License
|
|
23
25
|
Description-Content-Type: text/markdown
|
|
24
26
|
License-File: LICENSE
|
|
@@ -53,8 +55,7 @@ Requires-Dist: financetoolkit
|
|
|
53
55
|
Requires-Dist: tables
|
|
54
56
|
Requires-Dist: lightgbm
|
|
55
57
|
Requires-Dist: alphalens-reloaded
|
|
56
|
-
|
|
57
|
-
Requires-Dist: MetaTrader5; extra == "mt5"
|
|
58
|
+
Requires-Dist: MetaTrader5
|
|
58
59
|
Dynamic: author
|
|
59
60
|
Dynamic: author-email
|
|
60
61
|
Dynamic: classifier
|
|
@@ -66,7 +67,6 @@ Dynamic: keywords
|
|
|
66
67
|
Dynamic: license
|
|
67
68
|
Dynamic: maintainer
|
|
68
69
|
Dynamic: project-url
|
|
69
|
-
Dynamic: provides-extra
|
|
70
70
|
Dynamic: requires-dist
|
|
71
71
|
Dynamic: summary
|
|
72
72
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import sys
|
|
3
3
|
from os import path
|
|
4
|
+
import platform
|
|
4
5
|
|
|
5
6
|
from setuptools import setup
|
|
6
7
|
|
|
@@ -16,7 +17,10 @@ with io.open(path.join(here, "README.md"), encoding="utf-8") as f:
|
|
|
16
17
|
with io.open(path.join(here, "requirements.txt"), encoding="utf-8") as f:
|
|
17
18
|
REQUIREMENTS = [line.rstrip() for line in f]
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
if platform.system() == "Linux":
|
|
21
|
+
REQUIREMENTS.remove("MetaTrader5")
|
|
22
|
+
|
|
23
|
+
VERSION = "0.2.08"
|
|
20
24
|
DESCRIPTION = "Simplified Investment & Trading Toolkit"
|
|
21
25
|
|
|
22
26
|
KEYWORDS = [
|
|
@@ -54,6 +58,8 @@ CLASSIFIERS = [
|
|
|
54
58
|
"Programming Language :: Python :: 3.11",
|
|
55
59
|
"Programming Language :: Python :: 3.12",
|
|
56
60
|
"Operating System :: Microsoft :: Windows",
|
|
61
|
+
"Operating System :: POSIX :: Linux",
|
|
62
|
+
"Operating System :: MacOS",
|
|
57
63
|
"License :: OSI Approved :: MIT License",
|
|
58
64
|
]
|
|
59
65
|
|
|
@@ -88,7 +94,6 @@ setup(
|
|
|
88
94
|
packages=INLCUDE,
|
|
89
95
|
install_requires=REQUIREMENTS,
|
|
90
96
|
extras_require={
|
|
91
|
-
"MT5": ["MetaTrader5"],
|
|
92
97
|
# 'zipline': ['zipline'],
|
|
93
98
|
# 'cerebro': ['backtrader'],
|
|
94
99
|
},
|
|
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
|