bbstrader 0.1.94__py3-none-any.whl → 0.2.0__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 +2 -2
- bbstrader/btengine/data.py +14 -4
- bbstrader/btengine/strategy.py +12 -8
- bbstrader/config.py +4 -0
- bbstrader/core/__init__.py +0 -0
- bbstrader/core/data.py +23 -0
- bbstrader/core/utils.py +0 -0
- bbstrader/ibkr/__init__.py +0 -0
- bbstrader/metatrader/rates.py +3 -3
- bbstrader/metatrader/trade.py +17 -4
- bbstrader/models/__init__.py +1 -1
- bbstrader/models/factors.py +275 -0
- bbstrader/models/ml.py +1026 -0
- bbstrader/models/optimization.py +17 -16
- bbstrader/models/{portfolios.py → portfolio.py} +20 -11
- bbstrader/models/risk.py +1 -1
- bbstrader/trading/execution.py +48 -26
- bbstrader/trading/strategies.py +1 -1
- bbstrader/tseries.py +412 -63
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.0.dist-info}/METADATA +5 -1
- bbstrader-0.2.0.dist-info/RECORD +36 -0
- bbstrader-0.1.94.dist-info/RECORD +0 -32
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.0.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.0.dist-info}/WHEEL +0 -0
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.0.dist-info}/top_level.txt +0 -0
bbstrader/models/optimization.py
CHANGED
|
@@ -17,19 +17,19 @@ def markowitz_weights(prices=None, rfr=0.0, freq=252):
|
|
|
17
17
|
"""
|
|
18
18
|
Calculates optimal portfolio weights using Markowitz's mean-variance optimization (Max Sharpe Ratio) with multiple solvers.
|
|
19
19
|
|
|
20
|
-
Parameters
|
|
20
|
+
Parameters
|
|
21
21
|
----------
|
|
22
22
|
prices : pd.DataFrame, optional
|
|
23
23
|
Price data for assets, where rows represent time periods and columns represent assets.
|
|
24
24
|
freq : int, optional
|
|
25
25
|
Frequency of the data, such as 252 for daily returns in a year (default is 252).
|
|
26
26
|
|
|
27
|
-
Returns
|
|
27
|
+
Returns
|
|
28
28
|
-------
|
|
29
29
|
dict
|
|
30
30
|
Dictionary containing the optimal asset weights for maximizing the Sharpe ratio, normalized to sum to 1.
|
|
31
31
|
|
|
32
|
-
Notes
|
|
32
|
+
Notes
|
|
33
33
|
-----
|
|
34
34
|
This function attempts to maximize the Sharpe ratio by iterating through various solvers ('SCS', 'ECOS', 'OSQP')
|
|
35
35
|
from the PyPortfolioOpt library. If a solver fails, it proceeds to the next one. If none succeed, an error message
|
|
@@ -37,7 +37,7 @@ def markowitz_weights(prices=None, rfr=0.0, freq=252):
|
|
|
37
37
|
|
|
38
38
|
This function is useful for portfolio with a small number of assets, as it may not scale well for large portfolios.
|
|
39
39
|
|
|
40
|
-
Raises
|
|
40
|
+
Raises
|
|
41
41
|
------
|
|
42
42
|
Exception
|
|
43
43
|
If all solvers fail, each will print an exception error message during runtime.
|
|
@@ -61,7 +61,7 @@ def hierarchical_risk_parity(prices=None, returns=None, freq=252):
|
|
|
61
61
|
"""
|
|
62
62
|
Computes asset weights using Hierarchical Risk Parity (HRP) for risk-averse portfolio allocation.
|
|
63
63
|
|
|
64
|
-
Parameters
|
|
64
|
+
Parameters
|
|
65
65
|
----------
|
|
66
66
|
prices : pd.DataFrame, optional
|
|
67
67
|
Price data for assets; if provided, daily returns will be calculated.
|
|
@@ -70,17 +70,17 @@ def hierarchical_risk_parity(prices=None, returns=None, freq=252):
|
|
|
70
70
|
freq : int, optional
|
|
71
71
|
Number of days to consider in calculating portfolio weights (default is 252).
|
|
72
72
|
|
|
73
|
-
Returns
|
|
73
|
+
Returns
|
|
74
74
|
-------
|
|
75
75
|
dict
|
|
76
76
|
Optimized asset weights using the HRP method, with asset weights summing to 1.
|
|
77
77
|
|
|
78
|
-
Raises
|
|
78
|
+
Raises
|
|
79
79
|
------
|
|
80
80
|
ValueError
|
|
81
81
|
If neither `prices` nor `returns` are provided.
|
|
82
82
|
|
|
83
|
-
Notes
|
|
83
|
+
Notes
|
|
84
84
|
-----
|
|
85
85
|
Hierarchical Risk Parity is particularly useful for portfolios with a large number of assets,
|
|
86
86
|
as it mitigates issues of multicollinearity and estimation errors in covariance matrices by
|
|
@@ -101,7 +101,7 @@ def equal_weighted(prices=None, returns=None, round_digits=5):
|
|
|
101
101
|
"""
|
|
102
102
|
Generates an equal-weighted portfolio by assigning an equal proportion to each asset.
|
|
103
103
|
|
|
104
|
-
Parameters
|
|
104
|
+
Parameters
|
|
105
105
|
----------
|
|
106
106
|
prices : pd.DataFrame, optional
|
|
107
107
|
Price data for assets, where each column represents an asset.
|
|
@@ -110,21 +110,22 @@ def equal_weighted(prices=None, returns=None, round_digits=5):
|
|
|
110
110
|
round_digits : int, optional
|
|
111
111
|
Number of decimal places to round each weight to (default is 5).
|
|
112
112
|
|
|
113
|
-
Returns
|
|
113
|
+
Returns
|
|
114
114
|
-------
|
|
115
115
|
dict
|
|
116
116
|
Dictionary with equal weights assigned to each asset, summing to 1.
|
|
117
117
|
|
|
118
|
-
Raises
|
|
118
|
+
Raises
|
|
119
119
|
------
|
|
120
120
|
ValueError
|
|
121
121
|
If neither `prices` nor `returns` are provided.
|
|
122
122
|
|
|
123
|
-
Notes
|
|
123
|
+
Notes
|
|
124
124
|
-----
|
|
125
125
|
Equal weighting is a simple allocation method that assumes equal importance across all assets,
|
|
126
126
|
useful as a baseline model and when no strong views exist on asset return expectations or risk.
|
|
127
127
|
"""
|
|
128
|
+
|
|
128
129
|
if returns is None and prices is None:
|
|
129
130
|
raise ValueError("Either prices or returns must be provided")
|
|
130
131
|
if returns is None:
|
|
@@ -139,7 +140,7 @@ def optimized_weights(prices=None, returns=None, rfr=0.0, freq=252, method='equa
|
|
|
139
140
|
"""
|
|
140
141
|
Selects an optimization method to calculate portfolio weights based on user preference.
|
|
141
142
|
|
|
142
|
-
Parameters
|
|
143
|
+
Parameters
|
|
143
144
|
----------
|
|
144
145
|
prices : pd.DataFrame, optional
|
|
145
146
|
Price data for assets, required for certain methods.
|
|
@@ -150,17 +151,17 @@ def optimized_weights(prices=None, returns=None, rfr=0.0, freq=252, method='equa
|
|
|
150
151
|
method : str, optional
|
|
151
152
|
Optimization method to use ('markowitz', 'hrp', or 'equal') (default is 'markowitz').
|
|
152
153
|
|
|
153
|
-
Returns
|
|
154
|
+
Returns
|
|
154
155
|
-------
|
|
155
156
|
dict
|
|
156
157
|
Dictionary containing optimized asset weights based on the chosen method.
|
|
157
158
|
|
|
158
|
-
Raises
|
|
159
|
+
Raises
|
|
159
160
|
------
|
|
160
161
|
ValueError
|
|
161
162
|
If an unknown optimization method is specified.
|
|
162
163
|
|
|
163
|
-
Notes
|
|
164
|
+
Notes
|
|
164
165
|
-----
|
|
165
166
|
This function integrates different optimization methods:
|
|
166
167
|
- 'markowitz': mean-variance optimization with max Sharpe ratio
|
|
@@ -20,6 +20,15 @@ class EigenPortfolios(object):
|
|
|
20
20
|
to derive portfolios (eigenportfolios) that capture distinct risk factors in the asset returns. Each eigenportfolio
|
|
21
21
|
represents a principal component of the return covariance matrix, ordered by the magnitude of its eigenvalue. These
|
|
22
22
|
portfolios capture most of the variance in asset returns and are mutually uncorrelated.
|
|
23
|
+
|
|
24
|
+
Notes
|
|
25
|
+
-----
|
|
26
|
+
The implementation is inspired by the book "Machine Learning for Algorithmic Trading" by Stefan Jansen.
|
|
27
|
+
|
|
28
|
+
References
|
|
29
|
+
----------
|
|
30
|
+
Stefan Jansen (2020). Machine Learning for Algorithmic Trading - Second Edition.
|
|
31
|
+
chapter 13, Data-Driven Risk Factors and Asset Allocation with Unsupervised Learning.
|
|
23
32
|
|
|
24
33
|
"""
|
|
25
34
|
def __init__(self):
|
|
@@ -32,12 +41,12 @@ class EigenPortfolios(object):
|
|
|
32
41
|
"""
|
|
33
42
|
Returns the computed eigenportfolios (weights of assets in each portfolio).
|
|
34
43
|
|
|
35
|
-
Returns
|
|
44
|
+
Returns
|
|
36
45
|
-------
|
|
37
46
|
pd.DataFrame
|
|
38
47
|
DataFrame containing eigenportfolio weights for each asset.
|
|
39
48
|
|
|
40
|
-
Raises
|
|
49
|
+
Raises
|
|
41
50
|
------
|
|
42
51
|
ValueError
|
|
43
52
|
If `fit()` has not been called before retrieving portfolios.
|
|
@@ -50,19 +59,19 @@ class EigenPortfolios(object):
|
|
|
50
59
|
"""
|
|
51
60
|
Computes the eigenportfolios based on PCA of the asset returns' covariance matrix.
|
|
52
61
|
|
|
53
|
-
Parameters
|
|
62
|
+
Parameters
|
|
54
63
|
----------
|
|
55
64
|
returns : pd.DataFrame
|
|
56
65
|
Historical returns of assets to be used for PCA.
|
|
57
66
|
n_portfolios : int, optional
|
|
58
67
|
Number of eigenportfolios to compute (default is 4).
|
|
59
68
|
|
|
60
|
-
Returns
|
|
69
|
+
Returns
|
|
61
70
|
-------
|
|
62
71
|
pd.DataFrame
|
|
63
72
|
DataFrame containing normalized weights for each eigenportfolio.
|
|
64
73
|
|
|
65
|
-
Notes
|
|
74
|
+
Notes
|
|
66
75
|
-----
|
|
67
76
|
This method performs winsorization and normalization on returns to reduce the impact of outliers
|
|
68
77
|
and achieve zero mean and unit variance. It uses the first `n_portfolios` principal components
|
|
@@ -94,7 +103,7 @@ class EigenPortfolios(object):
|
|
|
94
103
|
"""
|
|
95
104
|
Plots the weights of each asset in each eigenportfolio as bar charts.
|
|
96
105
|
|
|
97
|
-
Notes
|
|
106
|
+
Notes
|
|
98
107
|
-----
|
|
99
108
|
Each subplot represents one eigenportfolio, showing the contribution of each asset.
|
|
100
109
|
"""
|
|
@@ -118,7 +127,7 @@ class EigenPortfolios(object):
|
|
|
118
127
|
"""
|
|
119
128
|
Plots the cumulative returns of each eigenportfolio over time.
|
|
120
129
|
|
|
121
|
-
Notes
|
|
130
|
+
Notes
|
|
122
131
|
-----
|
|
123
132
|
This method calculates the historical cumulative performance of each eigenportfolio
|
|
124
133
|
by weighting asset returns according to eigenportfolio weights.
|
|
@@ -152,7 +161,7 @@ class EigenPortfolios(object):
|
|
|
152
161
|
"""
|
|
153
162
|
Optimizes the chosen eigenportfolio based on a specified optimization method.
|
|
154
163
|
|
|
155
|
-
Parameters
|
|
164
|
+
Parameters
|
|
156
165
|
----------
|
|
157
166
|
portfolio : int, optional
|
|
158
167
|
Index of the eigenportfolio to optimize (default is 1).
|
|
@@ -165,17 +174,17 @@ class EigenPortfolios(object):
|
|
|
165
174
|
plot : bool, optional
|
|
166
175
|
Whether to plot the performance of the optimized portfolio (default is True).
|
|
167
176
|
|
|
168
|
-
Returns
|
|
177
|
+
Returns
|
|
169
178
|
-------
|
|
170
179
|
dict
|
|
171
180
|
Dictionary of optimized asset weights.
|
|
172
181
|
|
|
173
|
-
Raises
|
|
182
|
+
Raises
|
|
174
183
|
------
|
|
175
184
|
ValueError
|
|
176
185
|
If an unknown optimizer is specified, or if prices are not provided when using Markowitz optimization.
|
|
177
186
|
|
|
178
|
-
Notes
|
|
187
|
+
Notes
|
|
179
188
|
-----
|
|
180
189
|
The optimization method varies based on risk-return assumptions, with options for traditional Markowitz optimization,
|
|
181
190
|
Hierarchical Risk Parity, or equal weighting.
|
bbstrader/models/risk.py
CHANGED
|
@@ -385,7 +385,7 @@ def build_hmm_models(symbol_list=None, **kwargs
|
|
|
385
385
|
hmm_models[symbol] = hmm
|
|
386
386
|
if mt5_data:
|
|
387
387
|
for symbol in symbols:
|
|
388
|
-
rates = Rates(symbol, tf, start_pos=hmm_end, session_duration=sd)
|
|
388
|
+
rates = Rates(symbol, timeframe=tf, start_pos=hmm_end, session_duration=sd, **kwargs)
|
|
389
389
|
data = rates.get_rates_from_pos()
|
|
390
390
|
assert data is not None, f"No data for {symbol}"
|
|
391
391
|
hmm = HMMRiskManager(
|
bbstrader/trading/execution.py
CHANGED
|
@@ -85,12 +85,17 @@ def _mt5_execution(
|
|
|
85
85
|
time_frame = kwargs.get('time_frame', '15m')
|
|
86
86
|
STRATEGY = kwargs.get('strategy_name')
|
|
87
87
|
mtrades = kwargs.get('max_trades')
|
|
88
|
+
check_max_trades = kwargs.get('check_max_trades', True)
|
|
88
89
|
notify = kwargs.get('notify', False)
|
|
89
90
|
if notify:
|
|
90
91
|
telegram = kwargs.get('telegram', False)
|
|
91
92
|
bot_token = kwargs.get('bot_token')
|
|
92
93
|
chat_id = kwargs.get('chat_id')
|
|
93
|
-
|
|
94
|
+
|
|
95
|
+
expert_ids = kwargs.get('expert_id')
|
|
96
|
+
if expert_ids is None:
|
|
97
|
+
expert_ids = set([trade.expert_id for trade in trades_instances.values()])
|
|
98
|
+
|
|
94
99
|
def update_risk(weights):
|
|
95
100
|
if weights is not None:
|
|
96
101
|
for symbol in symbols:
|
|
@@ -144,8 +149,14 @@ def _mt5_execution(
|
|
|
144
149
|
positions_orders[type] = {}
|
|
145
150
|
for symbol in symbols:
|
|
146
151
|
positions_orders[type][symbol] = None
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
for id in expert_ids:
|
|
153
|
+
func = getattr(trades_instances[symbol], f"get_current_{type}")
|
|
154
|
+
func_value = func(id=id)
|
|
155
|
+
if func_value is not None:
|
|
156
|
+
if positions_orders[type][symbol] is None:
|
|
157
|
+
positions_orders[type][symbol] = func(id=id)
|
|
158
|
+
else:
|
|
159
|
+
positions_orders[type][symbol] += func(id=id)
|
|
149
160
|
buys = positions_orders['buys']
|
|
150
161
|
sells = positions_orders['sells']
|
|
151
162
|
for symbol in symbols:
|
|
@@ -155,14 +166,15 @@ def _mt5_execution(
|
|
|
155
166
|
logger.info(
|
|
156
167
|
f"Current {type.upper()} SYMBOL={symbol}: \
|
|
157
168
|
{positions_orders[type][symbol]}, STRATEGY={STRATEGY}")
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
169
|
+
if check_max_trades:
|
|
170
|
+
long_market = {
|
|
171
|
+
symbol: buys[symbol] is not None
|
|
172
|
+
and len(buys[symbol]) >= max_trades[symbol] for symbol in symbols
|
|
173
|
+
}
|
|
174
|
+
short_market = {
|
|
175
|
+
symbol: sells[symbol] is not None
|
|
176
|
+
and len(sells[symbol]) >= max_trades[symbol] for symbol in symbols
|
|
177
|
+
}
|
|
166
178
|
|
|
167
179
|
except Exception as e:
|
|
168
180
|
logger.error(f"Handling Positions and Orders, {e}, STRATEGY={STRATEGY}")
|
|
@@ -186,12 +198,15 @@ def _mt5_execution(
|
|
|
186
198
|
riskmsg = f"Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
|
|
187
199
|
signal = signals[symbol]
|
|
188
200
|
if isinstance(signal, dict):
|
|
189
|
-
|
|
190
|
-
price = signal
|
|
201
|
+
action = signal.get('action')
|
|
202
|
+
price = signal.get('price')
|
|
203
|
+
id = signal.get('id', trade.expert_id)
|
|
191
204
|
stoplimit = signal.get('stoplimit')
|
|
205
|
+
signal = action
|
|
192
206
|
elif isinstance(signal, str):
|
|
193
207
|
price = None
|
|
194
208
|
stoplimit = None
|
|
209
|
+
id = trade.expert_id
|
|
195
210
|
if trade.trading_time() and today in trading_days:
|
|
196
211
|
if signal is not None:
|
|
197
212
|
signal = 'BMKT' if signal == 'LONG' else signal
|
|
@@ -207,9 +222,9 @@ def _mt5_execution(
|
|
|
207
222
|
if notify:
|
|
208
223
|
_send_notification(info)
|
|
209
224
|
if position_type in POSITIONS_TYPES:
|
|
210
|
-
trade.close_positions(position_type=order_type)
|
|
225
|
+
trade.close_positions(position_type=order_type, id=id)
|
|
211
226
|
else:
|
|
212
|
-
trade.close_orders(order_type=order_type)
|
|
227
|
+
trade.close_orders(order_type=order_type, id=id)
|
|
213
228
|
elif signal in BUYS and not long_market[symbol]:
|
|
214
229
|
if use_trade_time:
|
|
215
230
|
if time_intervals % trade_time == 0 or buys[symbol] is None:
|
|
@@ -217,7 +232,7 @@ def _mt5_execution(
|
|
|
217
232
|
if notify:
|
|
218
233
|
_send_notification(info)
|
|
219
234
|
trade.open_buy_position(
|
|
220
|
-
action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
|
|
235
|
+
action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
|
|
221
236
|
else:
|
|
222
237
|
logger.info(tfmsg)
|
|
223
238
|
check(buys[symbol], sells[symbol], symbol)
|
|
@@ -226,9 +241,11 @@ def _mt5_execution(
|
|
|
226
241
|
if notify:
|
|
227
242
|
_send_notification(info)
|
|
228
243
|
trade.open_buy_position(
|
|
229
|
-
action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
|
|
230
|
-
elif signal in BUYS
|
|
231
|
-
|
|
244
|
+
action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
|
|
245
|
+
elif signal in BUYS:
|
|
246
|
+
if check_max_trades:
|
|
247
|
+
if long_market[symbol]:
|
|
248
|
+
logger.info(riskmsg)
|
|
232
249
|
check(buys[symbol], sells[symbol], symbol)
|
|
233
250
|
|
|
234
251
|
elif signal in SELLS and not short_market[symbol]:
|
|
@@ -238,7 +255,7 @@ def _mt5_execution(
|
|
|
238
255
|
if notify:
|
|
239
256
|
_send_notification(info)
|
|
240
257
|
trade.open_sell_position(
|
|
241
|
-
action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
|
|
258
|
+
action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
|
|
242
259
|
else:
|
|
243
260
|
logger.info(tfmsg)
|
|
244
261
|
check(buys[symbol], sells[symbol], symbol)
|
|
@@ -247,9 +264,11 @@ def _mt5_execution(
|
|
|
247
264
|
if notify:
|
|
248
265
|
_send_notification(info)
|
|
249
266
|
trade.open_sell_position(
|
|
250
|
-
action=signal, price=price, stoplimit=stoplimit, mm=mm, comment=comment)
|
|
251
|
-
elif signal in SELLS
|
|
252
|
-
|
|
267
|
+
action=signal, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
|
|
268
|
+
elif signal in SELLS:
|
|
269
|
+
if check_max_trades:
|
|
270
|
+
if short_market[symbol]:
|
|
271
|
+
logger.info(riskmsg)
|
|
253
272
|
check(buys[symbol], sells[symbol], symbol)
|
|
254
273
|
else:
|
|
255
274
|
check(buys[symbol], sells[symbol], symbol)
|
|
@@ -288,7 +307,8 @@ def _mt5_execution(
|
|
|
288
307
|
for symbol in symbols:
|
|
289
308
|
trade = trades_instances[symbol]
|
|
290
309
|
if trade.days_end() and closing:
|
|
291
|
-
|
|
310
|
+
for id in expert_ids:
|
|
311
|
+
trade.close_positions(position_type='all', id=id, comment=comment)
|
|
292
312
|
logmsg("Day")
|
|
293
313
|
trade.statistics(save=True)
|
|
294
314
|
if day_end:
|
|
@@ -312,7 +332,8 @@ def _mt5_execution(
|
|
|
312
332
|
logmsg("Day")
|
|
313
333
|
|
|
314
334
|
elif trade.days_end() and today == FRIDAY and closing:
|
|
315
|
-
|
|
335
|
+
for id in expert_ids:
|
|
336
|
+
trade.close_positions(position_type='all', id=id, comment=comment)
|
|
316
337
|
logmsg("Week")
|
|
317
338
|
trade.statistics(save=True)
|
|
318
339
|
if day_end and today != FRIDAY:
|
|
@@ -341,7 +362,8 @@ def _mt5_execution(
|
|
|
341
362
|
and today == FRIDAY
|
|
342
363
|
and num_days/len(symbols) >= 20
|
|
343
364
|
) and closing:
|
|
344
|
-
|
|
365
|
+
for id in expert_ids:
|
|
366
|
+
trade.close_positions(position_type='all', id=id, comment=comment)
|
|
345
367
|
logmsg("Month")
|
|
346
368
|
trade.statistics(save=True)
|
|
347
369
|
if day_end and today != FRIDAY:
|
bbstrader/trading/strategies.py
CHANGED
|
@@ -404,7 +404,7 @@ class KalmanFilterStrategy(Strategy):
|
|
|
404
404
|
|
|
405
405
|
self.hmm_tiker = kwargs.get("hmm_tiker")
|
|
406
406
|
self._assert_tikers()
|
|
407
|
-
self.account = Account()
|
|
407
|
+
self.account = Account(**kwargs)
|
|
408
408
|
self.hmm_window = kwargs.get("hmm_window", 50)
|
|
409
409
|
self.qty = kwargs.get("quantity", 100)
|
|
410
410
|
self.tf = kwargs.get("time_frame", "D1")
|