bbstrader 0.1.6__py3-none-any.whl → 0.1.7__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/__ini__.py +0 -1
- bbstrader/btengine/__init__.py +12 -9
- bbstrader/btengine/backtest.py +97 -702
- bbstrader/btengine/data.py +3 -1
- bbstrader/btengine/event.py +15 -9
- bbstrader/btengine/execution.py +66 -6
- bbstrader/btengine/performance.py +34 -1
- bbstrader/btengine/portfolio.py +24 -14
- bbstrader/btengine/strategy.py +1 -2
- bbstrader/metatrader/account.py +4 -4
- bbstrader/metatrader/rates.py +6 -1
- bbstrader/metatrader/trade.py +45 -32
- bbstrader/models/risk.py +39 -2
- bbstrader/trading/__init__.py +8 -1
- bbstrader/trading/execution.py +337 -928
- bbstrader/trading/strategies.py +838 -0
- bbstrader/tseries.py +603 -19
- {bbstrader-0.1.6.dist-info → bbstrader-0.1.7.dist-info}/METADATA +15 -7
- bbstrader-0.1.7.dist-info/RECORD +26 -0
- {bbstrader-0.1.6.dist-info → bbstrader-0.1.7.dist-info}/WHEEL +1 -1
- bbstrader/strategies.py +0 -681
- bbstrader/trading/run.py +0 -131
- bbstrader/trading/utils.py +0 -153
- bbstrader-0.1.6.dist-info/RECORD +0 -28
- {bbstrader-0.1.6.dist-info → bbstrader-0.1.7.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.6.dist-info → bbstrader-0.1.7.dist-info}/top_level.txt +0 -0
bbstrader/btengine/data.py
CHANGED
|
@@ -360,10 +360,12 @@ class YFHistoricDataHandler(BaseCSVDataHandler):
|
|
|
360
360
|
|
|
361
361
|
|
|
362
362
|
# TODO # Get data from EODHD
|
|
363
|
+
# https://eodhd.com/
|
|
363
364
|
class EODHDHistoricDataHandler(BaseCSVDataHandler):
|
|
364
365
|
...
|
|
365
366
|
|
|
366
|
-
# TODO # Get data from
|
|
367
|
+
# TODO # Get data from FMP using Financialtoolkit API
|
|
368
|
+
# https://github.com/bbalouki/FinanceToolkit
|
|
367
369
|
class FMPHistoricDataHandler(BaseCSVDataHandler):
|
|
368
370
|
...
|
|
369
371
|
|
bbstrader/btengine/event.py
CHANGED
|
@@ -54,9 +54,10 @@ class SignalEvent(Event):
|
|
|
54
54
|
strategy_id: int,
|
|
55
55
|
symbol: str,
|
|
56
56
|
datetime: datetime,
|
|
57
|
-
signal_type:
|
|
57
|
+
signal_type: Literal['LONG', 'SHORT', 'EXIT'],
|
|
58
58
|
quantity: int | float = 100,
|
|
59
|
-
strength: int | float = 1.0
|
|
59
|
+
strength: int | float = 1.0,
|
|
60
|
+
price: int | float = None
|
|
60
61
|
):
|
|
61
62
|
"""
|
|
62
63
|
Initialises the SignalEvent.
|
|
@@ -67,10 +68,11 @@ class SignalEvent(Event):
|
|
|
67
68
|
|
|
68
69
|
symbol (str): The ticker symbol, e.g. 'GOOG'.
|
|
69
70
|
datetime (datetime): The timestamp at which the signal was generated.
|
|
70
|
-
signal_type (str): 'LONG' or 'SHORT'.
|
|
71
|
+
signal_type (str): 'LONG' or 'SHORT' or 'EXIT'.
|
|
71
72
|
quantity (int | float): An optional integer (or float) representing the order size.
|
|
72
73
|
strength (int | float): An adjustment factor "suggestion" used to scale
|
|
73
74
|
quantity at the portfolio level. Useful for pairs strategies.
|
|
75
|
+
price (int | float): An optional price to be used when the signal is generated.
|
|
74
76
|
"""
|
|
75
77
|
self.type = 'SIGNAL'
|
|
76
78
|
self.strategy_id = strategy_id
|
|
@@ -79,6 +81,7 @@ class SignalEvent(Event):
|
|
|
79
81
|
self.signal_type = signal_type
|
|
80
82
|
self.quantity = quantity
|
|
81
83
|
self.strength = strength
|
|
84
|
+
self.price = price
|
|
82
85
|
|
|
83
86
|
|
|
84
87
|
class OrderEvent(Event):
|
|
@@ -96,9 +99,10 @@ class OrderEvent(Event):
|
|
|
96
99
|
|
|
97
100
|
def __init__(self,
|
|
98
101
|
symbol: str,
|
|
99
|
-
order_type:
|
|
102
|
+
order_type: Literal['MKT', 'LMT'],
|
|
100
103
|
quantity: int | float,
|
|
101
|
-
direction:
|
|
104
|
+
direction: Literal['BUY', 'SELL'],
|
|
105
|
+
price: int | float = None
|
|
102
106
|
):
|
|
103
107
|
"""
|
|
104
108
|
Initialises the order type, setting whether it is
|
|
@@ -110,20 +114,22 @@ class OrderEvent(Event):
|
|
|
110
114
|
order_type (str): 'MKT' or 'LMT' for Market or Limit.
|
|
111
115
|
quantity (int | float): Non-negative number for quantity.
|
|
112
116
|
direction (str): 'BUY' or 'SELL' for long or short.
|
|
117
|
+
price (int | float): The price at which to order.
|
|
113
118
|
"""
|
|
114
119
|
self.type = 'ORDER'
|
|
115
120
|
self.symbol = symbol
|
|
116
121
|
self.order_type = order_type
|
|
117
122
|
self.quantity = quantity
|
|
118
123
|
self.direction = direction
|
|
124
|
+
self.price = price
|
|
119
125
|
|
|
120
126
|
def print_order(self):
|
|
121
127
|
"""
|
|
122
128
|
Outputs the values within the Order.
|
|
123
129
|
"""
|
|
124
130
|
print(
|
|
125
|
-
"Order: Symbol=%s, Type=%s, Quantity=%s, Direction=%s" %
|
|
126
|
-
(self.symbol, self.order_type, self.quantity, self.direction)
|
|
131
|
+
"Order: Symbol=%s, Type=%s, Quantity=%s, Direction=%s, Price=%s" %
|
|
132
|
+
(self.symbol, self.order_type, self.quantity, self.direction, self.price)
|
|
127
133
|
)
|
|
128
134
|
|
|
129
135
|
|
|
@@ -151,7 +157,7 @@ class FillEvent(Event):
|
|
|
151
157
|
symbol: str,
|
|
152
158
|
exchange: str,
|
|
153
159
|
quantity: int | float,
|
|
154
|
-
direction: Literal['
|
|
160
|
+
direction: Literal['BUY', 'SELL'],
|
|
155
161
|
fill_cost: int | float | None,
|
|
156
162
|
commission: float | None = None
|
|
157
163
|
):
|
|
@@ -170,7 +176,7 @@ class FillEvent(Event):
|
|
|
170
176
|
exchange (str): The exchange where the order was filled.
|
|
171
177
|
quantity (int | float): The filled quantity.
|
|
172
178
|
direction (str): The direction of fill `('LONG', 'SHORT', 'EXIT')`
|
|
173
|
-
fill_cost (int | float):
|
|
179
|
+
fill_cost (int | float): Price of the shares when filled.
|
|
174
180
|
commission (float | None): An optional commission sent from IB.
|
|
175
181
|
"""
|
|
176
182
|
self.type = 'FILL'
|
bbstrader/btengine/execution.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import datetime
|
|
1
|
+
from datetime import datetime
|
|
2
2
|
from queue import Queue
|
|
3
3
|
from abc import ABCMeta, abstractmethod
|
|
4
4
|
from bbstrader.btengine.event import FillEvent, OrderEvent
|
|
5
|
+
from bbstrader.metatrader.account import Account
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
7
8
|
"ExecutionHandler",
|
|
8
|
-
"SimulatedExecutionHandler"
|
|
9
|
+
"SimulatedExecutionHandler",
|
|
10
|
+
"MT5ExecutionHandler"
|
|
9
11
|
]
|
|
10
12
|
|
|
11
13
|
|
|
@@ -71,13 +73,71 @@ class SimulatedExecutionHandler(ExecutionHandler):
|
|
|
71
73
|
"""
|
|
72
74
|
if event.type == 'ORDER':
|
|
73
75
|
fill_event = FillEvent(
|
|
74
|
-
datetime.
|
|
76
|
+
datetime.now(), event.symbol,
|
|
75
77
|
'ARCA', event.quantity, event.direction, None
|
|
76
78
|
)
|
|
77
79
|
self.events.put(fill_event)
|
|
78
80
|
|
|
79
|
-
# TODO # Use in live execution
|
|
80
|
-
|
|
81
81
|
|
|
82
82
|
class MT5ExecutionHandler(ExecutionHandler):
|
|
83
|
-
|
|
83
|
+
def __init__(self, events: Queue, **kwargs):
|
|
84
|
+
"""
|
|
85
|
+
"""
|
|
86
|
+
self.events = events
|
|
87
|
+
self.account = Account()
|
|
88
|
+
|
|
89
|
+
def _estimate_total_fees(self, symbol, lot):
|
|
90
|
+
# TODO: Implement the calculation of fees based on the broker's fees
|
|
91
|
+
# https://www.metatrader5.com/en/terminal/help/trading/market_watch
|
|
92
|
+
# Calculate fees based on the broker's fees , swap and commission
|
|
93
|
+
return 0.0
|
|
94
|
+
|
|
95
|
+
def _calculate_lot(self, symbol, quantity, price):
|
|
96
|
+
FX = self.account.get_symbol_type(symbol) == 'FX'
|
|
97
|
+
COMD = self.account.get_symbol_type(symbol) == 'COMD'
|
|
98
|
+
FUT = self.account.get_symbol_type(symbol) == 'FUT'
|
|
99
|
+
CRYPTO = self.account.get_symbol_type(symbol) == 'CRYPTO'
|
|
100
|
+
symbol_info = self.account.get_symbol_info(symbol)
|
|
101
|
+
contract_size = symbol_info.trade_contract_size
|
|
102
|
+
|
|
103
|
+
lot = (quantity*price) / (contract_size * price)
|
|
104
|
+
if contract_size == 1:
|
|
105
|
+
lot = quantity
|
|
106
|
+
if COMD or FUT or CRYPTO and contract_size > 1:
|
|
107
|
+
lot = quantity / contract_size
|
|
108
|
+
if FX:
|
|
109
|
+
lot = (quantity*price / contract_size)
|
|
110
|
+
return self._check_lot(symbol, lot)
|
|
111
|
+
|
|
112
|
+
def _check_lot(self, symbol, lot):
|
|
113
|
+
symbol_info = self.account.get_symbol_info(symbol)
|
|
114
|
+
if lot < symbol_info.volume_min:
|
|
115
|
+
return symbol_info.volume_min
|
|
116
|
+
elif lot > symbol_info.volume_max:
|
|
117
|
+
return symbol_info.volume_max
|
|
118
|
+
return round(lot, 2)
|
|
119
|
+
|
|
120
|
+
def execute_order(self, event: OrderEvent):
|
|
121
|
+
"""
|
|
122
|
+
Executes an Order event by converting it into a Fill event.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
event (OrderEvent): Contains an Event object with order information.
|
|
126
|
+
"""
|
|
127
|
+
if event.type == 'ORDER':
|
|
128
|
+
symbol = event.symbol
|
|
129
|
+
direction = event.direction
|
|
130
|
+
quantity = event.quantity
|
|
131
|
+
price = event.price
|
|
132
|
+
lot = self._calculate_lot(symbol, quantity, price)
|
|
133
|
+
fees = self._estimate_total_fees(symbol, lot)
|
|
134
|
+
fill_event = FillEvent(
|
|
135
|
+
timeindex=datetime.now(), symbol=symbol,
|
|
136
|
+
exchange='MT5', quantity=quantity, direction=direction,
|
|
137
|
+
fill_cost=None, commission=fees
|
|
138
|
+
)
|
|
139
|
+
self.events.put(fill_event)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class IBExecutionHandler(ExecutionHandler):
|
|
143
|
+
...
|
|
@@ -6,9 +6,11 @@ import yfinance as yf
|
|
|
6
6
|
from scipy.stats import mstats
|
|
7
7
|
import matplotlib.pyplot as plt
|
|
8
8
|
from matplotlib.ticker import MaxNLocator
|
|
9
|
+
import quantstats as qs
|
|
9
10
|
|
|
10
11
|
import warnings
|
|
11
12
|
warnings.filterwarnings("ignore")
|
|
13
|
+
warnings.simplefilter(action='ignore', category=FutureWarning)
|
|
12
14
|
sns.set_theme()
|
|
13
15
|
|
|
14
16
|
__all__ = [
|
|
@@ -17,7 +19,8 @@ __all__ = [
|
|
|
17
19
|
"create_sharpe_ratio",
|
|
18
20
|
"create_sortino_ratio",
|
|
19
21
|
"plot_returns_and_dd",
|
|
20
|
-
"plot_monthly_yearly_returns"
|
|
22
|
+
"plot_monthly_yearly_returns",
|
|
23
|
+
"show_qs_stats"
|
|
21
24
|
]
|
|
22
25
|
|
|
23
26
|
|
|
@@ -307,3 +310,33 @@ def plot_monthly_yearly_returns(df, title):
|
|
|
307
310
|
|
|
308
311
|
# Show the plot
|
|
309
312
|
plt.show()
|
|
313
|
+
|
|
314
|
+
def show_qs_stats(equity_curve, benchmark, strategy_name):
|
|
315
|
+
"""
|
|
316
|
+
Generate the full quantstats report for the strategy.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
equity_curve (pd.DataFrame):
|
|
320
|
+
The DataFrame containing the strategy returns and drawdowns.
|
|
321
|
+
benchmark (str):
|
|
322
|
+
The ticker symbol of the benchmark to compare the strategy to.
|
|
323
|
+
strategy_name (str): The name of the strategy.
|
|
324
|
+
"""
|
|
325
|
+
# Load the returns data
|
|
326
|
+
returns = equity_curve.copy()
|
|
327
|
+
|
|
328
|
+
# Drop duplicate index entries
|
|
329
|
+
returns = returns[~returns.index.duplicated(keep='first')]
|
|
330
|
+
|
|
331
|
+
# Extend pandas functionality with quantstats
|
|
332
|
+
qs.extend_pandas()
|
|
333
|
+
|
|
334
|
+
# Generate the full report with a benchmark
|
|
335
|
+
file = strategy_name.replace(' ', '_')
|
|
336
|
+
qs.reports.full(
|
|
337
|
+
returns['Returns'], mode='full', benchmark=benchmark)
|
|
338
|
+
qs.reports.html(
|
|
339
|
+
returns['Returns'],
|
|
340
|
+
benchmark='SPY',
|
|
341
|
+
output=f"{file}_report.html",
|
|
342
|
+
title=strategy_name)
|
bbstrader/btengine/portfolio.py
CHANGED
|
@@ -6,10 +6,10 @@ from bbstrader.btengine.event import (
|
|
|
6
6
|
)
|
|
7
7
|
from bbstrader.btengine.data import DataHandler
|
|
8
8
|
from bbstrader.btengine.performance import (
|
|
9
|
-
create_drawdowns, plot_performance,
|
|
10
|
-
create_sharpe_ratio, create_sortino_ratio,
|
|
9
|
+
create_drawdowns, plot_performance, show_qs_stats,
|
|
11
10
|
plot_returns_and_dd, plot_monthly_yearly_returns
|
|
12
11
|
)
|
|
12
|
+
import quantstats as qs
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class Portfolio(object):
|
|
@@ -20,7 +20,7 @@ class Portfolio(object):
|
|
|
20
20
|
sizing tools (such as the `Kelly Criterion`).
|
|
21
21
|
|
|
22
22
|
The portfolio order management system is possibly the most complex component of
|
|
23
|
-
an
|
|
23
|
+
an event driven backtester. Its role is to keep track of all current market positions
|
|
24
24
|
as well as the market value of the positions (known as the "holdings").
|
|
25
25
|
This is simply an estimate of the liquidation value of the position and is derived in part
|
|
26
26
|
from the data handling facility of the backtester.
|
|
@@ -95,7 +95,7 @@ class Portfolio(object):
|
|
|
95
95
|
self.timeframe = kwargs.get("time_frame", "D1")
|
|
96
96
|
self.trading_hours = kwargs.get("session_duration", 6.5)
|
|
97
97
|
self.benchmark = kwargs.get('benchmark', 'SPY')
|
|
98
|
-
self.strategy_name = kwargs.get('strategy_name', '
|
|
98
|
+
self.strategy_name = kwargs.get('strategy_name', '')
|
|
99
99
|
if self.timeframe not in self._tf_mapping():
|
|
100
100
|
raise ValueError(
|
|
101
101
|
f"Timeframe not supported,"
|
|
@@ -227,10 +227,10 @@ class Portfolio(object):
|
|
|
227
227
|
fill_dir = -1
|
|
228
228
|
|
|
229
229
|
# Update holdings list with new quantities
|
|
230
|
-
|
|
230
|
+
price = self.bars.get_latest_bar_value(
|
|
231
231
|
fill.symbol, "Adj Close"
|
|
232
232
|
)
|
|
233
|
-
cost = fill_dir *
|
|
233
|
+
cost = fill_dir * price * fill.quantity
|
|
234
234
|
self.current_holdings[fill.symbol] += cost
|
|
235
235
|
self.current_holdings['Commission'] += fill.commission
|
|
236
236
|
self.current_holdings['Cash'] -= (cost + fill.commission)
|
|
@@ -260,20 +260,21 @@ class Portfolio(object):
|
|
|
260
260
|
direction = signal.signal_type
|
|
261
261
|
quantity = signal.quantity
|
|
262
262
|
strength = signal.strength
|
|
263
|
+
price = signal.price
|
|
263
264
|
cur_quantity = self.current_positions[symbol]
|
|
264
265
|
|
|
265
266
|
order_type = 'MKT'
|
|
266
267
|
mkt_quantity = round(quantity * strength)
|
|
267
268
|
|
|
268
269
|
if direction == 'LONG' and cur_quantity == 0:
|
|
269
|
-
order = OrderEvent(symbol, order_type, mkt_quantity, 'BUY')
|
|
270
|
+
order = OrderEvent(symbol, order_type, mkt_quantity, 'BUY', price)
|
|
270
271
|
if direction == 'SHORT' and cur_quantity == 0:
|
|
271
|
-
order = OrderEvent(symbol, order_type, mkt_quantity, 'SELL')
|
|
272
|
+
order = OrderEvent(symbol, order_type, mkt_quantity, 'SELL', price)
|
|
272
273
|
|
|
273
274
|
if direction == 'EXIT' and cur_quantity > 0:
|
|
274
|
-
order = OrderEvent(symbol, order_type, abs(cur_quantity), 'SELL')
|
|
275
|
+
order = OrderEvent(symbol, order_type, abs(cur_quantity), 'SELL', price)
|
|
275
276
|
if direction == 'EXIT' and cur_quantity < 0:
|
|
276
|
-
order = OrderEvent(symbol, order_type, abs(cur_quantity), 'BUY')
|
|
277
|
+
order = OrderEvent(symbol, order_type, abs(cur_quantity), 'BUY', price)
|
|
277
278
|
|
|
278
279
|
return order
|
|
279
280
|
|
|
@@ -305,9 +306,12 @@ class Portfolio(object):
|
|
|
305
306
|
returns = self.equity_curve['Returns']
|
|
306
307
|
pnl = self.equity_curve['Equity Curve']
|
|
307
308
|
|
|
308
|
-
sharpe_ratio =
|
|
309
|
-
sortino_ratio =
|
|
310
|
-
drawdown,
|
|
309
|
+
sharpe_ratio = qs.stats.sharpe(returns, periods=self.tf)
|
|
310
|
+
sortino_ratio = qs.stats.sortino(returns, periods=self.tf)
|
|
311
|
+
drawdown, _, _ = create_drawdowns(pnl)
|
|
312
|
+
max_dd = qs.stats.max_drawdown(returns)
|
|
313
|
+
dd_details = qs.stats.drawdown_details(drawdown)
|
|
314
|
+
dd_duration = dd_details['days'].max()
|
|
311
315
|
self.equity_curve['Drawdown'] = drawdown
|
|
312
316
|
|
|
313
317
|
stats = [
|
|
@@ -317,10 +321,16 @@ class Portfolio(object):
|
|
|
317
321
|
("Max Drawdown", f"{max_dd * 100.0:.2f}%"),
|
|
318
322
|
("Drawdown Duration", f"{dd_duration}")
|
|
319
323
|
]
|
|
320
|
-
|
|
324
|
+
now = datetime.now().strftime('%Y%m%d%H%M%S')
|
|
325
|
+
strategy_name = self.strategy_name.replace(' ', '_')
|
|
326
|
+
file = f"{strategy_name}_{now}_equity.csv"
|
|
327
|
+
self.equity_curve.to_csv(file)
|
|
321
328
|
plot_performance(self.equity_curve, self.strategy_name)
|
|
322
329
|
plot_returns_and_dd(self.equity_curve,
|
|
323
330
|
self.benchmark, self.strategy_name)
|
|
331
|
+
qs.plots.monthly_heatmap(returns, savefig='monthly_heatmap.png')
|
|
324
332
|
plot_monthly_yearly_returns(self.equity_curve, self.strategy_name)
|
|
333
|
+
show_qs_stats(self.equity_curve, self.benchmark, self.strategy_name)
|
|
325
334
|
|
|
326
335
|
return stats
|
|
336
|
+
|
bbstrader/btengine/strategy.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from abc import ABCMeta, abstractmethod
|
|
2
2
|
from typing import Dict, Union
|
|
3
3
|
|
|
4
|
-
|
|
5
4
|
class Strategy(metaclass=ABCMeta):
|
|
6
5
|
"""
|
|
7
6
|
A `Strategy()` object encapsulates all calculation on market data
|
|
@@ -30,4 +29,4 @@ class Strategy(metaclass=ABCMeta):
|
|
|
30
29
|
"""
|
|
31
30
|
raise NotImplementedError(
|
|
32
31
|
"Should implement calculate_signals()"
|
|
33
|
-
)
|
|
32
|
+
)
|
bbstrader/metatrader/account.py
CHANGED
|
@@ -782,7 +782,7 @@ class Account(object):
|
|
|
782
782
|
else:
|
|
783
783
|
positions = mt5.positions_get()
|
|
784
784
|
|
|
785
|
-
if positions is None:
|
|
785
|
+
if positions is None or len(positions) == 0:
|
|
786
786
|
return None
|
|
787
787
|
if to_df:
|
|
788
788
|
df = pd.DataFrame(list(positions), columns=positions[0]._asdict())
|
|
@@ -885,7 +885,7 @@ class Account(object):
|
|
|
885
885
|
else:
|
|
886
886
|
position_deals = mt5.history_deals_get(date_from, date_to)
|
|
887
887
|
|
|
888
|
-
if position_deals is None:
|
|
888
|
+
if position_deals is None or len(position_deals) == 0:
|
|
889
889
|
return None
|
|
890
890
|
|
|
891
891
|
df = pd.DataFrame(list(position_deals),
|
|
@@ -961,7 +961,7 @@ class Account(object):
|
|
|
961
961
|
else:
|
|
962
962
|
orders = mt5.orders_get()
|
|
963
963
|
|
|
964
|
-
if orders is None:
|
|
964
|
+
if orders is None or len(orders) == 0:
|
|
965
965
|
return None
|
|
966
966
|
|
|
967
967
|
if to_df:
|
|
@@ -1064,7 +1064,7 @@ class Account(object):
|
|
|
1064
1064
|
else:
|
|
1065
1065
|
history_orders = mt5.history_orders_get(date_from, date_to)
|
|
1066
1066
|
|
|
1067
|
-
if history_orders is None:
|
|
1067
|
+
if history_orders is None or len(history_orders) == 0:
|
|
1068
1068
|
return None
|
|
1069
1069
|
|
|
1070
1070
|
df = pd.DataFrame(list(history_orders),
|
bbstrader/metatrader/rates.py
CHANGED
|
@@ -22,7 +22,12 @@ class Rates(object):
|
|
|
22
22
|
and count of bars or by providing a specific date range.
|
|
23
23
|
|
|
24
24
|
Notes:
|
|
25
|
-
|
|
25
|
+
1. Befor using this class, ensure that the `Max bars in chart` in you terminal
|
|
26
|
+
is set to a value that is greater than the number of bars you want to retrieve
|
|
27
|
+
or just set it to Unlimited.
|
|
28
|
+
In your MT5 terminal, go to `Tools` -> `Options` -> `Charts` -> `Max bars in chart`.
|
|
29
|
+
|
|
30
|
+
2. The `get_open, get_high, get_low, get_close, get_adj_close, get_returns,
|
|
26
31
|
get_volume` properties return data in Broker's timezone.
|
|
27
32
|
|
|
28
33
|
Example:
|