bbstrader 0.2.96__py3-none-any.whl → 0.2.98__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 +2 -1
- bbstrader/btengine/data.py +6 -6
- bbstrader/btengine/execution.py +1 -1
- bbstrader/btengine/strategy.py +90 -101
- bbstrader/core/utils.py +0 -58
- bbstrader/metatrader/account.py +30 -6
- bbstrader/metatrader/copier.py +7 -11
- bbstrader/metatrader/trade.py +186 -68
- bbstrader/metatrader/utils.py +16 -0
- bbstrader/models/risk.py +2 -2
- bbstrader/trading/execution.py +691 -550
- bbstrader/trading/scripts.py +10 -11
- bbstrader/trading/strategies.py +6 -0
- {bbstrader-0.2.96.dist-info → bbstrader-0.2.98.dist-info}/METADATA +12 -15
- {bbstrader-0.2.96.dist-info → bbstrader-0.2.98.dist-info}/RECORD +19 -19
- {bbstrader-0.2.96.dist-info → bbstrader-0.2.98.dist-info}/WHEEL +0 -0
- {bbstrader-0.2.96.dist-info → bbstrader-0.2.98.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.2.96.dist-info → bbstrader-0.2.98.dist-info}/licenses/LICENSE +0 -0
- {bbstrader-0.2.96.dist-info → bbstrader-0.2.98.dist-info}/top_level.txt +0 -0
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,107 @@ 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
|
+
|
|
140
|
+
Buys = Literal["BMKT", "BLMT", "BSTP", "BSTPLMT"]
|
|
141
|
+
Sells = Literal["SMKT", "SLMT", "SSTP", "SSTPLMT"]
|
|
142
|
+
Positions = Literal["all", "buy", "sell", "profitable", "losing"]
|
|
143
|
+
Orders = Literal[
|
|
144
|
+
"all",
|
|
145
|
+
"buy_stops",
|
|
146
|
+
"sell_stops",
|
|
147
|
+
"buy_limits",
|
|
148
|
+
"sell_limits",
|
|
149
|
+
"buy_stop_limits",
|
|
150
|
+
"sell_stop_limits",
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
|
|
50
154
|
class Trade(RiskManagement):
|
|
51
155
|
"""
|
|
52
156
|
Extends the `RiskManagement` class to include specific trading operations,
|
|
@@ -149,15 +253,12 @@ class Trade(RiskManagement):
|
|
|
149
253
|
See the ``bbstrader.metatrader.risk.RiskManagement`` class for more details on these parameters.
|
|
150
254
|
See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
|
|
151
255
|
"""
|
|
152
|
-
# Call the parent class constructor first
|
|
153
256
|
super().__init__(
|
|
154
257
|
symbol=symbol,
|
|
155
258
|
start_time=start_time,
|
|
156
259
|
finishing_time=finishing_time,
|
|
157
|
-
**kwargs,
|
|
260
|
+
**kwargs,
|
|
158
261
|
)
|
|
159
|
-
|
|
160
|
-
# Initialize Trade-specific attributes
|
|
161
262
|
self.symbol = symbol
|
|
162
263
|
self.expert_name = expert_name
|
|
163
264
|
self.expert_id = expert_id
|
|
@@ -171,12 +272,6 @@ class Trade(RiskManagement):
|
|
|
171
272
|
self.tf = kwargs.get("time_frame", "D1")
|
|
172
273
|
self.kwargs = kwargs
|
|
173
274
|
|
|
174
|
-
self.start_time_hour, self.start_time_minutes = self.start.split(":")
|
|
175
|
-
self.finishing_time_hour, self.finishing_time_minutes = self.finishing.split(
|
|
176
|
-
":"
|
|
177
|
-
)
|
|
178
|
-
self.ending_time_hour, self.ending_time_minutes = self.end.split(":")
|
|
179
|
-
|
|
180
275
|
self.buy_positions = []
|
|
181
276
|
self.sell_positions = []
|
|
182
277
|
self.opened_positions = []
|
|
@@ -390,18 +485,14 @@ class Trade(RiskManagement):
|
|
|
390
485
|
session_data, headers=["Statistics", "Values"], tablefmt="outline"
|
|
391
486
|
)
|
|
392
487
|
|
|
393
|
-
# Print the formatted statistics
|
|
394
488
|
if self.verbose:
|
|
395
489
|
print("\n[======= Trading Session Statistics =======]")
|
|
396
490
|
print(session_table)
|
|
397
491
|
|
|
398
|
-
|
|
399
|
-
if save:
|
|
492
|
+
if save and stats["deals"] > 0:
|
|
400
493
|
today_date = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
401
|
-
# Create a dictionary with the statistics
|
|
402
494
|
statistics_dict = {item[0]: item[1] for item in session_data}
|
|
403
495
|
stats_df = pd.DataFrame(statistics_dict, index=[0])
|
|
404
|
-
# Create the directory if it doesn't exist
|
|
405
496
|
dir = dir or ".sessions"
|
|
406
497
|
os.makedirs(dir, exist_ok=True)
|
|
407
498
|
if "." in self.symbol:
|
|
@@ -414,8 +505,6 @@ class Trade(RiskManagement):
|
|
|
414
505
|
stats_df.to_csv(filepath, index=False)
|
|
415
506
|
LOGGER.info(f"Session statistics saved to {filepath}")
|
|
416
507
|
|
|
417
|
-
Buys = Literal["BMKT", "BLMT", "BSTP", "BSTPLMT"]
|
|
418
|
-
|
|
419
508
|
def open_buy_position(
|
|
420
509
|
self,
|
|
421
510
|
action: Buys = "BMKT",
|
|
@@ -437,7 +526,8 @@ class Trade(RiskManagement):
|
|
|
437
526
|
action (str): `BMKT` for Market orders or `BLMT`,
|
|
438
527
|
`BSTP`,`BSTPLMT` for pending orders
|
|
439
528
|
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
|
|
529
|
+
stoplimit (float): A price a pending Limit order is set at when the price reaches
|
|
530
|
+
the 'price' value (this condition is mandatory).
|
|
441
531
|
The pending order is not passed to the trading system until that moment
|
|
442
532
|
id (int): The strategy id or expert Id
|
|
443
533
|
mm (bool): Weither to put stop loss and tp or not
|
|
@@ -504,8 +594,6 @@ class Trade(RiskManagement):
|
|
|
504
594
|
}
|
|
505
595
|
return type
|
|
506
596
|
|
|
507
|
-
Sells = Literal["SMKT", "SLMT", "SSTP", "SSTPLMT"]
|
|
508
|
-
|
|
509
597
|
def open_sell_position(
|
|
510
598
|
self,
|
|
511
599
|
action: Sells = "SMKT",
|
|
@@ -527,7 +615,8 @@ class Trade(RiskManagement):
|
|
|
527
615
|
action (str): `SMKT` for Market orders
|
|
528
616
|
or ``SLMT``, ``SSTP``,``SSTPLMT`` for pending orders
|
|
529
617
|
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
|
|
618
|
+
stoplimit (float): A price a pending Limit order is set at when the price reaches
|
|
619
|
+
the 'price' value (this condition is mandatory).
|
|
531
620
|
The pending order is not passed to the trading system until that moment
|
|
532
621
|
id (int): The strategy id or expert Id
|
|
533
622
|
mm (bool): Weither to put stop loss and tp or not
|
|
@@ -594,6 +683,8 @@ class Trade(RiskManagement):
|
|
|
594
683
|
Args:
|
|
595
684
|
comment (str): The comment for the closing position
|
|
596
685
|
"""
|
|
686
|
+
if self.copy_mode:
|
|
687
|
+
return True
|
|
597
688
|
if self.days_end():
|
|
598
689
|
return False
|
|
599
690
|
elif not self.trading_time():
|
|
@@ -603,6 +694,9 @@ class Trade(RiskManagement):
|
|
|
603
694
|
LOGGER.error(f"Account Risk not allowed, SYMBOL={self.symbol}")
|
|
604
695
|
self._check(comment)
|
|
605
696
|
return False
|
|
697
|
+
elif self.is_max_trades_reached():
|
|
698
|
+
LOGGER.error(f"Maximum trades reached for Today, SYMBOL={self.symbol}")
|
|
699
|
+
return False
|
|
606
700
|
elif self.profit_target():
|
|
607
701
|
self._check(f"Profit target Reached !!! SYMBOL={self.symbol}")
|
|
608
702
|
return True
|
|
@@ -1273,13 +1367,16 @@ class Trade(RiskManagement):
|
|
|
1273
1367
|
profit = 0.0
|
|
1274
1368
|
balance = self.get_account_info().balance
|
|
1275
1369
|
target = round((balance * self.target) / 100, 2)
|
|
1276
|
-
|
|
1277
|
-
|
|
1370
|
+
opened_positions = self.get_today_deals(group=self.symbol)
|
|
1371
|
+
if len(opened_positions) != 0:
|
|
1372
|
+
for position in opened_positions:
|
|
1278
1373
|
time.sleep(0.1)
|
|
1279
1374
|
# This return two TradeDeal Object,
|
|
1280
1375
|
# The first one is the opening order
|
|
1281
1376
|
# The second is the closing order
|
|
1282
|
-
history = self.get_trades_history(
|
|
1377
|
+
history = self.get_trades_history(
|
|
1378
|
+
position=position.position_id, to_df=False
|
|
1379
|
+
)
|
|
1283
1380
|
if history is not None and len(history) == 2:
|
|
1284
1381
|
profit += history[1].profit
|
|
1285
1382
|
commission += history[0].commission
|
|
@@ -1501,16 +1598,6 @@ class Trade(RiskManagement):
|
|
|
1501
1598
|
f"No {order_type.upper()} {tikets_type.upper()} to close, SYMBOL={self.symbol}."
|
|
1502
1599
|
)
|
|
1503
1600
|
|
|
1504
|
-
Orders = Literal[
|
|
1505
|
-
"all",
|
|
1506
|
-
"buy_stops",
|
|
1507
|
-
"sell_stops",
|
|
1508
|
-
"buy_limits",
|
|
1509
|
-
"sell_limits",
|
|
1510
|
-
"buy_stop_limits",
|
|
1511
|
-
"sell_stop_limits",
|
|
1512
|
-
]
|
|
1513
|
-
|
|
1514
1601
|
def close_orders(
|
|
1515
1602
|
self,
|
|
1516
1603
|
order_type: Orders,
|
|
@@ -1545,8 +1632,6 @@ class Trade(RiskManagement):
|
|
|
1545
1632
|
orders, "orders", self.close_order, order_type, id=id, comment=comment
|
|
1546
1633
|
)
|
|
1547
1634
|
|
|
1548
|
-
Positions = Literal["all", "buy", "sell", "profitable", "losing"]
|
|
1549
|
-
|
|
1550
1635
|
def close_positions(
|
|
1551
1636
|
self,
|
|
1552
1637
|
position_type: Positions,
|
|
@@ -1582,6 +1667,49 @@ class Trade(RiskManagement):
|
|
|
1582
1667
|
comment=comment,
|
|
1583
1668
|
)
|
|
1584
1669
|
|
|
1670
|
+
def get_today_deals(self, group=None) -> List[TradeDeal]:
|
|
1671
|
+
"""
|
|
1672
|
+
Get all today deals for a specific symbol or group of symbols
|
|
1673
|
+
|
|
1674
|
+
Args:
|
|
1675
|
+
group (str): Symbol or group or symbol
|
|
1676
|
+
Returns:
|
|
1677
|
+
List[TradeDeal]: List of today deals
|
|
1678
|
+
"""
|
|
1679
|
+
date_from = datetime.now() - timedelta(days=2)
|
|
1680
|
+
history = (
|
|
1681
|
+
self.get_trades_history(date_from=date_from, group=group, to_df=False) or []
|
|
1682
|
+
)
|
|
1683
|
+
positions_ids = set(
|
|
1684
|
+
[deal.position_id for deal in history if deal.magic == self.expert_id]
|
|
1685
|
+
)
|
|
1686
|
+
today_deals = []
|
|
1687
|
+
for position in positions_ids:
|
|
1688
|
+
deal = self.get_trades_history(
|
|
1689
|
+
date_from=date_from, position=position, to_df=False
|
|
1690
|
+
)
|
|
1691
|
+
if deal is not None and len(deal) == 2:
|
|
1692
|
+
deal_time = datetime.fromtimestamp(deal[1].time)
|
|
1693
|
+
if deal_time.date() == datetime.now().date():
|
|
1694
|
+
today_deals.append(deal[1])
|
|
1695
|
+
return today_deals
|
|
1696
|
+
|
|
1697
|
+
def is_max_trades_reached(self) -> bool:
|
|
1698
|
+
"""
|
|
1699
|
+
Check if the maximum number of trades for the day has been reached.
|
|
1700
|
+
|
|
1701
|
+
:return: bool
|
|
1702
|
+
"""
|
|
1703
|
+
negative_deals = 0
|
|
1704
|
+
max_trades = self.max_trade()
|
|
1705
|
+
today_deals = self.get_today_deals(group=self.symbol)
|
|
1706
|
+
for deal in today_deals:
|
|
1707
|
+
if deal.profit < 0:
|
|
1708
|
+
negative_deals += 1
|
|
1709
|
+
if negative_deals >= max_trades:
|
|
1710
|
+
return True
|
|
1711
|
+
return False
|
|
1712
|
+
|
|
1585
1713
|
def get_stats(self) -> Tuple[Dict[str, Any]]:
|
|
1586
1714
|
"""
|
|
1587
1715
|
get some stats about the trading day and trading history
|
|
@@ -1594,11 +1722,14 @@ class Trade(RiskManagement):
|
|
|
1594
1722
|
loss_trades = 0
|
|
1595
1723
|
win_trades = 0
|
|
1596
1724
|
balance = self.get_account_info().balance
|
|
1597
|
-
|
|
1725
|
+
today_deals = self.get_today_deals(group=self.symbol)
|
|
1726
|
+
deals = len(today_deals)
|
|
1598
1727
|
if deals != 0:
|
|
1599
|
-
for position in
|
|
1728
|
+
for position in today_deals:
|
|
1600
1729
|
time.sleep(0.1)
|
|
1601
|
-
history = self.get_trades_history(
|
|
1730
|
+
history = self.get_trades_history(
|
|
1731
|
+
position=position.position_id, to_df=False
|
|
1732
|
+
)
|
|
1602
1733
|
if history is not None and len(history) == 2:
|
|
1603
1734
|
result = history[1].profit
|
|
1604
1735
|
comm = history[0].commission
|
|
@@ -1641,13 +1772,12 @@ class Trade(RiskManagement):
|
|
|
1641
1772
|
_fees = df2["fee"].sum()
|
|
1642
1773
|
_swap = df2["swap"].sum()
|
|
1643
1774
|
total_profit = commisions + _fees + _swap + profit
|
|
1644
|
-
|
|
1645
|
-
balance = account_info.balance
|
|
1775
|
+
balance = self.get_account_info().balance
|
|
1646
1776
|
initial_balance = balance - total_profit
|
|
1647
1777
|
profittable = "Yes" if balance > initial_balance else "No"
|
|
1648
1778
|
stats2 = {"total_profit": total_profit, "profitability": profittable}
|
|
1649
1779
|
else:
|
|
1650
|
-
stats2 = {"total_profit": 0, "profitability":
|
|
1780
|
+
stats2 = {"total_profit": 0, "profitability": "No"}
|
|
1651
1781
|
return (stats1, stats2)
|
|
1652
1782
|
|
|
1653
1783
|
def sharpe(self):
|
|
@@ -1657,7 +1787,9 @@ class Trade(RiskManagement):
|
|
|
1657
1787
|
The function assumes that the returns are the excess of
|
|
1658
1788
|
those compared to a benchmark.
|
|
1659
1789
|
"""
|
|
1660
|
-
|
|
1790
|
+
import warnings
|
|
1791
|
+
|
|
1792
|
+
warnings.filterwarnings("ignore")
|
|
1661
1793
|
df2 = self.get_trades_history()
|
|
1662
1794
|
if df2 is None:
|
|
1663
1795
|
return 0.0
|
|
@@ -1665,39 +1797,25 @@ class Trade(RiskManagement):
|
|
|
1665
1797
|
profit = df[["profit", "commission", "fee", "swap"]].sum(axis=1)
|
|
1666
1798
|
returns = profit.pct_change(fill_method=None)
|
|
1667
1799
|
periods = self.max_trade() * 252
|
|
1668
|
-
sharpe =
|
|
1800
|
+
sharpe = qs.stats.sharpe(returns, periods=periods)
|
|
1669
1801
|
|
|
1670
1802
|
return round(sharpe, 3)
|
|
1671
1803
|
|
|
1672
1804
|
def days_end(self) -> bool:
|
|
1673
1805
|
"""Check if it is the end of the trading day."""
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
ending_hour = int(self.ending_time_hour)
|
|
1678
|
-
ending_minute = int(self.ending_time_minutes)
|
|
1679
|
-
|
|
1680
|
-
if current_hour > ending_hour or (
|
|
1681
|
-
current_hour == ending_hour and current_minute >= ending_minute
|
|
1682
|
-
):
|
|
1806
|
+
now = datetime.now()
|
|
1807
|
+
end = datetime.strptime(self.end, "%H:%M").time()
|
|
1808
|
+
if now.time() >= end:
|
|
1683
1809
|
return True
|
|
1684
|
-
|
|
1685
|
-
return False
|
|
1810
|
+
return False
|
|
1686
1811
|
|
|
1687
1812
|
def trading_time(self):
|
|
1688
1813
|
"""Check if it is time to trade."""
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
):
|
|
1814
|
+
now = datetime.now()
|
|
1815
|
+
start = datetime.strptime(self.start, "%H:%M").time()
|
|
1816
|
+
end = datetime.strptime(self.finishing, "%H:%M").time()
|
|
1817
|
+
if start <= now.time() <= end:
|
|
1694
1818
|
return True
|
|
1695
|
-
elif datetime.now().hour == int(self.start_time_hour):
|
|
1696
|
-
if datetime.now().minute >= int(self.start_time_minutes):
|
|
1697
|
-
return True
|
|
1698
|
-
elif datetime.now().hour == int(self.finishing_time_hour):
|
|
1699
|
-
if datetime.now().minute < int(self.finishing_time_minutes):
|
|
1700
|
-
return True
|
|
1701
1819
|
return False
|
|
1702
1820
|
|
|
1703
1821
|
def sleep_time(self, weekend=False):
|
bbstrader/metatrader/utils.py
CHANGED
|
@@ -286,6 +286,22 @@ class TickInfo(NamedTuple):
|
|
|
286
286
|
volume_real: float
|
|
287
287
|
|
|
288
288
|
|
|
289
|
+
class BookInfo(NamedTuple):
|
|
290
|
+
"""
|
|
291
|
+
Represents the structure of a book.
|
|
292
|
+
* type: Type of the order (buy/sell)
|
|
293
|
+
* price: Price of the order
|
|
294
|
+
* volume: Volume of the order in lots
|
|
295
|
+
* volume_dbl: Volume with greater accuracy
|
|
296
|
+
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
type: int
|
|
300
|
+
price: float
|
|
301
|
+
volume: float
|
|
302
|
+
volume_dbl: float
|
|
303
|
+
|
|
304
|
+
|
|
289
305
|
class TradeRequest(NamedTuple):
|
|
290
306
|
"""
|
|
291
307
|
Represents a Trade Request Structure
|
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):
|