bbstrader 0.2.96__py3-none-any.whl → 0.2.97__py3-none-any.whl
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/__main__.py +1 -0
- bbstrader/btengine/data.py +6 -6
- bbstrader/btengine/execution.py +1 -1
- bbstrader/btengine/strategy.py +87 -99
- bbstrader/core/utils.py +0 -58
- bbstrader/metatrader/copier.py +1 -0
- bbstrader/metatrader/trade.py +157 -11
- bbstrader/models/risk.py +2 -2
- bbstrader/trading/execution.py +14 -6
- bbstrader/trading/strategies.py +6 -0
- {bbstrader-0.2.96.dist-info → bbstrader-0.2.97.dist-info}/METADATA +2 -1
- {bbstrader-0.2.96.dist-info → bbstrader-0.2.97.dist-info}/RECORD +16 -16
- {bbstrader-0.2.96.dist-info → bbstrader-0.2.97.dist-info}/WHEEL +0 -0
- {bbstrader-0.2.96.dist-info → bbstrader-0.2.97.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.2.96.dist-info → bbstrader-0.2.97.dist-info}/licenses/LICENSE +0 -0
- {bbstrader-0.2.96.dist-info → bbstrader-0.2.97.dist-info}/top_level.txt +0 -0
bbstrader/__main__.py
CHANGED
bbstrader/btengine/data.py
CHANGED
|
@@ -66,21 +66,21 @@ class DataHandler(metaclass=ABCMeta):
|
|
|
66
66
|
"""
|
|
67
67
|
Returns the last bar updated.
|
|
68
68
|
"""
|
|
69
|
-
|
|
69
|
+
pass
|
|
70
70
|
|
|
71
71
|
@abstractmethod
|
|
72
72
|
def get_latest_bars(self, symbol, N=1, df=True) -> pd.DataFrame | List[pd.Series]:
|
|
73
73
|
"""
|
|
74
74
|
Returns the last N bars updated.
|
|
75
75
|
"""
|
|
76
|
-
|
|
76
|
+
pass
|
|
77
77
|
|
|
78
78
|
@abstractmethod
|
|
79
79
|
def get_latest_bar_datetime(self, symbol) -> datetime | pd.Timestamp:
|
|
80
80
|
"""
|
|
81
81
|
Returns a Python datetime object for the last bar.
|
|
82
82
|
"""
|
|
83
|
-
|
|
83
|
+
pass
|
|
84
84
|
|
|
85
85
|
@abstractmethod
|
|
86
86
|
def get_latest_bar_value(self, symbol, val_type) -> float:
|
|
@@ -88,7 +88,7 @@ class DataHandler(metaclass=ABCMeta):
|
|
|
88
88
|
Returns one of the Open, High, Low, Close, Adj Close, Volume or Returns
|
|
89
89
|
from the last bar.
|
|
90
90
|
"""
|
|
91
|
-
|
|
91
|
+
pass
|
|
92
92
|
|
|
93
93
|
@abstractmethod
|
|
94
94
|
def get_latest_bars_values(self, symbol, val_type, N=1) -> np.ndarray:
|
|
@@ -96,7 +96,7 @@ class DataHandler(metaclass=ABCMeta):
|
|
|
96
96
|
Returns the last N bar values from the
|
|
97
97
|
latest_symbol list, or N-k if less available.
|
|
98
98
|
"""
|
|
99
|
-
|
|
99
|
+
pass
|
|
100
100
|
|
|
101
101
|
@abstractmethod
|
|
102
102
|
def update_bars(self):
|
|
@@ -105,7 +105,7 @@ class DataHandler(metaclass=ABCMeta):
|
|
|
105
105
|
in a tuple OHLCVI format: (datetime, Open, High, Low,
|
|
106
106
|
Close, Adj Close, Volume, Retruns).
|
|
107
107
|
"""
|
|
108
|
-
|
|
108
|
+
pass
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
class BaseCSVDataHandler(DataHandler):
|
bbstrader/btengine/execution.py
CHANGED
bbstrader/btengine/strategy.py
CHANGED
|
@@ -12,7 +12,7 @@ from loguru import logger
|
|
|
12
12
|
from bbstrader.btengine.data import DataHandler
|
|
13
13
|
from bbstrader.btengine.event import FillEvent, SignalEvent
|
|
14
14
|
from bbstrader.config import BBSTRADER_DIR
|
|
15
|
-
from bbstrader.
|
|
15
|
+
from bbstrader.metatrader.trade import TradeSignal
|
|
16
16
|
from bbstrader.metatrader.account import (
|
|
17
17
|
Account,
|
|
18
18
|
AdmiralMarktsGroup,
|
|
@@ -55,7 +55,7 @@ class Strategy(metaclass=ABCMeta):
|
|
|
55
55
|
|
|
56
56
|
@abstractmethod
|
|
57
57
|
def calculate_signals(self, *args, **kwargs) -> List[TradeSignal]:
|
|
58
|
-
|
|
58
|
+
pass
|
|
59
59
|
|
|
60
60
|
def check_pending_orders(self, *args, **kwargs): ...
|
|
61
61
|
def get_update_from_portfolio(self, *args, **kwargs): ...
|
|
@@ -503,108 +503,96 @@ class MT5Strategy(Strategy):
|
|
|
503
503
|
"""
|
|
504
504
|
Check for pending orders and handle them accordingly.
|
|
505
505
|
"""
|
|
506
|
-
for symbol in self.symbols:
|
|
507
|
-
dtime = self.data.get_latest_bar_datetime(symbol)
|
|
508
506
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
507
|
+
def logmsg(order, type, symbol, dtime):
|
|
508
|
+
return self.logger.info(
|
|
509
|
+
f"{type} ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
|
|
510
|
+
f"PRICE @ {order.price}",
|
|
511
|
+
custom_time=dtime,
|
|
512
|
+
)
|
|
515
513
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
)
|
|
521
|
-
try:
|
|
522
|
-
self._orders[symbol]["BLMT"].remove(order)
|
|
523
|
-
assert order not in self._orders[symbol]["BLMT"]
|
|
524
|
-
logmsg(order, "BUY LIMIT")
|
|
525
|
-
except AssertionError:
|
|
526
|
-
self._orders[symbol]["BLMT"] = [
|
|
527
|
-
o for o in self._orders[symbol]["BLMT"] if o != order
|
|
528
|
-
]
|
|
529
|
-
logmsg(order, "BUY LIMIT")
|
|
530
|
-
for order in self._orders[symbol]["SLMT"].copy():
|
|
531
|
-
if self.data.get_latest_bar_value(symbol, "close") >= order.price:
|
|
532
|
-
self.sell_mkt(
|
|
533
|
-
order.strategy_id, symbol, order.price, order.quantity, dtime
|
|
534
|
-
)
|
|
514
|
+
def process_orders(order_type, condition, execute_fn, log_label, symbol, dtime):
|
|
515
|
+
for order in self._orders[symbol][order_type].copy():
|
|
516
|
+
if condition(order):
|
|
517
|
+
execute_fn(order)
|
|
535
518
|
try:
|
|
536
|
-
self._orders[symbol][
|
|
537
|
-
assert order not in self._orders[symbol][
|
|
538
|
-
logmsg(order, "SELL LIMIT")
|
|
519
|
+
self._orders[symbol][order_type].remove(order)
|
|
520
|
+
assert order not in self._orders[symbol][order_type]
|
|
539
521
|
except AssertionError:
|
|
540
|
-
self._orders[symbol][
|
|
541
|
-
o for o in self._orders[symbol][
|
|
522
|
+
self._orders[symbol][order_type] = [
|
|
523
|
+
o for o in self._orders[symbol][order_type] if o != order
|
|
542
524
|
]
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
self.
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
dtime
|
|
598
|
-
)
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
525
|
+
logmsg(order, log_label, symbol, dtime)
|
|
526
|
+
|
|
527
|
+
for symbol in self.symbols:
|
|
528
|
+
dtime = self.data.get_latest_bar_datetime(symbol)
|
|
529
|
+
latest_close = self.data.get_latest_bar_value(symbol, "close")
|
|
530
|
+
|
|
531
|
+
process_orders(
|
|
532
|
+
"BLMT",
|
|
533
|
+
lambda o: latest_close <= o.price,
|
|
534
|
+
lambda o: self.buy_mkt(
|
|
535
|
+
o.strategy_id, symbol, o.price, o.quantity, dtime
|
|
536
|
+
),
|
|
537
|
+
"BUY LIMIT",
|
|
538
|
+
symbol,
|
|
539
|
+
dtime,
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
process_orders(
|
|
543
|
+
"SLMT",
|
|
544
|
+
lambda o: latest_close >= o.price,
|
|
545
|
+
lambda o: self.sell_mkt(
|
|
546
|
+
o.strategy_id, symbol, o.price, o.quantity, dtime
|
|
547
|
+
),
|
|
548
|
+
"SELL LIMIT",
|
|
549
|
+
symbol,
|
|
550
|
+
dtime,
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
process_orders(
|
|
554
|
+
"BSTP",
|
|
555
|
+
lambda o: latest_close >= o.price,
|
|
556
|
+
lambda o: self.buy_mkt(
|
|
557
|
+
o.strategy_id, symbol, o.price, o.quantity, dtime
|
|
558
|
+
),
|
|
559
|
+
"BUY STOP",
|
|
560
|
+
symbol,
|
|
561
|
+
dtime,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
process_orders(
|
|
565
|
+
"SSTP",
|
|
566
|
+
lambda o: latest_close <= o.price,
|
|
567
|
+
lambda o: self.sell_mkt(
|
|
568
|
+
o.strategy_id, symbol, o.price, o.quantity, dtime
|
|
569
|
+
),
|
|
570
|
+
"SELL STOP",
|
|
571
|
+
symbol,
|
|
572
|
+
dtime,
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
process_orders(
|
|
576
|
+
"BSTPLMT",
|
|
577
|
+
lambda o: latest_close >= o.price,
|
|
578
|
+
lambda o: self.buy_limit(
|
|
579
|
+
o.strategy_id, symbol, o.stoplimit, o.quantity, dtime
|
|
580
|
+
),
|
|
581
|
+
"BUY STOP LIMIT",
|
|
582
|
+
symbol,
|
|
583
|
+
dtime,
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
process_orders(
|
|
587
|
+
"SSTPLMT",
|
|
588
|
+
lambda o: latest_close <= o.price,
|
|
589
|
+
lambda o: self.sell_limit(
|
|
590
|
+
o.strategy_id, symbol, o.stoplimit, o.quantity, dtime
|
|
591
|
+
),
|
|
592
|
+
"SELL STOP LIMIT",
|
|
593
|
+
symbol,
|
|
594
|
+
dtime,
|
|
595
|
+
)
|
|
608
596
|
|
|
609
597
|
@staticmethod
|
|
610
598
|
def calculate_pct_change(current_price, lh_price):
|
bbstrader/core/utils.py
CHANGED
|
@@ -2,8 +2,6 @@ import configparser
|
|
|
2
2
|
import importlib
|
|
3
3
|
import importlib.util
|
|
4
4
|
import os
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from enum import Enum
|
|
7
5
|
from typing import Any, Dict, List
|
|
8
6
|
|
|
9
7
|
__all__ = ["load_module", "load_class"]
|
|
@@ -90,59 +88,3 @@ def dict_from_ini(file_path, sections: str | List[str] = None) -> Dict[str, Any]
|
|
|
90
88
|
except KeyError:
|
|
91
89
|
raise KeyError(f"{section} not found in the {file_path} file")
|
|
92
90
|
return ini_dict
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class TradeAction(Enum):
|
|
96
|
-
"""
|
|
97
|
-
An enumeration class for trade actions.
|
|
98
|
-
"""
|
|
99
|
-
|
|
100
|
-
BUY = "LONG"
|
|
101
|
-
LONG = "LONG"
|
|
102
|
-
SELL = "SHORT"
|
|
103
|
-
EXIT = "EXIT"
|
|
104
|
-
BMKT = "BMKT"
|
|
105
|
-
SMKT = "SMKT"
|
|
106
|
-
BLMT = "BLMT"
|
|
107
|
-
SLMT = "SLMT"
|
|
108
|
-
BSTP = "BSTP"
|
|
109
|
-
SSTP = "SSTP"
|
|
110
|
-
SHORT = "SHORT"
|
|
111
|
-
BSTPLMT = "BSTPLMT"
|
|
112
|
-
SSTPLMT = "SSTPLMT"
|
|
113
|
-
EXIT_LONG = "EXIT_LONG"
|
|
114
|
-
EXIT_SHORT = "EXIT_SHORT"
|
|
115
|
-
EXIT_STOP = "EXIT_STOP"
|
|
116
|
-
EXIT_LIMIT = "EXIT_LIMIT"
|
|
117
|
-
EXIT_LONG_STOP = "EXIT_LONG_STOP"
|
|
118
|
-
EXIT_LONG_LIMIT = "EXIT_LONG_LIMIT"
|
|
119
|
-
EXIT_SHORT_STOP = "EXIT_SHORT_STOP"
|
|
120
|
-
EXIT_SHORT_LIMIT = "EXIT_SHORT_LIMIT"
|
|
121
|
-
EXIT_LONG_STOP_LIMIT = "EXIT_LONG_STOP_LIMIT"
|
|
122
|
-
EXIT_SHORT_STOP_LIMIT = "EXIT_SHORT_STOP_LIMIT"
|
|
123
|
-
EXIT_PROFITABLES = "EXIT_PROFITABLES"
|
|
124
|
-
EXIT_LOSINGS = "EXIT_LOSINGS"
|
|
125
|
-
EXIT_ALL_POSITIONS = "EXIT_ALL_POSITIONS"
|
|
126
|
-
EXIT_ALL_ORDERS = "EXIT_ALL_ORDERS"
|
|
127
|
-
|
|
128
|
-
def __str__(self):
|
|
129
|
-
return self.value
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
@dataclass()
|
|
133
|
-
class TradeSignal:
|
|
134
|
-
"""
|
|
135
|
-
A dataclass for storing trading signal.
|
|
136
|
-
"""
|
|
137
|
-
|
|
138
|
-
id: int
|
|
139
|
-
symbol: str
|
|
140
|
-
action: TradeAction
|
|
141
|
-
price: float = None
|
|
142
|
-
stoplimit: float = None
|
|
143
|
-
|
|
144
|
-
def __repr__(self):
|
|
145
|
-
return (
|
|
146
|
-
f"TradeSignal(id={self.id}, symbol='{self.symbol}', "
|
|
147
|
-
f"action='{self.action.value}', price={self.price}, stoplimit={self.stoplimit})"
|
|
148
|
-
)
|
bbstrader/metatrader/copier.py
CHANGED
bbstrader/metatrader/trade.py
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import time
|
|
3
|
-
from
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from enum import Enum
|
|
4
6
|
from logging import Logger
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple
|
|
7
9
|
|
|
8
10
|
import pandas as pd
|
|
11
|
+
import quantstats as qs
|
|
9
12
|
from loguru import logger as log
|
|
10
13
|
from tabulate import tabulate
|
|
11
14
|
|
|
12
|
-
from bbstrader.btengine.performance import create_sharpe_ratio
|
|
13
15
|
from bbstrader.config import BBSTRADER_DIR, config_logger
|
|
14
16
|
from bbstrader.metatrader.account import INIT_MSG, check_mt5_connection
|
|
15
17
|
from bbstrader.metatrader.risk import RiskManagement
|
|
16
18
|
from bbstrader.metatrader.utils import (
|
|
19
|
+
TradeDeal,
|
|
17
20
|
TradePosition,
|
|
18
21
|
raise_mt5_error,
|
|
19
22
|
trade_retcode_message,
|
|
@@ -47,6 +50,93 @@ global LOGGER
|
|
|
47
50
|
LOGGER = log
|
|
48
51
|
|
|
49
52
|
|
|
53
|
+
class TradeAction(Enum):
|
|
54
|
+
"""
|
|
55
|
+
An enumeration class for trade actions.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
BUY = "LONG"
|
|
59
|
+
SELL = "SHORT"
|
|
60
|
+
LONG = "LONG"
|
|
61
|
+
SHORT = "SHORT"
|
|
62
|
+
BMKT = "BMKT"
|
|
63
|
+
SMKT = "SMKT"
|
|
64
|
+
BLMT = "BLMT"
|
|
65
|
+
SLMT = "SLMT"
|
|
66
|
+
BSTP = "BSTP"
|
|
67
|
+
SSTP = "SSTP"
|
|
68
|
+
BSTPLMT = "BSTPLMT"
|
|
69
|
+
SSTPLMT = "SSTPLMT"
|
|
70
|
+
EXIT = "EXIT"
|
|
71
|
+
EXIT_LONG = "EXIT_LONG"
|
|
72
|
+
EXIT_SHORT = "EXIT_SHORT"
|
|
73
|
+
EXIT_STOP = "EXIT_STOP"
|
|
74
|
+
EXIT_LIMIT = "EXIT_LIMIT"
|
|
75
|
+
EXIT_LONG_STOP = "EXIT_LONG_STOP"
|
|
76
|
+
EXIT_LONG_LIMIT = "EXIT_LONG_LIMIT"
|
|
77
|
+
EXIT_SHORT_STOP = "EXIT_SHORT_STOP"
|
|
78
|
+
EXIT_SHORT_LIMIT = "EXIT_SHORT_LIMIT"
|
|
79
|
+
EXIT_LONG_STOP_LIMIT = "EXIT_LONG_STOP_LIMIT"
|
|
80
|
+
EXIT_SHORT_STOP_LIMIT = "EXIT_SHORT_STOP_LIMIT"
|
|
81
|
+
EXIT_PROFITABLES = "EXIT_PROFITABLES"
|
|
82
|
+
EXIT_LOSINGS = "EXIT_LOSINGS"
|
|
83
|
+
EXIT_ALL_POSITIONS = "EXIT_ALL_POSITIONS"
|
|
84
|
+
EXIT_ALL_ORDERS = "EXIT_ALL_ORDERS"
|
|
85
|
+
|
|
86
|
+
def __str__(self):
|
|
87
|
+
return self.value
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass()
|
|
91
|
+
class TradeSignal:
|
|
92
|
+
"""
|
|
93
|
+
Represents a trading signal generated by a trading system or strategy.
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
id (int):
|
|
97
|
+
A unique identifier for the trade signal or the strategy.
|
|
98
|
+
|
|
99
|
+
symbol (str):
|
|
100
|
+
The trading symbol (e.g., stock ticker, forex pair, crypto asset)
|
|
101
|
+
related to the signal.
|
|
102
|
+
|
|
103
|
+
action (TradeAction):
|
|
104
|
+
The trading action to perform.
|
|
105
|
+
Must be an instance of the `TradeAction` enum (e.g., BUY, SELL).
|
|
106
|
+
|
|
107
|
+
price (float, optional):
|
|
108
|
+
The price at which the trade should be executed.
|
|
109
|
+
|
|
110
|
+
stoplimit (float, optional):
|
|
111
|
+
A stop-limit price for the trade.
|
|
112
|
+
Must not be set without specifying a price.
|
|
113
|
+
|
|
114
|
+
comment (str, optional):
|
|
115
|
+
An optional comment or description related to the trade signal.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
id: int
|
|
119
|
+
symbol: str
|
|
120
|
+
action: TradeAction
|
|
121
|
+
price: float = None
|
|
122
|
+
stoplimit: float = None
|
|
123
|
+
comment: str = None
|
|
124
|
+
|
|
125
|
+
def __post_init__(self):
|
|
126
|
+
if not isinstance(self.action, TradeAction):
|
|
127
|
+
raise TypeError(
|
|
128
|
+
f"action must be of type TradeAction, not {type(self.action)}"
|
|
129
|
+
)
|
|
130
|
+
if self.stoplimit is not None and self.price is None:
|
|
131
|
+
raise ValueError("stoplimit cannot be set without price")
|
|
132
|
+
|
|
133
|
+
def __repr__(self):
|
|
134
|
+
return (
|
|
135
|
+
f"TradeSignal(id={self.id}, symbol='{self.symbol}', action='{self.action.value}', "
|
|
136
|
+
f"price={self.price}, stoplimit={self.stoplimit}), comment='{self.comment}'"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
50
140
|
class Trade(RiskManagement):
|
|
51
141
|
"""
|
|
52
142
|
Extends the `RiskManagement` class to include specific trading operations,
|
|
@@ -437,7 +527,8 @@ class Trade(RiskManagement):
|
|
|
437
527
|
action (str): `BMKT` for Market orders or `BLMT`,
|
|
438
528
|
`BSTP`,`BSTPLMT` for pending orders
|
|
439
529
|
price (float): The price at which to open an order
|
|
440
|
-
stoplimit (float): A price a pending Limit order is set at when the price reaches
|
|
530
|
+
stoplimit (float): A price a pending Limit order is set at when the price reaches
|
|
531
|
+
the 'price' value (this condition is mandatory).
|
|
441
532
|
The pending order is not passed to the trading system until that moment
|
|
442
533
|
id (int): The strategy id or expert Id
|
|
443
534
|
mm (bool): Weither to put stop loss and tp or not
|
|
@@ -527,7 +618,8 @@ class Trade(RiskManagement):
|
|
|
527
618
|
action (str): `SMKT` for Market orders
|
|
528
619
|
or ``SLMT``, ``SSTP``,``SSTPLMT`` for pending orders
|
|
529
620
|
price (float): The price at which to open an order
|
|
530
|
-
stoplimit (float): A price a pending Limit order is set at when the price reaches
|
|
621
|
+
stoplimit (float): A price a pending Limit order is set at when the price reaches
|
|
622
|
+
the 'price' value (this condition is mandatory).
|
|
531
623
|
The pending order is not passed to the trading system until that moment
|
|
532
624
|
id (int): The strategy id or expert Id
|
|
533
625
|
mm (bool): Weither to put stop loss and tp or not
|
|
@@ -594,6 +686,8 @@ class Trade(RiskManagement):
|
|
|
594
686
|
Args:
|
|
595
687
|
comment (str): The comment for the closing position
|
|
596
688
|
"""
|
|
689
|
+
if self.copy_mode:
|
|
690
|
+
return True
|
|
597
691
|
if self.days_end():
|
|
598
692
|
return False
|
|
599
693
|
elif not self.trading_time():
|
|
@@ -603,6 +697,9 @@ class Trade(RiskManagement):
|
|
|
603
697
|
LOGGER.error(f"Account Risk not allowed, SYMBOL={self.symbol}")
|
|
604
698
|
self._check(comment)
|
|
605
699
|
return False
|
|
700
|
+
elif self.is_max_trades_reached():
|
|
701
|
+
LOGGER.error(f"Maximum trades reached for Today, SYMBOL={self.symbol}")
|
|
702
|
+
return False
|
|
606
703
|
elif self.profit_target():
|
|
607
704
|
self._check(f"Profit target Reached !!! SYMBOL={self.symbol}")
|
|
608
705
|
return True
|
|
@@ -1582,6 +1679,50 @@ class Trade(RiskManagement):
|
|
|
1582
1679
|
comment=comment,
|
|
1583
1680
|
)
|
|
1584
1681
|
|
|
1682
|
+
def get_today_deals(self, group=None) -> List[TradeDeal]:
|
|
1683
|
+
"""
|
|
1684
|
+
Get all today deals for a specific symbol or group of symbols
|
|
1685
|
+
|
|
1686
|
+
Args:
|
|
1687
|
+
group (str): Symbol or group or symbol
|
|
1688
|
+
Returns:
|
|
1689
|
+
List[TradeDeal]: List of today deals
|
|
1690
|
+
"""
|
|
1691
|
+
date_from = datetime.now() - timedelta(days=2)
|
|
1692
|
+
history = self.get_trades_history(date_from=date_from, group=group, to_df=False)
|
|
1693
|
+
positions_ids = set(
|
|
1694
|
+
[
|
|
1695
|
+
deal.position_id
|
|
1696
|
+
for deal in history
|
|
1697
|
+
if history is not None and deal.magic == self.expert_id
|
|
1698
|
+
]
|
|
1699
|
+
)
|
|
1700
|
+
today_deals = []
|
|
1701
|
+
for position in positions_ids:
|
|
1702
|
+
deal = self.get_trades_history(
|
|
1703
|
+
date_from=date_from, position=position, to_df=False
|
|
1704
|
+
)
|
|
1705
|
+
if deal is not None and len(deal) == 2:
|
|
1706
|
+
deal_time = datetime.fromtimestamp(deal[1].time)
|
|
1707
|
+
if deal_time.date() == datetime.now().date():
|
|
1708
|
+
today_deals.append(deal[1])
|
|
1709
|
+
return today_deals
|
|
1710
|
+
|
|
1711
|
+
def is_max_trades_reached(self) -> bool:
|
|
1712
|
+
"""
|
|
1713
|
+
Check if the maximum number of trades for the day has been reached.
|
|
1714
|
+
|
|
1715
|
+
:return: bool
|
|
1716
|
+
"""
|
|
1717
|
+
negative_deals = 0
|
|
1718
|
+
today_deals = self.get_today_deals(group=self.symbol)
|
|
1719
|
+
for deal in today_deals:
|
|
1720
|
+
if deal.profit < 0:
|
|
1721
|
+
negative_deals += 1
|
|
1722
|
+
if negative_deals >= self.max_trades:
|
|
1723
|
+
return True
|
|
1724
|
+
return False
|
|
1725
|
+
|
|
1585
1726
|
def get_stats(self) -> Tuple[Dict[str, Any]]:
|
|
1586
1727
|
"""
|
|
1587
1728
|
get some stats about the trading day and trading history
|
|
@@ -1594,11 +1735,14 @@ class Trade(RiskManagement):
|
|
|
1594
1735
|
loss_trades = 0
|
|
1595
1736
|
win_trades = 0
|
|
1596
1737
|
balance = self.get_account_info().balance
|
|
1597
|
-
|
|
1738
|
+
today_deals = self.get_today_deals(group=self.symbol)
|
|
1739
|
+
deals = len(today_deals)
|
|
1598
1740
|
if deals != 0:
|
|
1599
|
-
for position in
|
|
1741
|
+
for position in today_deals:
|
|
1600
1742
|
time.sleep(0.1)
|
|
1601
|
-
history = self.get_trades_history(
|
|
1743
|
+
history = self.get_trades_history(
|
|
1744
|
+
position=position.position_id, to_df=False
|
|
1745
|
+
)
|
|
1602
1746
|
if history is not None and len(history) == 2:
|
|
1603
1747
|
result = history[1].profit
|
|
1604
1748
|
comm = history[0].commission
|
|
@@ -1641,13 +1785,12 @@ class Trade(RiskManagement):
|
|
|
1641
1785
|
_fees = df2["fee"].sum()
|
|
1642
1786
|
_swap = df2["swap"].sum()
|
|
1643
1787
|
total_profit = commisions + _fees + _swap + profit
|
|
1644
|
-
|
|
1645
|
-
balance = account_info.balance
|
|
1788
|
+
balance = self.get_account_info().balance
|
|
1646
1789
|
initial_balance = balance - total_profit
|
|
1647
1790
|
profittable = "Yes" if balance > initial_balance else "No"
|
|
1648
1791
|
stats2 = {"total_profit": total_profit, "profitability": profittable}
|
|
1649
1792
|
else:
|
|
1650
|
-
stats2 = {"total_profit": 0, "profitability":
|
|
1793
|
+
stats2 = {"total_profit": 0, "profitability": "No"}
|
|
1651
1794
|
return (stats1, stats2)
|
|
1652
1795
|
|
|
1653
1796
|
def sharpe(self):
|
|
@@ -1658,6 +1801,9 @@ class Trade(RiskManagement):
|
|
|
1658
1801
|
those compared to a benchmark.
|
|
1659
1802
|
"""
|
|
1660
1803
|
# Get total history
|
|
1804
|
+
import warnings
|
|
1805
|
+
|
|
1806
|
+
warnings.filterwarnings("ignore")
|
|
1661
1807
|
df2 = self.get_trades_history()
|
|
1662
1808
|
if df2 is None:
|
|
1663
1809
|
return 0.0
|
|
@@ -1665,7 +1811,7 @@ class Trade(RiskManagement):
|
|
|
1665
1811
|
profit = df[["profit", "commission", "fee", "swap"]].sum(axis=1)
|
|
1666
1812
|
returns = profit.pct_change(fill_method=None)
|
|
1667
1813
|
periods = self.max_trade() * 252
|
|
1668
|
-
sharpe =
|
|
1814
|
+
sharpe = qs.stats.sharpe(returns, periods=periods)
|
|
1669
1815
|
|
|
1670
1816
|
return round(sharpe, 3)
|
|
1671
1817
|
|
bbstrader/models/risk.py
CHANGED
|
@@ -68,7 +68,7 @@ class RiskModel(metaclass=ABCMeta):
|
|
|
68
68
|
such as historical returns or volatility, used to
|
|
69
69
|
assess market conditions.
|
|
70
70
|
"""
|
|
71
|
-
|
|
71
|
+
pass
|
|
72
72
|
|
|
73
73
|
@abstractmethod
|
|
74
74
|
def which_quantity_allowed(self):
|
|
@@ -76,7 +76,7 @@ class RiskModel(metaclass=ABCMeta):
|
|
|
76
76
|
Defines the strategy for asset allocation within
|
|
77
77
|
the portfolio to optimize risk-reward ratio.
|
|
78
78
|
"""
|
|
79
|
-
|
|
79
|
+
pass
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
class HMMRiskManager(RiskModel):
|
bbstrader/trading/execution.py
CHANGED
|
@@ -8,9 +8,8 @@ from loguru import logger as log
|
|
|
8
8
|
|
|
9
9
|
from bbstrader.btengine.strategy import MT5Strategy, Strategy
|
|
10
10
|
from bbstrader.config import BBSTRADER_DIR
|
|
11
|
-
from bbstrader.core.utils import TradeAction
|
|
12
11
|
from bbstrader.metatrader.account import Account, check_mt5_connection
|
|
13
|
-
from bbstrader.metatrader.trade import Trade
|
|
12
|
+
from bbstrader.metatrader.trade import Trade, TradeAction
|
|
14
13
|
from bbstrader.trading.utils import send_message
|
|
15
14
|
|
|
16
15
|
try:
|
|
@@ -114,6 +113,7 @@ def _mt5_execution(
|
|
|
114
113
|
comment,
|
|
115
114
|
**kwargs,
|
|
116
115
|
):
|
|
116
|
+
global logger
|
|
117
117
|
logger = kwargs.get("logger", log)
|
|
118
118
|
|
|
119
119
|
def _print_exc(dm, msg):
|
|
@@ -168,6 +168,7 @@ def _mt5_execution(
|
|
|
168
168
|
if symbol not in weights:
|
|
169
169
|
continue
|
|
170
170
|
trade = trades_instances[symbol]
|
|
171
|
+
assert daily_risk is not None
|
|
171
172
|
dailydd = round(weights[symbol] * daily_risk, 5)
|
|
172
173
|
trade.dailydd = dailydd
|
|
173
174
|
|
|
@@ -212,7 +213,9 @@ def _mt5_execution(
|
|
|
212
213
|
f"Running {STRATEGY} Strategy in {time_frame} Interval ..., ACCOUNT={ACCOUNT}"
|
|
213
214
|
)
|
|
214
215
|
|
|
215
|
-
def run_trade_algorithm(
|
|
216
|
+
def run_trade_algorithm(
|
|
217
|
+
signal, symbol, id, trade: Trade, price, stoplimit, comment
|
|
218
|
+
):
|
|
216
219
|
signal = "BMKT" if signal == "LONG" or signal == "BUY" else signal
|
|
217
220
|
signal = "SMKT" if signal == "SHORT" or signal == "SELL" else signal
|
|
218
221
|
info = f"SIGNAL = {signal}, SYMBOL={symbol}, STRATEGY={STRATEGY}, TIMEFRAME={time_frame} , ACCOUNT={ACCOUNT}"
|
|
@@ -242,9 +245,13 @@ def _mt5_execution(
|
|
|
242
245
|
if notify:
|
|
243
246
|
_send_notification(sigmsg, symbol)
|
|
244
247
|
if position_type in POSITIONS_TYPES:
|
|
245
|
-
trade.close_positions(
|
|
248
|
+
trade.close_positions(
|
|
249
|
+
position_type=order_type, id=id, comment=comment
|
|
250
|
+
)
|
|
246
251
|
else:
|
|
247
|
-
trade.close_orders(
|
|
252
|
+
trade.close_orders(
|
|
253
|
+
order_type=order_type, id=id, comment=comment
|
|
254
|
+
)
|
|
248
255
|
elif signal in BUYS and not long_market[symbol]:
|
|
249
256
|
if use_trade_time:
|
|
250
257
|
if time_intervals % trade_time == 0 or buys[symbol] is None:
|
|
@@ -412,6 +419,7 @@ def _mt5_execution(
|
|
|
412
419
|
trade,
|
|
413
420
|
signal.price,
|
|
414
421
|
signal.stoplimit,
|
|
422
|
+
signal.comment or comment,
|
|
415
423
|
)
|
|
416
424
|
else:
|
|
417
425
|
if len(symbols) >= 10:
|
|
@@ -822,7 +830,7 @@ def mt5_engine(account_id: str, **kwargs):
|
|
|
822
830
|
|
|
823
831
|
def RunMt5Engines(accounts: Dict[str, Dict], start_delay: float = 1.0):
|
|
824
832
|
"""Runs multiple MT5 execution engines in parallel using multiprocessing.
|
|
825
|
-
|
|
833
|
+
|
|
826
834
|
Args:
|
|
827
835
|
accounts: Dictionary of accounts to run the execution engines on.
|
|
828
836
|
Keys are the account names or IDs and values are the parameters for the execution engine.
|
bbstrader/trading/strategies.py
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Strategies module for trading strategies backtesting and execution.
|
|
3
|
+
|
|
4
|
+
DISCLAIMER:
|
|
5
|
+
This module is for educational purposes only and should not be
|
|
6
|
+
considered as financial advice. Always consult with a qualified financial advisor before making any investment decisions.
|
|
7
|
+
The authors and contributors of this module are not responsible for any financial losses or damages incurred as a result of using
|
|
8
|
+
this module or the information contained herein.
|
|
3
9
|
"""
|
|
4
10
|
|
|
5
11
|
from datetime import datetime
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bbstrader
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.97
|
|
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/
|
|
@@ -63,6 +63,7 @@ Requires-Dist: tweepy
|
|
|
63
63
|
Requires-Dist: beautifulsoup4
|
|
64
64
|
Requires-Dist: dash
|
|
65
65
|
Requires-Dist: nltk
|
|
66
|
+
Requires-Dist: spacy
|
|
66
67
|
Requires-Dist: textblob
|
|
67
68
|
Requires-Dist: vaderSentiment
|
|
68
69
|
Provides-Extra: mt5
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
bbstrader/__ini__.py,sha256=v6zyJHj5FMRL-_P7AwnTGbCF-riMqhqlTvDgfulj7go,621
|
|
2
|
-
bbstrader/__main__.py,sha256=
|
|
2
|
+
bbstrader/__main__.py,sha256=EtfD-TrAphboYxwfe7-WeD-1d4bJceeBputFrV-W-qQ,1525
|
|
3
3
|
bbstrader/compat.py,sha256=djbHMvTvy0HYm1zyZ6Ttp_LMwP2PqTSVw1r7pqbz7So,487
|
|
4
4
|
bbstrader/config.py,sha256=c2nCUw-bYWf5kkyFls5Nqld8HdMczexSilTni7rYUBw,3973
|
|
5
5
|
bbstrader/tseries.py,sha256=H4D_A966HdN8YjBfuCcF8QBQdhjOrTcidR98wP2KN_I,68339
|
|
6
6
|
bbstrader/btengine/__init__.py,sha256=y1btjaEfhWsH8vuE7mBRpP9Tu-Azt9REhuVYsPCAfBU,2955
|
|
7
7
|
bbstrader/btengine/backtest.py,sha256=ZzGhoN-_g0cF-OCyk173imze2OXEhykHTUiJ9MowDO8,14582
|
|
8
|
-
bbstrader/btengine/data.py,sha256=
|
|
8
|
+
bbstrader/btengine/data.py,sha256=Tuc6M8itbGpPjsfRpZBB8v0FJpPt7-hUkP6I5meP0Sg,26927
|
|
9
9
|
bbstrader/btengine/event.py,sha256=38mhZH9d53C4x7bZER2B0O6M18txzS4u7zveKyxeP5Y,8603
|
|
10
|
-
bbstrader/btengine/execution.py,sha256=
|
|
10
|
+
bbstrader/btengine/execution.py,sha256=mXY0tyFv6G1XF48R5Kx2-pDnwu1mUyOc0ZeeysQG62A,10587
|
|
11
11
|
bbstrader/btengine/performance.py,sha256=1ecWrTzHBQbk4ORvbTEKxwCzlL1brcXOEUwgbnjAwx4,12470
|
|
12
12
|
bbstrader/btengine/portfolio.py,sha256=mh2_zNJDmKzb0lo55PXhbXYxXMmXRA4YLkgzwxRMuZE,16110
|
|
13
13
|
bbstrader/btengine/scripts.py,sha256=8o66dq4Ex4DsH4s8xvJqUOFjLzZJSnbBvvNBzohtzoE,4837
|
|
14
|
-
bbstrader/btengine/strategy.py,sha256=
|
|
14
|
+
bbstrader/btengine/strategy.py,sha256=PFIb9gUWahaXpzCFL-RchpBumlEJsz5Elbz_A2qZ8Jc,31285
|
|
15
15
|
bbstrader/core/__init__.py,sha256=GIFzFSStPfE0XM2j7mDeZZQeMTh_AwPsDOQXwMVJLgw,97
|
|
16
16
|
bbstrader/core/data.py,sha256=VPuynoT0uFYduh7la8gZSnEv_Gq8Xu2vJZJ7TfQMll8,18797
|
|
17
|
-
bbstrader/core/utils.py,sha256=
|
|
17
|
+
bbstrader/core/utils.py,sha256=WjuabzBjhY65ku2KL-f7CMalE2x-wrX-6mCA_qhhFPE,2728
|
|
18
18
|
bbstrader/ibkr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
bbstrader/ibkr/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
bbstrader/metatrader/__init__.py,sha256=A5Ye9tpc2sp9Xk5qjKw-EfYsoRcZtAt8nqvC3tCtZs8,333
|
|
21
21
|
bbstrader/metatrader/account.py,sha256=qAFqCPmoUjiqjY5M9sjbSvh-STjQUGdFCHOEcB7uoDI,56493
|
|
22
|
-
bbstrader/metatrader/copier.py,sha256
|
|
22
|
+
bbstrader/metatrader/copier.py,sha256=-cFroX4M1q9rlupU4xLP0N6lR8ZbP_izUOQ2sBe_lFc,31797
|
|
23
23
|
bbstrader/metatrader/rates.py,sha256=0bKyjGnafZ19DwC0-IxXUBJzOdAo-cNmhabwhKLxcos,20585
|
|
24
24
|
bbstrader/metatrader/risk.py,sha256=pwG4q1_uPGgPlokDGVNtd04O6p28yIbgT-evvHuo-Qc,27029
|
|
25
25
|
bbstrader/metatrader/scripts.py,sha256=Yjp7Un-wDTInptHS_rPFpXNKWbVM871VEkaHxsO2MPQ,2115
|
|
26
|
-
bbstrader/metatrader/trade.py,sha256=
|
|
26
|
+
bbstrader/metatrader/trade.py,sha256=Q1cArcpMnovRTED0kwMybcpwPB28rQKXLcJV4jldF0w,81202
|
|
27
27
|
bbstrader/metatrader/utils.py,sha256=lYIvAeiL_ceACmkVNo4-QRopias08KyBUI7THfdj3c0,17103
|
|
28
28
|
bbstrader/models/__init__.py,sha256=s2mJrtKePXQaw_PvcrtPCD2mPCdVXP4Myzg0MlLVipo,547
|
|
29
29
|
bbstrader/models/factors.py,sha256=5kSAOS1MHvTZ-Ti03TtjOxl_EvC-V_9e389xeR_82ak,13020
|
|
@@ -31,15 +31,15 @@ bbstrader/models/ml.py,sha256=tCr7YyODl0CDoOUpYqJ1q12ls86Sc-_Fu3b2Y0Z7TJ8,47551
|
|
|
31
31
|
bbstrader/models/nlp.py,sha256=P7SYaTIqEBldjwYfS6IrO66Y6-ioDXUrCSf3bZxQrDE,28073
|
|
32
32
|
bbstrader/models/optimization.py,sha256=vnks6uxFZdqXgxaZJNxq8z0IH45KZ8yaXo38JhIVKGc,6399
|
|
33
33
|
bbstrader/models/portfolio.py,sha256=r-47Zrn2r7iKCHm5YVtwkbBJXAZGM3QYy-rXCWY9-Bg,8079
|
|
34
|
-
bbstrader/models/risk.py,sha256=
|
|
34
|
+
bbstrader/models/risk.py,sha256=JQOXPshMOru6Eb0AMU6oKCNzg6mlGfL6_tN90lWcVBE,14878
|
|
35
35
|
bbstrader/trading/__init__.py,sha256=ycLyuuxN5SujqtzR9X0Q74UQfK93q2va-GGAXdr-KS8,457
|
|
36
|
-
bbstrader/trading/execution.py,sha256=
|
|
36
|
+
bbstrader/trading/execution.py,sha256=4-XyRll1x8gfF68cTZXjtRF79csDx_oKNI0F71cv1Bw,35028
|
|
37
37
|
bbstrader/trading/scripts.py,sha256=wR5TUrHn-Cd2kzfURXn14VTCEZ-QA8ydwYHayMPK0oI,5720
|
|
38
|
-
bbstrader/trading/strategies.py,sha256=
|
|
38
|
+
bbstrader/trading/strategies.py,sha256=D_M91UwRiFwZKvKFKmz427TPd-dNyWWbhehaP8RI17c,36539
|
|
39
39
|
bbstrader/trading/utils.py,sha256=57dKF9dcRu04oU2VRqydRrzW39dCW2wlDWhVt-sZdRw,1857
|
|
40
|
-
bbstrader-0.2.
|
|
41
|
-
bbstrader-0.2.
|
|
42
|
-
bbstrader-0.2.
|
|
43
|
-
bbstrader-0.2.
|
|
44
|
-
bbstrader-0.2.
|
|
45
|
-
bbstrader-0.2.
|
|
40
|
+
bbstrader-0.2.97.dist-info/licenses/LICENSE,sha256=ZwC_RqqGmOPBUiMDKqLyJZ5HBeHq53LpL7TMRzrJY8c,1094
|
|
41
|
+
bbstrader-0.2.97.dist-info/METADATA,sha256=mBnvqbUXVjE-dl5A5tkdYRuGd_fh2oKU55K-Ryfm9po,11590
|
|
42
|
+
bbstrader-0.2.97.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
43
|
+
bbstrader-0.2.97.dist-info/entry_points.txt,sha256=0yDCbhbgHswOzJnY5wRSM_FjjyMHGvY7lJpSSVh0xtI,54
|
|
44
|
+
bbstrader-0.2.97.dist-info/top_level.txt,sha256=Wwj322jZmxGZ6gD_TdaPiPLjED5ReObm5omerwlmZIg,10
|
|
45
|
+
bbstrader-0.2.97.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|