bbstrader 0.2.98__py3-none-any.whl → 0.2.991__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/__init__.py +19 -0
- bbstrader/btengine/backtest.py +7 -7
- bbstrader/btengine/event.py +27 -18
- bbstrader/btengine/execution.py +3 -3
- bbstrader/btengine/portfolio.py +3 -3
- bbstrader/btengine/strategy.py +8 -2
- bbstrader/core/data.py +98 -2
- bbstrader/metatrader/account.py +1 -1
- bbstrader/metatrader/analysis.py +98 -0
- bbstrader/metatrader/copier.py +71 -46
- bbstrader/metatrader/trade.py +47 -55
- bbstrader/models/factors.py +97 -97
- bbstrader/models/nlp.py +9 -2
- bbstrader/trading/execution.py +144 -157
- bbstrader/trading/strategies.py +13 -5
- {bbstrader-0.2.98.dist-info → bbstrader-0.2.991.dist-info}/METADATA +1 -3
- {bbstrader-0.2.98.dist-info → bbstrader-0.2.991.dist-info}/RECORD +21 -19
- {bbstrader-0.2.98.dist-info → bbstrader-0.2.991.dist-info}/WHEEL +1 -1
- {bbstrader-0.2.98.dist-info → bbstrader-0.2.991.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.2.98.dist-info → bbstrader-0.2.991.dist-info}/licenses/LICENSE +0 -0
- {bbstrader-0.2.98.dist-info → bbstrader-0.2.991.dist-info}/top_level.txt +0 -0
bbstrader/metatrader/trade.py
CHANGED
|
@@ -150,6 +150,7 @@ Orders = Literal[
|
|
|
150
150
|
"sell_stop_limits",
|
|
151
151
|
]
|
|
152
152
|
|
|
153
|
+
EXPERT_ID = 98181105
|
|
153
154
|
|
|
154
155
|
class Trade(RiskManagement):
|
|
155
156
|
"""
|
|
@@ -209,7 +210,7 @@ class Trade(RiskManagement):
|
|
|
209
210
|
self,
|
|
210
211
|
symbol: str = "EURUSD",
|
|
211
212
|
expert_name: str = "bbstrader",
|
|
212
|
-
expert_id: int =
|
|
213
|
+
expert_id: int = EXPERT_ID,
|
|
213
214
|
version: str = "2.0",
|
|
214
215
|
target: float = 5.0,
|
|
215
216
|
start_time: str = "0:00",
|
|
@@ -376,21 +377,18 @@ class Trade(RiskManagement):
|
|
|
376
377
|
|
|
377
378
|
def summary(self):
|
|
378
379
|
"""Show a brief description about the trading program"""
|
|
380
|
+
start = datetime.strptime(self.start, "%H:%M").time()
|
|
381
|
+
finish = datetime.strptime(self.finishing, "%H:%M").time()
|
|
382
|
+
end = datetime.strptime(self.end, "%H:%M").time()
|
|
379
383
|
summary_data = [
|
|
380
384
|
["Expert Advisor Name", f"@{self.expert_name}"],
|
|
381
385
|
["Expert Advisor Version", f"@{self.version}"],
|
|
382
386
|
["Expert | Strategy ID", self.expert_id],
|
|
383
387
|
["Trading Symbol", self.symbol],
|
|
384
388
|
["Trading Time Frame", self.tf],
|
|
385
|
-
["Start Trading Time", f"{
|
|
386
|
-
[
|
|
387
|
-
|
|
388
|
-
f"{self.finishing_time_hour}:{self.finishing_time_minutes}",
|
|
389
|
-
],
|
|
390
|
-
[
|
|
391
|
-
"Closing Position After",
|
|
392
|
-
f"{self.ending_time_hour}:{self.ending_time_minutes}",
|
|
393
|
-
],
|
|
389
|
+
["Start Trading Time", f"{start}"],
|
|
390
|
+
["Finishing Trading Time", f"{finish}"],
|
|
391
|
+
["Closing Position After", f"{end}"],
|
|
394
392
|
]
|
|
395
393
|
# Custom table format
|
|
396
394
|
summary_table = tabulate(
|
|
@@ -520,7 +518,7 @@ class Trade(RiskManagement):
|
|
|
520
518
|
tp: Optional[float] = None,
|
|
521
519
|
):
|
|
522
520
|
"""
|
|
523
|
-
Open a Buy
|
|
521
|
+
Open a Buy position
|
|
524
522
|
|
|
525
523
|
Args:
|
|
526
524
|
action (str): `BMKT` for Market orders or `BLMT`,
|
|
@@ -533,6 +531,9 @@ class Trade(RiskManagement):
|
|
|
533
531
|
mm (bool): Weither to put stop loss and tp or not
|
|
534
532
|
trail (bool): Weither to trail the stop loss or not
|
|
535
533
|
comment (str): The comment for the opening position
|
|
534
|
+
volume (float): The volume (lot) to trade
|
|
535
|
+
sl (float): The stop loss price
|
|
536
|
+
tp (float): The take profit price
|
|
536
537
|
"""
|
|
537
538
|
Id = id if id is not None else self.expert_id
|
|
538
539
|
point = self.get_symbol_info(self.symbol).point
|
|
@@ -582,7 +583,7 @@ class Trade(RiskManagement):
|
|
|
582
583
|
return False
|
|
583
584
|
|
|
584
585
|
def _order_type(self):
|
|
585
|
-
|
|
586
|
+
return {
|
|
586
587
|
"BMKT": (Mt5.ORDER_TYPE_BUY, "BUY"),
|
|
587
588
|
"SMKT": (Mt5.ORDER_TYPE_BUY, "SELL"),
|
|
588
589
|
"BLMT": (Mt5.ORDER_TYPE_BUY_LIMIT, "BUY_LIMIT"),
|
|
@@ -592,7 +593,6 @@ class Trade(RiskManagement):
|
|
|
592
593
|
"BSTPLMT": (Mt5.ORDER_TYPE_BUY_STOP_LIMIT, "BUY_STOP_LIMIT"),
|
|
593
594
|
"SSTPLMT": (Mt5.ORDER_TYPE_SELL_STOP_LIMIT, "SELL_STOP_LIMIT"),
|
|
594
595
|
}
|
|
595
|
-
return type
|
|
596
596
|
|
|
597
597
|
def open_sell_position(
|
|
598
598
|
self,
|
|
@@ -609,7 +609,7 @@ class Trade(RiskManagement):
|
|
|
609
609
|
tp: Optional[float] = None,
|
|
610
610
|
):
|
|
611
611
|
"""
|
|
612
|
-
Open a sell
|
|
612
|
+
Open a sell position
|
|
613
613
|
|
|
614
614
|
Args:
|
|
615
615
|
action (str): `SMKT` for Market orders
|
|
@@ -622,10 +622,9 @@ class Trade(RiskManagement):
|
|
|
622
622
|
mm (bool): Weither to put stop loss and tp or not
|
|
623
623
|
trail (bool): Weither to trail the stop loss or not
|
|
624
624
|
comment (str): The comment for the closing position
|
|
625
|
-
symbol (str): The symbol to trade
|
|
626
625
|
volume (float): The volume (lot) to trade
|
|
627
|
-
sl (float): The stop loss
|
|
628
|
-
tp (float): The take profit
|
|
626
|
+
sl (float): The stop loss price
|
|
627
|
+
tp (float): The take profit price
|
|
629
628
|
"""
|
|
630
629
|
Id = id if id is not None else self.expert_id
|
|
631
630
|
point = self.get_symbol_info(self.symbol).point
|
|
@@ -691,11 +690,11 @@ class Trade(RiskManagement):
|
|
|
691
690
|
LOGGER.info(f"Not Trading time, SYMBOL={self.symbol}")
|
|
692
691
|
return False
|
|
693
692
|
elif not self.is_risk_ok():
|
|
694
|
-
LOGGER.
|
|
693
|
+
LOGGER.warning(f"Account Risk not allowed, SYMBOL={self.symbol}")
|
|
695
694
|
self._check(comment)
|
|
696
695
|
return False
|
|
697
696
|
elif self.is_max_trades_reached():
|
|
698
|
-
LOGGER.
|
|
697
|
+
LOGGER.warning(f"Maximum trades reached for Today, SYMBOL={self.symbol}")
|
|
699
698
|
return False
|
|
700
699
|
elif self.profit_target():
|
|
701
700
|
self._check(f"Profit target Reached !!! SYMBOL={self.symbol}")
|
|
@@ -835,7 +834,8 @@ class Trade(RiskManagement):
|
|
|
835
834
|
action (str): (`'BMKT'`, `'SMKT'`) for Market orders
|
|
836
835
|
or (`'BLMT', 'SLMT', 'BSTP', 'SSTP', 'BSTPLMT', 'SSTPLMT'`) for pending orders
|
|
837
836
|
price (float): The price at which to open an order
|
|
838
|
-
stoplimit (float): A price a pending Limit order is set at
|
|
837
|
+
stoplimit (float): A price a pending Limit order is set at
|
|
838
|
+
when the price reaches the 'price' value (this condition is mandatory).
|
|
839
839
|
The pending order is not passed to the trading system until that moment
|
|
840
840
|
id (int): The strategy id or expert Id
|
|
841
841
|
mm (bool): Weither to put stop loss and tp or not
|
|
@@ -843,43 +843,32 @@ class Trade(RiskManagement):
|
|
|
843
843
|
comment (str): The comment for the closing position
|
|
844
844
|
symbol (str): The symbol to trade
|
|
845
845
|
volume (float): The volume (lot) to trade
|
|
846
|
-
sl (float): The stop loss
|
|
847
|
-
tp (float): The take profit
|
|
846
|
+
sl (float): The stop loss price
|
|
847
|
+
tp (float): The take profit price
|
|
848
848
|
"""
|
|
849
849
|
BUYS = ["BMKT", "BLMT", "BSTP", "BSTPLMT"]
|
|
850
850
|
SELLS = ["SMKT", "SLMT", "SSTP", "SSTPLMT"]
|
|
851
851
|
if action in BUYS:
|
|
852
|
-
|
|
853
|
-
action=action,
|
|
854
|
-
price=price,
|
|
855
|
-
stoplimit=stoplimit,
|
|
856
|
-
id=id,
|
|
857
|
-
mm=mm,
|
|
858
|
-
trail=trail,
|
|
859
|
-
comment=comment,
|
|
860
|
-
symbol=symbol,
|
|
861
|
-
volume=volume,
|
|
862
|
-
sl=sl,
|
|
863
|
-
tp=tp,
|
|
864
|
-
)
|
|
852
|
+
open_position = self.open_buy_position
|
|
865
853
|
elif action in SELLS:
|
|
866
|
-
|
|
867
|
-
action=action,
|
|
868
|
-
price=price,
|
|
869
|
-
stoplimit=stoplimit,
|
|
870
|
-
id=id,
|
|
871
|
-
mm=mm,
|
|
872
|
-
trail=trail,
|
|
873
|
-
comment=comment,
|
|
874
|
-
symbol=symbol,
|
|
875
|
-
volume=volume,
|
|
876
|
-
sl=sl,
|
|
877
|
-
tp=tp,
|
|
878
|
-
)
|
|
854
|
+
open_position = self.open_sell_position
|
|
879
855
|
else:
|
|
880
856
|
raise ValueError(
|
|
881
857
|
f"Invalid action type '{action}', must be {', '.join(BUYS + SELLS)}"
|
|
882
858
|
)
|
|
859
|
+
return open_position(
|
|
860
|
+
action=action,
|
|
861
|
+
price=price,
|
|
862
|
+
stoplimit=stoplimit,
|
|
863
|
+
id=id,
|
|
864
|
+
mm=mm,
|
|
865
|
+
trail=trail,
|
|
866
|
+
comment=comment,
|
|
867
|
+
symbol=symbol,
|
|
868
|
+
volume=volume,
|
|
869
|
+
sl=sl,
|
|
870
|
+
tp=tp,
|
|
871
|
+
)
|
|
883
872
|
|
|
884
873
|
@property
|
|
885
874
|
def orders(self):
|
|
@@ -1134,9 +1123,9 @@ class Trade(RiskManagement):
|
|
|
1134
1123
|
be = self.get_break_even()
|
|
1135
1124
|
if trail_after_points is not None:
|
|
1136
1125
|
if isinstance(trail_after_points, int):
|
|
1137
|
-
assert
|
|
1138
|
-
|
|
1139
|
-
)
|
|
1126
|
+
assert (
|
|
1127
|
+
trail_after_points > be
|
|
1128
|
+
), "trail_after_points must be greater than break even or set to None"
|
|
1140
1129
|
trail_after_points = self._get_trail_after_points(trail_after_points)
|
|
1141
1130
|
if positions is not None:
|
|
1142
1131
|
for position in positions:
|
|
@@ -1225,7 +1214,8 @@ class Trade(RiskManagement):
|
|
|
1225
1214
|
Sets the break-even level for a given trading position.
|
|
1226
1215
|
|
|
1227
1216
|
Args:
|
|
1228
|
-
position (TradePosition): The trading position for which the break-even is to be set.
|
|
1217
|
+
position (TradePosition): The trading position for which the break-even is to be set.
|
|
1218
|
+
This is the value return by `mt5.positions_get()`.
|
|
1229
1219
|
be (int): The break-even level in points.
|
|
1230
1220
|
level (float): The break-even level in price, if set to None , it will be calated automaticaly.
|
|
1231
1221
|
price (float): The break-even price, if set to None , it will be calated automaticaly.
|
|
@@ -1456,7 +1446,8 @@ class Trade(RiskManagement):
|
|
|
1456
1446
|
Args:
|
|
1457
1447
|
ticket (int): Order ticket to modify (e.g TradeOrder.ticket)
|
|
1458
1448
|
price (float): The price at which to modify the order
|
|
1459
|
-
stoplimit (float): A price a pending Limit order is set at
|
|
1449
|
+
stoplimit (float): A price a pending Limit order is set at
|
|
1450
|
+
when the price reaches the 'price' value (this condition is mandatory).
|
|
1460
1451
|
The pending order is not passed to the trading system until that moment
|
|
1461
1452
|
sl (float): The stop loss in points
|
|
1462
1453
|
tp (float): The take profit in points
|
|
@@ -1606,7 +1597,8 @@ class Trade(RiskManagement):
|
|
|
1606
1597
|
):
|
|
1607
1598
|
"""
|
|
1608
1599
|
Args:
|
|
1609
|
-
order_type (str): Type of orders to close
|
|
1600
|
+
order_type (str): Type of orders to close
|
|
1601
|
+
('all', 'buy_stops', 'sell_stops', 'buy_limits', 'sell_limits', 'buy_stop_limits', 'sell_stop_limits')
|
|
1610
1602
|
id (int): The unique ID of the Expert or Strategy
|
|
1611
1603
|
comment (str): Comment for the closing position
|
|
1612
1604
|
"""
|
|
@@ -1915,7 +1907,7 @@ def create_trade_instance(
|
|
|
1915
1907
|
if ids is not None and isinstance(ids, (int, float))
|
|
1916
1908
|
else params["expert_id"]
|
|
1917
1909
|
if "expert_id" in params
|
|
1918
|
-
else
|
|
1910
|
+
else EXPERT_ID
|
|
1919
1911
|
)
|
|
1920
1912
|
params["pchange_sl"] = (
|
|
1921
1913
|
pchange_sl[symbol]
|
bbstrader/models/factors.py
CHANGED
|
@@ -16,6 +16,101 @@ __all__ = [
|
|
|
16
16
|
"search_coint_candidate_pairs",
|
|
17
17
|
]
|
|
18
18
|
|
|
19
|
+
def _download_and_process_data(source, tickers, start, end, tf, path, **kwargs):
|
|
20
|
+
"""Download and process data for a list of tickers from the specified source."""
|
|
21
|
+
data_list = []
|
|
22
|
+
for ticker in tickers:
|
|
23
|
+
try:
|
|
24
|
+
if source == "yf":
|
|
25
|
+
data = yf.download(
|
|
26
|
+
ticker,
|
|
27
|
+
start=start,
|
|
28
|
+
end=end,
|
|
29
|
+
progress=False,
|
|
30
|
+
multi_level_index=False,
|
|
31
|
+
)
|
|
32
|
+
data = data.drop(columns=["Adj Close"], axis=1)
|
|
33
|
+
elif source == "mt5":
|
|
34
|
+
start, end = pd.Timestamp(start), pd.Timestamp(end)
|
|
35
|
+
data = download_historical_data(
|
|
36
|
+
symbol=ticker,
|
|
37
|
+
timeframe=tf,
|
|
38
|
+
date_from=start,
|
|
39
|
+
date_to=end,
|
|
40
|
+
**{"path": path},
|
|
41
|
+
)
|
|
42
|
+
data = data.drop(columns=["adj_close"], axis=1)
|
|
43
|
+
elif source in ["fmp", "eodhd"]:
|
|
44
|
+
handler_class = (
|
|
45
|
+
FMPDataHandler if source == "fmp" else EODHDataHandler
|
|
46
|
+
)
|
|
47
|
+
handler = handler_class(events=None, symbol_list=[ticker], **kwargs)
|
|
48
|
+
data = handler.data[ticker]
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError(f"Invalid source: {source}")
|
|
51
|
+
|
|
52
|
+
data = data.reset_index()
|
|
53
|
+
data = data.rename(columns=str.lower)
|
|
54
|
+
data["ticker"] = ticker
|
|
55
|
+
data_list.append(data)
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
print(f"No Data found for {ticker}: {e}")
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
return pd.concat(data_list)
|
|
62
|
+
|
|
63
|
+
def _handle_date_range(start, end, window):
|
|
64
|
+
"""Handle start and end date generation."""
|
|
65
|
+
if start is None or end is None:
|
|
66
|
+
end = pd.Timestamp(datetime.now()).strftime("%Y-%m-%d")
|
|
67
|
+
start = (
|
|
68
|
+
pd.Timestamp(datetime.now())
|
|
69
|
+
- pd.DateOffset(years=window)
|
|
70
|
+
+ pd.DateOffset(days=1)
|
|
71
|
+
).strftime("%Y-%m-%d")
|
|
72
|
+
return start, end
|
|
73
|
+
|
|
74
|
+
def _period_search(start, end, securities, candidates, window, npairs):
|
|
75
|
+
if window < 3 or (pd.Timestamp(end) - pd.Timestamp(start)).days / 365 < 3:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
"The date range must be at least two (2) years for period search."
|
|
78
|
+
)
|
|
79
|
+
top_pairs = []
|
|
80
|
+
p_start = pd.Timestamp(end) - pd.DateOffset(years=1)
|
|
81
|
+
periods = pd.date_range(start=p_start, end=pd.Timestamp(end), freq="BQE")
|
|
82
|
+
npairs = max(round(npairs / 2), 1)
|
|
83
|
+
for period in periods:
|
|
84
|
+
s_start = period - pd.DateOffset(years=2) + pd.DateOffset(days=1)
|
|
85
|
+
print(f"Searching for pairs in period: {s_start} - {period}")
|
|
86
|
+
pairs = find_cointegrated_pairs(
|
|
87
|
+
securities,
|
|
88
|
+
candidates,
|
|
89
|
+
n=npairs,
|
|
90
|
+
start=str(s_start),
|
|
91
|
+
stop=str(period),
|
|
92
|
+
coint=True,
|
|
93
|
+
)
|
|
94
|
+
pairs["period"] = period
|
|
95
|
+
top_pairs.append(pairs)
|
|
96
|
+
top_pairs = pd.concat(top_pairs)
|
|
97
|
+
if len(top_pairs.columns) <= 1:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
"No pairs found in the specified period."
|
|
100
|
+
"Please adjust the date range or increase the number of pairs."
|
|
101
|
+
)
|
|
102
|
+
return top_pairs.head(npairs * 2)
|
|
103
|
+
|
|
104
|
+
def _process_asset_data(securities, candidates, universe, rolling_window):
|
|
105
|
+
"""Process and select assets from the data."""
|
|
106
|
+
securities = select_assets(
|
|
107
|
+
securities, n=universe, rolling_window=rolling_window
|
|
108
|
+
)
|
|
109
|
+
candidates = select_assets(
|
|
110
|
+
candidates, n=universe, rolling_window=rolling_window
|
|
111
|
+
)
|
|
112
|
+
return securities, candidates
|
|
113
|
+
|
|
19
114
|
|
|
20
115
|
def search_coint_candidate_pairs(
|
|
21
116
|
securities: pd.DataFrame | List[str] = None,
|
|
@@ -145,101 +240,6 @@ def search_coint_candidate_pairs(
|
|
|
145
240
|
|
|
146
241
|
"""
|
|
147
242
|
|
|
148
|
-
def _download_and_process_data(source, tickers, start, end, tf, path, **kwargs):
|
|
149
|
-
"""Download and process data for a list of tickers from the specified source."""
|
|
150
|
-
data_list = []
|
|
151
|
-
for ticker in tickers:
|
|
152
|
-
try:
|
|
153
|
-
if source == "yf":
|
|
154
|
-
data = yf.download(
|
|
155
|
-
ticker,
|
|
156
|
-
start=start,
|
|
157
|
-
end=end,
|
|
158
|
-
progress=False,
|
|
159
|
-
multi_level_index=False,
|
|
160
|
-
)
|
|
161
|
-
data = data.drop(columns=["Adj Close"], axis=1)
|
|
162
|
-
elif source == "mt5":
|
|
163
|
-
start, end = pd.Timestamp(start), pd.Timestamp(end)
|
|
164
|
-
data = download_historical_data(
|
|
165
|
-
symbol=ticker,
|
|
166
|
-
timeframe=tf,
|
|
167
|
-
date_from=start,
|
|
168
|
-
date_to=end,
|
|
169
|
-
**{"path": path},
|
|
170
|
-
)
|
|
171
|
-
data = data.drop(columns=["adj_close"], axis=1)
|
|
172
|
-
elif source in ["fmp", "eodhd"]:
|
|
173
|
-
handler_class = (
|
|
174
|
-
FMPDataHandler if source == "fmp" else EODHDataHandler
|
|
175
|
-
)
|
|
176
|
-
handler = handler_class(events=None, symbol_list=[ticker], **kwargs)
|
|
177
|
-
data = handler.data[ticker]
|
|
178
|
-
else:
|
|
179
|
-
raise ValueError(f"Invalid source: {source}")
|
|
180
|
-
|
|
181
|
-
data = data.reset_index()
|
|
182
|
-
data = data.rename(columns=str.lower)
|
|
183
|
-
data["ticker"] = ticker
|
|
184
|
-
data_list.append(data)
|
|
185
|
-
|
|
186
|
-
except Exception as e:
|
|
187
|
-
print(f"No Data found for {ticker}: {e}")
|
|
188
|
-
continue
|
|
189
|
-
|
|
190
|
-
return pd.concat(data_list)
|
|
191
|
-
|
|
192
|
-
def _handle_date_range(start, end, window):
|
|
193
|
-
"""Handle start and end date generation."""
|
|
194
|
-
if start is None or end is None:
|
|
195
|
-
end = pd.Timestamp(datetime.now()).strftime("%Y-%m-%d")
|
|
196
|
-
start = (
|
|
197
|
-
pd.Timestamp(datetime.now())
|
|
198
|
-
- pd.DateOffset(years=window)
|
|
199
|
-
+ pd.DateOffset(days=1)
|
|
200
|
-
).strftime("%Y-%m-%d")
|
|
201
|
-
return start, end
|
|
202
|
-
|
|
203
|
-
def _period_search(start, end, securities, candidates, npairs=npairs):
|
|
204
|
-
if window < 3 or (pd.Timestamp(end) - pd.Timestamp(start)).days / 365 < 3:
|
|
205
|
-
raise ValueError(
|
|
206
|
-
"The date range must be at least two (2) years for period search."
|
|
207
|
-
)
|
|
208
|
-
top_pairs = []
|
|
209
|
-
p_start = pd.Timestamp(end) - pd.DateOffset(years=1)
|
|
210
|
-
periods = pd.date_range(start=p_start, end=pd.Timestamp(end), freq="BQE")
|
|
211
|
-
npairs = max(round(npairs / 2), 1)
|
|
212
|
-
for period in periods:
|
|
213
|
-
s_start = period - pd.DateOffset(years=2) + pd.DateOffset(days=1)
|
|
214
|
-
print(f"Searching for pairs in period: {s_start} - {period}")
|
|
215
|
-
pairs = find_cointegrated_pairs(
|
|
216
|
-
securities,
|
|
217
|
-
candidates,
|
|
218
|
-
n=npairs,
|
|
219
|
-
start=str(s_start),
|
|
220
|
-
stop=str(period),
|
|
221
|
-
coint=True,
|
|
222
|
-
)
|
|
223
|
-
pairs["period"] = period
|
|
224
|
-
top_pairs.append(pairs)
|
|
225
|
-
top_pairs = pd.concat(top_pairs)
|
|
226
|
-
if len(top_pairs.columns) <= 1:
|
|
227
|
-
raise ValueError(
|
|
228
|
-
"No pairs found in the specified period."
|
|
229
|
-
"Please adjust the date range or increase the number of pairs."
|
|
230
|
-
)
|
|
231
|
-
return top_pairs.head(npairs * 2)
|
|
232
|
-
|
|
233
|
-
def _process_asset_data(securities, candidates, universe, rolling_window):
|
|
234
|
-
"""Process and select assets from the data."""
|
|
235
|
-
securities = select_assets(
|
|
236
|
-
securities, n=universe, rolling_window=rolling_window
|
|
237
|
-
)
|
|
238
|
-
candidates = select_assets(
|
|
239
|
-
candidates, n=universe, rolling_window=rolling_window
|
|
240
|
-
)
|
|
241
|
-
return securities, candidates
|
|
242
|
-
|
|
243
243
|
if (
|
|
244
244
|
securities is not None
|
|
245
245
|
and candidates is not None
|
|
@@ -255,7 +255,7 @@ def search_coint_candidate_pairs(
|
|
|
255
255
|
if period_search:
|
|
256
256
|
start = securities.index.get_level_values("date").min()
|
|
257
257
|
end = securities.index.get_level_values("date").max()
|
|
258
|
-
top_pairs = _period_search(start, end, securities, candidates)
|
|
258
|
+
top_pairs = _period_search(start, end, securities, candidates, window, npairs)
|
|
259
259
|
else:
|
|
260
260
|
top_pairs = find_cointegrated_pairs(
|
|
261
261
|
securities, candidates, n=npairs, coint=True
|
|
@@ -291,7 +291,7 @@ def search_coint_candidate_pairs(
|
|
|
291
291
|
)
|
|
292
292
|
if period_search:
|
|
293
293
|
top_pairs = _period_search(
|
|
294
|
-
start, end, securities_data, candidates_data
|
|
294
|
+
start, end, securities_data, candidates_data, window, npairs
|
|
295
295
|
).head(npairs)
|
|
296
296
|
else:
|
|
297
297
|
top_pairs = find_cointegrated_pairs(
|
bbstrader/models/nlp.py
CHANGED
|
@@ -506,6 +506,7 @@ class SentimentAnalyzer(object):
|
|
|
506
506
|
reddit_posts = news.get_reddit_posts(
|
|
507
507
|
ticker, n_posts=top_news, **{k: kwargs.get(k) for k in rd_params}
|
|
508
508
|
)
|
|
509
|
+
coindesk_news = news.get_coindesk_news(query=ticker, list_of_str=True)
|
|
509
510
|
fmp_source_news = []
|
|
510
511
|
fmp_news = news.get_fmp_news(kwargs.get("fmp_api"))
|
|
511
512
|
for source in ["articles"]: # , "releases", asset_type]:
|
|
@@ -518,7 +519,7 @@ class SentimentAnalyzer(object):
|
|
|
518
519
|
source_news = []
|
|
519
520
|
if any([len(s) > 0 for s in [yahoo_news, google_news]]):
|
|
520
521
|
sources += 1
|
|
521
|
-
for source in [reddit_posts, fmp_source_news]:
|
|
522
|
+
for source in [reddit_posts, fmp_source_news, coindesk_news]:
|
|
522
523
|
if len(source) > 0:
|
|
523
524
|
sources += 1
|
|
524
525
|
# Compute sentiment
|
|
@@ -531,11 +532,17 @@ class SentimentAnalyzer(object):
|
|
|
531
532
|
fmp_sentiment = self.analyze_sentiment(
|
|
532
533
|
fmp_source_news, lexicon=lexicon, textblob=True
|
|
533
534
|
)
|
|
535
|
+
coindesk_sentiment = self.analyze_sentiment(
|
|
536
|
+
coindesk_news, lexicon=lexicon, textblob=True
|
|
537
|
+
)
|
|
534
538
|
|
|
535
539
|
# Weighted average sentiment score
|
|
536
540
|
if sources != 0:
|
|
537
541
|
overall_sentiment = (
|
|
538
|
-
news_sentiment
|
|
542
|
+
news_sentiment
|
|
543
|
+
+ reddit_sentiment
|
|
544
|
+
+ fmp_sentiment
|
|
545
|
+
+ coindesk_sentiment
|
|
539
546
|
) / sources
|
|
540
547
|
else:
|
|
541
548
|
overall_sentiment = 0.0
|