bbstrader 0.2.98__tar.gz → 0.2.991__tar.gz
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-0.2.98/bbstrader.egg-info → bbstrader-0.2.991}/PKG-INFO +1 -3
- {bbstrader-0.2.98 → bbstrader-0.2.991}/README.md +0 -2
- bbstrader-0.2.991/bbstrader/__init__.py +19 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/backtest.py +7 -7
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/event.py +27 -18
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/execution.py +3 -3
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/portfolio.py +3 -3
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/strategy.py +8 -2
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/core/data.py +98 -2
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/account.py +1 -1
- bbstrader-0.2.991/bbstrader/metatrader/analysis.py +98 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/copier.py +71 -46
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/trade.py +47 -55
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/factors.py +97 -97
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/nlp.py +9 -2
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/trading/execution.py +144 -157
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/trading/strategies.py +13 -5
- {bbstrader-0.2.98 → bbstrader-0.2.991/bbstrader.egg-info}/PKG-INFO +1 -3
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader.egg-info/SOURCES.txt +2 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/setup.py +1 -1
- {bbstrader-0.2.98 → bbstrader-0.2.991}/LICENSE +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/MANIFEST.in +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/__ini__.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/__main__.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/__init__.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/data.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/performance.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/scripts.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/compat.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/config.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/core/__init__.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/core/utils.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/ibkr/__init__.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/ibkr/utils.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/__init__.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/rates.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/risk.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/scripts.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/utils.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/__init__.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/ml.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/optimization.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/portfolio.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/risk.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/trading/__init__.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/trading/scripts.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/trading/utils.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/tseries.py +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader.egg-info/dependency_links.txt +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader.egg-info/entry_points.txt +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader.egg-info/requires.txt +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader.egg-info/top_level.txt +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/requirements.txt +0 -0
- {bbstrader-0.2.98 → bbstrader-0.2.991}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bbstrader
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.991
|
|
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/
|
|
@@ -81,8 +81,6 @@ Dynamic: requires-dist
|
|
|
81
81
|
Dynamic: summary
|
|
82
82
|
|
|
83
83
|
# Simplified Investment & Trading Toolkit
|
|
84
|
-

|
|
85
|
-
|
|
86
84
|
[](https://bbstrader.readthedocs.io/en/latest/?badge=latest)
|
|
87
85
|
[](https://pypi.org/project/bbstrader/)
|
|
88
86
|
[](https://pypi.python.org/pypi/bbstrader)
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
# Simplified Investment & Trading Toolkit
|
|
2
|
-

|
|
3
|
-
|
|
4
2
|
[](https://bbstrader.readthedocs.io/en/latest/?badge=latest)
|
|
5
3
|
[](https://pypi.org/project/bbstrader/)
|
|
6
4
|
[](https://pypi.python.org/pypi/bbstrader)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simplified Investment & Trading Toolkit
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__author__ = "Bertin Balouki SIMYELI"
|
|
7
|
+
__copyright__ = "2023-2025 Bertin Balouki SIMYELI"
|
|
8
|
+
__email__ = "bertin@bbstrader.com"
|
|
9
|
+
__license__ = "MIT"
|
|
10
|
+
__version__ = "0.2.0"
|
|
11
|
+
|
|
12
|
+
from bbstrader import compat # noqa: F401
|
|
13
|
+
from bbstrader import core # noqa: F401
|
|
14
|
+
from bbstrader import btengine # noqa: F401
|
|
15
|
+
from bbstrader import metatrader # noqa: F401
|
|
16
|
+
from bbstrader import models # noqa: F401
|
|
17
|
+
from bbstrader import trading # noqa: F401
|
|
18
|
+
from bbstrader import tseries # noqa: F401
|
|
19
|
+
from bbstrader.config import config_logger # noqa: F401
|
|
@@ -4,7 +4,7 @@ from datetime import datetime
|
|
|
4
4
|
from typing import List, Literal, Optional
|
|
5
5
|
|
|
6
6
|
from tabulate import tabulate
|
|
7
|
-
|
|
7
|
+
from bbstrader.btengine.event import Events
|
|
8
8
|
from bbstrader.btengine.data import DataHandler
|
|
9
9
|
from bbstrader.btengine.execution import ExecutionHandler, SimExecutionHandler
|
|
10
10
|
from bbstrader.btengine.portfolio import Portfolio
|
|
@@ -165,19 +165,19 @@ class BacktestEngine(Backtest):
|
|
|
165
165
|
break
|
|
166
166
|
else:
|
|
167
167
|
if event is not None:
|
|
168
|
-
if event.type ==
|
|
168
|
+
if event.type == Events.MARKET:
|
|
169
169
|
self.strategy.calculate_signals(event)
|
|
170
170
|
self.portfolio.update_timeindex(event)
|
|
171
171
|
|
|
172
|
-
elif event.type ==
|
|
172
|
+
elif event.type == Events.SIGNAL:
|
|
173
173
|
self.signals += 1
|
|
174
174
|
self.portfolio.update_signal(event)
|
|
175
175
|
|
|
176
|
-
elif event.type ==
|
|
176
|
+
elif event.type == Events.ORDER:
|
|
177
177
|
self.orders += 1
|
|
178
178
|
self.execution_handler.execute_order(event)
|
|
179
179
|
|
|
180
|
-
elif event.type ==
|
|
180
|
+
elif event.type == Events.FILL:
|
|
181
181
|
self.fills += 1
|
|
182
182
|
self.portfolio.update_fill(event)
|
|
183
183
|
self.strategy.update_trades_from_fill(event)
|
|
@@ -354,7 +354,7 @@ def run_backtest_with(engine: Literal["bbstrader", "cerebro", "zipline"], **kwar
|
|
|
354
354
|
)
|
|
355
355
|
elif engine == "cerebro":
|
|
356
356
|
# TODO:
|
|
357
|
-
|
|
357
|
+
raise NotImplementedError("cerebro engine is not supported yet")
|
|
358
358
|
elif engine == "zipline":
|
|
359
359
|
# TODO:
|
|
360
|
-
|
|
360
|
+
raise NotImplementedError("zipline engine is not supported yet")
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
+
from enum import Enum
|
|
2
3
|
from typing import Literal
|
|
3
4
|
|
|
4
|
-
__all__ = ["Event", "MarketEvent", "SignalEvent", "OrderEvent", "FillEvent"]
|
|
5
|
+
__all__ = ["Event", "Events", "MarketEvent", "SignalEvent", "OrderEvent", "FillEvent"]
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class Event(object):
|
|
@@ -18,6 +19,13 @@ class Event(object):
|
|
|
18
19
|
...
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
class Events(Enum):
|
|
23
|
+
MARKET = "MARKET"
|
|
24
|
+
SIGNAL = "SIGNAL"
|
|
25
|
+
ORDER = "ORDER"
|
|
26
|
+
FILL = "FILL"
|
|
27
|
+
|
|
28
|
+
|
|
21
29
|
class MarketEvent(Event):
|
|
22
30
|
"""
|
|
23
31
|
Market Events are triggered when the outer while loop of the backtesting
|
|
@@ -32,7 +40,7 @@ class MarketEvent(Event):
|
|
|
32
40
|
"""
|
|
33
41
|
Initialises the MarketEvent.
|
|
34
42
|
"""
|
|
35
|
-
self.type =
|
|
43
|
+
self.type = Events.MARKET
|
|
36
44
|
|
|
37
45
|
|
|
38
46
|
class SignalEvent(Event):
|
|
@@ -72,7 +80,7 @@ class SignalEvent(Event):
|
|
|
72
80
|
price (int | float): An optional price to be used when the signal is generated.
|
|
73
81
|
stoplimit (int | float): An optional stop-limit price for the signal
|
|
74
82
|
"""
|
|
75
|
-
self.type =
|
|
83
|
+
self.type = Events.SIGNAL
|
|
76
84
|
self.strategy_id = strategy_id
|
|
77
85
|
self.symbol = symbol
|
|
78
86
|
self.datetime = datetime
|
|
@@ -118,7 +126,7 @@ class OrderEvent(Event):
|
|
|
118
126
|
price (int | float): The price at which to order.
|
|
119
127
|
signal (str): The signal that generated the order.
|
|
120
128
|
"""
|
|
121
|
-
self.type =
|
|
129
|
+
self.type = Events.ORDER
|
|
122
130
|
self.symbol = symbol
|
|
123
131
|
self.order_type = order_type
|
|
124
132
|
self.quantity = quantity
|
|
@@ -126,20 +134,21 @@ class OrderEvent(Event):
|
|
|
126
134
|
self.price = price
|
|
127
135
|
self.signal = signal
|
|
128
136
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
)
|
|
137
|
+
def print_order(self):
|
|
138
|
+
"""
|
|
139
|
+
Outputs the values within the Order.
|
|
140
|
+
"""
|
|
141
|
+
print(
|
|
142
|
+
"Order: Symbol=%s, Type=%s, Quantity=%s, Direction=%s, Price=%s"
|
|
143
|
+
% (
|
|
144
|
+
self.symbol,
|
|
145
|
+
self.order_type,
|
|
146
|
+
self.quantity,
|
|
147
|
+
self.direction,
|
|
148
|
+
self.price,
|
|
142
149
|
)
|
|
150
|
+
)
|
|
151
|
+
|
|
143
152
|
|
|
144
153
|
|
|
145
154
|
class FillEvent(Event):
|
|
@@ -191,7 +200,7 @@ class FillEvent(Event):
|
|
|
191
200
|
commission (float | None): An optional commission sent from IB.
|
|
192
201
|
order (str): The order that this fill is related
|
|
193
202
|
"""
|
|
194
|
-
self.type =
|
|
203
|
+
self.type = Events.FILL
|
|
195
204
|
self.timeindex = timeindex
|
|
196
205
|
self.symbol = symbol
|
|
197
206
|
self.exchange = exchange
|
|
@@ -4,7 +4,7 @@ from queue import Queue
|
|
|
4
4
|
from loguru import logger
|
|
5
5
|
|
|
6
6
|
from bbstrader.btengine.data import DataHandler
|
|
7
|
-
from bbstrader.btengine.event import FillEvent, OrderEvent
|
|
7
|
+
from bbstrader.btengine.event import Events, FillEvent, OrderEvent
|
|
8
8
|
from bbstrader.config import BBSTRADER_DIR
|
|
9
9
|
from bbstrader.metatrader.account import Account
|
|
10
10
|
|
|
@@ -80,7 +80,7 @@ class SimExecutionHandler(ExecutionHandler):
|
|
|
80
80
|
Args:
|
|
81
81
|
event (OrderEvent): Contains an Event object with order information.
|
|
82
82
|
"""
|
|
83
|
-
if event.type ==
|
|
83
|
+
if event.type == Events.ORDER:
|
|
84
84
|
dtime = self.bardata.get_latest_bar_datetime(event.symbol)
|
|
85
85
|
fill_event = FillEvent(
|
|
86
86
|
timeindex=dtime,
|
|
@@ -233,7 +233,7 @@ class MT5ExecutionHandler(ExecutionHandler):
|
|
|
233
233
|
Args:
|
|
234
234
|
event (OrderEvent): Contains an Event object with order information.
|
|
235
235
|
"""
|
|
236
|
-
if event.type ==
|
|
236
|
+
if event.type == Events.ORDER:
|
|
237
237
|
symbol = event.symbol
|
|
238
238
|
direction = event.direction
|
|
239
239
|
quantity = event.quantity
|
|
@@ -6,7 +6,7 @@ import pandas as pd
|
|
|
6
6
|
import quantstats as qs
|
|
7
7
|
|
|
8
8
|
from bbstrader.btengine.data import DataHandler
|
|
9
|
-
from bbstrader.btengine.event import FillEvent, MarketEvent, OrderEvent, SignalEvent
|
|
9
|
+
from bbstrader.btengine.event import Events, FillEvent, MarketEvent, OrderEvent, SignalEvent
|
|
10
10
|
from bbstrader.btengine.performance import (
|
|
11
11
|
create_drawdowns,
|
|
12
12
|
create_sharpe_ratio,
|
|
@@ -282,7 +282,7 @@ class Portfolio(object):
|
|
|
282
282
|
Updates the portfolio current positions and holdings
|
|
283
283
|
from a FillEvent.
|
|
284
284
|
"""
|
|
285
|
-
if event.type ==
|
|
285
|
+
if event.type == Events.FILL:
|
|
286
286
|
self.update_positions_from_fill(event)
|
|
287
287
|
self.update_holdings_from_fill(event)
|
|
288
288
|
|
|
@@ -337,7 +337,7 @@ class Portfolio(object):
|
|
|
337
337
|
Acts on a SignalEvent to generate new orders
|
|
338
338
|
based on the portfolio logic.
|
|
339
339
|
"""
|
|
340
|
-
if event.type ==
|
|
340
|
+
if event.type == Events.SIGNAL:
|
|
341
341
|
order_event = self.generate_order(event)
|
|
342
342
|
self.events.put(order_event)
|
|
343
343
|
|
|
@@ -10,7 +10,7 @@ import pytz
|
|
|
10
10
|
from loguru import logger
|
|
11
11
|
|
|
12
12
|
from bbstrader.btengine.data import DataHandler
|
|
13
|
-
from bbstrader.btengine.event import FillEvent, SignalEvent
|
|
13
|
+
from bbstrader.btengine.event import Events, FillEvent, SignalEvent
|
|
14
14
|
from bbstrader.config import BBSTRADER_DIR
|
|
15
15
|
from bbstrader.metatrader.account import (
|
|
16
16
|
Account,
|
|
@@ -188,7 +188,7 @@ class MT5Strategy(Strategy):
|
|
|
188
188
|
This method updates the trades for the strategy based on the fill event.
|
|
189
189
|
It is used to keep track of the number of trades executed for each order.
|
|
190
190
|
"""
|
|
191
|
-
if event.type ==
|
|
191
|
+
if event.type == Events.FILL:
|
|
192
192
|
if event.order != "EXIT":
|
|
193
193
|
self._trades[event.symbol][event.order] += 1
|
|
194
194
|
elif event.order == "EXIT" and event.direction == "BUY":
|
|
@@ -678,6 +678,12 @@ class MT5Strategy(Strategy):
|
|
|
678
678
|
if period_count == 0 or period_count is None:
|
|
679
679
|
return True
|
|
680
680
|
return period_count % signal_inverval == 0
|
|
681
|
+
|
|
682
|
+
@staticmethod
|
|
683
|
+
def stop_time(time_zone: str, stop_time: str) -> bool:
|
|
684
|
+
now = datetime.now(pytz.timezone(time_zone)).time()
|
|
685
|
+
stop_time = datetime.strptime(stop_time, "%H:%M").time()
|
|
686
|
+
return now >= stop_time
|
|
681
687
|
|
|
682
688
|
def ispositions(
|
|
683
689
|
self, symbol, strategy_id, position, max_trades, one_true=False, account=None
|
|
@@ -2,7 +2,7 @@ import json
|
|
|
2
2
|
import re
|
|
3
3
|
import ssl
|
|
4
4
|
from datetime import datetime
|
|
5
|
-
from typing import List
|
|
5
|
+
from typing import List, Literal
|
|
6
6
|
from urllib.request import urlopen
|
|
7
7
|
|
|
8
8
|
import certifi
|
|
@@ -18,7 +18,7 @@ __all__ = ["FmpData", "FmpNews", "FinancialNews"]
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def _get_search_query(query: str) -> str:
|
|
21
|
-
if " " in query:
|
|
21
|
+
if " " in query or query == "":
|
|
22
22
|
return query
|
|
23
23
|
try:
|
|
24
24
|
name = yf.Ticker(query).info["shortName"]
|
|
@@ -422,6 +422,102 @@ class FinancialNews(object):
|
|
|
422
422
|
def get_fmp_news(self, api=None) -> FmpNews:
|
|
423
423
|
return FmpNews(api=api)
|
|
424
424
|
|
|
425
|
+
def get_coindesk_news(
|
|
426
|
+
self,
|
|
427
|
+
query="",
|
|
428
|
+
lang: Literal["EN", "ES", "TR", "FR", "JP", "PT"] = "EN",
|
|
429
|
+
limit=50,
|
|
430
|
+
list_of_str=False,
|
|
431
|
+
) -> List[str] | List[dict]:
|
|
432
|
+
"""
|
|
433
|
+
Fetches and filters recent news articles from CoinDesk's News API.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
query : str, optional
|
|
437
|
+
A search term to filter articles by title, body, or keywords.
|
|
438
|
+
If empty, all articles are returned without filtering (default is "").
|
|
439
|
+
|
|
440
|
+
lang : Literal["EN", "ES", "TR", "FR", "JP", "PT"], optional
|
|
441
|
+
Language in which to fetch news articles. Supported languages:
|
|
442
|
+
English (EN), Spanish (ES), Turkish (TR), French (FR), Japanese (JP), and Portuguese (PT).
|
|
443
|
+
Default is "EN".
|
|
444
|
+
|
|
445
|
+
limit : int, optional
|
|
446
|
+
Maximum number of articles to retrieve. Default is 50.
|
|
447
|
+
|
|
448
|
+
list_of_str : bool, optional
|
|
449
|
+
If True, returns a list of strings (concatenated article content).
|
|
450
|
+
If False, returns a list of filtered article dictionaries.
|
|
451
|
+
Default is False.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
List[str] | List[dict]
|
|
455
|
+
- If `query` is empty: returns a list of filtered article dictionaries.
|
|
456
|
+
- If `query` is provided:
|
|
457
|
+
- Returns a list of strings if `list_of_str=True`.
|
|
458
|
+
- Returns a list of filtered article dictionaries otherwise.
|
|
459
|
+
|
|
460
|
+
Each article dictionary contains the following fields:
|
|
461
|
+
- 'published_on': datetime of publication
|
|
462
|
+
- 'title': article headline
|
|
463
|
+
- 'subtitle': secondary headline
|
|
464
|
+
- 'url': direct link to the article
|
|
465
|
+
- 'body': article content
|
|
466
|
+
- 'keywords': associated tags
|
|
467
|
+
- 'sentiment': sentiment label
|
|
468
|
+
- 'status': publication status
|
|
469
|
+
|
|
470
|
+
Notes:
|
|
471
|
+
- Articles marked as sponsored are automatically excluded.
|
|
472
|
+
"""
|
|
473
|
+
maximum = 100
|
|
474
|
+
if limit > maximum:
|
|
475
|
+
raise ValueError(f"Number of total news articles allowed is {maximum}")
|
|
476
|
+
|
|
477
|
+
response = requests.get(
|
|
478
|
+
"https://data-api.coindesk.com/news/v1/article/list",
|
|
479
|
+
params={"lang": lang, "limit": limit},
|
|
480
|
+
headers={"Content-type": "application/json; charset=UTF-8"},
|
|
481
|
+
)
|
|
482
|
+
json_response = response.json()
|
|
483
|
+
articles = json_response["Data"]
|
|
484
|
+
if len(articles) == 0:
|
|
485
|
+
return []
|
|
486
|
+
to_keep = [
|
|
487
|
+
"PUBLISHED_ON",
|
|
488
|
+
"TITLE",
|
|
489
|
+
"SUBTITLE",
|
|
490
|
+
"URL",
|
|
491
|
+
"BODY",
|
|
492
|
+
"KEYWORDS",
|
|
493
|
+
"SENTIMENT",
|
|
494
|
+
"STATUS",
|
|
495
|
+
]
|
|
496
|
+
filtered_articles = []
|
|
497
|
+
for article in articles:
|
|
498
|
+
filtered_articles.append(
|
|
499
|
+
{
|
|
500
|
+
k.lower(): article[k]
|
|
501
|
+
if k in article and k != "PUBLISHED_ON"
|
|
502
|
+
else datetime.fromtimestamp(article[k])
|
|
503
|
+
for k in to_keep
|
|
504
|
+
if article[k] is not None and "sponsored" not in str(article[k])
|
|
505
|
+
}
|
|
506
|
+
)
|
|
507
|
+
if query == "" or len(filtered_articles) == 0:
|
|
508
|
+
return filtered_articles
|
|
509
|
+
to_return = []
|
|
510
|
+
query = _get_search_query(query)
|
|
511
|
+
for article in filtered_articles:
|
|
512
|
+
if not all(k in article for k in ("title", "body", "keywords")):
|
|
513
|
+
continue
|
|
514
|
+
text = article["title"] + " " + article["body"] + " " + article["keywords"]
|
|
515
|
+
if list_of_str and _find_news(query, text=text):
|
|
516
|
+
to_return.append(text)
|
|
517
|
+
if not list_of_str and _find_news(query, text=text):
|
|
518
|
+
to_return.append(article)
|
|
519
|
+
return to_return
|
|
520
|
+
|
|
425
521
|
|
|
426
522
|
class FmpData(Toolkit):
|
|
427
523
|
"""
|
|
@@ -1059,7 +1059,7 @@ class Account(object):
|
|
|
1059
1059
|
if book is None:
|
|
1060
1060
|
return None
|
|
1061
1061
|
else:
|
|
1062
|
-
return
|
|
1062
|
+
return tuple([BookInfo(**entry._asdict()) for entry in book])
|
|
1063
1063
|
except Exception as e:
|
|
1064
1064
|
raise_mt5_error(e)
|
|
1065
1065
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
import MetaTrader5 as mt5
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import seaborn as sns
|
|
6
|
+
|
|
7
|
+
from bbstrader.metatrader.account import check_mt5_connection
|
|
8
|
+
from bbstrader.metatrader.utils import TIMEFRAMES
|
|
9
|
+
|
|
10
|
+
sns.set_theme()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _get_data(path, symbol, timeframe, bars):
|
|
14
|
+
check_mt5_connection(path=path)
|
|
15
|
+
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, bars)
|
|
16
|
+
df = pd.DataFrame(rates)
|
|
17
|
+
df["time"] = pd.to_datetime(df["time"], unit="s")
|
|
18
|
+
return df
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def volume_profile(df, bins):
|
|
22
|
+
prices = (df["high"] + df["low"]) / 2
|
|
23
|
+
volumes = df["tick_volume"]
|
|
24
|
+
hist, bin_edges = np.histogram(prices, bins=bins, weights=volumes)
|
|
25
|
+
bin_centers = 0.5 * (bin_edges[:-1] + bin_edges[1:])
|
|
26
|
+
return hist, bin_edges, bin_centers
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def value_area(hist, bin_centers, percentage):
|
|
30
|
+
total_volume = np.sum(hist)
|
|
31
|
+
poc_index = np.argmax(hist)
|
|
32
|
+
poc = bin_centers[poc_index]
|
|
33
|
+
|
|
34
|
+
sorted_indices = np.argsort(hist)[::-1]
|
|
35
|
+
volume_accum = 0
|
|
36
|
+
value_area_indices = []
|
|
37
|
+
|
|
38
|
+
for idx in sorted_indices:
|
|
39
|
+
volume_accum += hist[idx]
|
|
40
|
+
value_area_indices.append(idx)
|
|
41
|
+
if volume_accum >= percentage * total_volume:
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
vah = max(bin_centers[i] for i in value_area_indices)
|
|
45
|
+
val = min(bin_centers[i] for i in value_area_indices)
|
|
46
|
+
return poc, vah, val
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def display_volume_profile(
|
|
50
|
+
symbol,
|
|
51
|
+
path,
|
|
52
|
+
timeframe: str = "1m",
|
|
53
|
+
bars: int = 1440,
|
|
54
|
+
bins: int = 100,
|
|
55
|
+
va_percentage: float = 0.7,
|
|
56
|
+
):
|
|
57
|
+
"""
|
|
58
|
+
Display a volume profile chart for a given market symbol using historical data.
|
|
59
|
+
|
|
60
|
+
This function retrieves historical price and volume data for a given symbol and
|
|
61
|
+
plots a vertical volume profile chart showing the volume distribution across
|
|
62
|
+
price levels. It highlights key levels such as:
|
|
63
|
+
- Point of Control (POC): Price level with the highest traded volume.
|
|
64
|
+
- Value Area High (VAH): Upper bound of the value area.
|
|
65
|
+
- Value Area Low (VAL): Lower bound of the value area.
|
|
66
|
+
- Current Price: Latest bid price from MetaTrader 5.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
symbol (str): Market symbol (e.g., "AAPL", "EURUSD").
|
|
70
|
+
path (str): Path to the historical data see ``bbstrader.metatrader.account.check_mt5_connection()``.
|
|
71
|
+
timeframe (str, optional): Timeframe for each candle (default is "1m").
|
|
72
|
+
bars (int, optional): Number of historical bars to fetch (default is 1440).
|
|
73
|
+
bins (int, optional): Number of price bins for volume profile calculation (default is 100).
|
|
74
|
+
va_percentage (float, optional): Percentage of total volume to define the value area (default is 0.7).
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
None: Displays a matplotlib chart of the volume profile.
|
|
78
|
+
"""
|
|
79
|
+
df = _get_data(path, symbol, TIMEFRAMES[timeframe], bars)
|
|
80
|
+
hist, bin_edges, bin_centers = volume_profile(df, bins)
|
|
81
|
+
poc, vah, val = value_area(hist, bin_centers, va_percentage)
|
|
82
|
+
current_price = mt5.symbol_info_tick(symbol).bid
|
|
83
|
+
|
|
84
|
+
plt.figure(figsize=(6, 10))
|
|
85
|
+
plt.barh(bin_centers, hist, height=bin_centers[1] - bin_centers[0], color="skyblue")
|
|
86
|
+
plt.axhline(poc, color="red", linestyle="--", label=f"POC: {poc:.5f}")
|
|
87
|
+
plt.axhline(vah, color="green", linestyle="--", label=f"VAH: {vah:.5f}")
|
|
88
|
+
plt.axhline(val, color="orange", linestyle="--", label=f"VAL: {val:.5f}")
|
|
89
|
+
plt.axhline(
|
|
90
|
+
current_price, color="black", linestyle=":", label=f"Price: {current_price:.5f}"
|
|
91
|
+
)
|
|
92
|
+
plt.legend()
|
|
93
|
+
plt.title("Volume Profile")
|
|
94
|
+
plt.xlabel("Volume")
|
|
95
|
+
plt.ylabel("Price")
|
|
96
|
+
plt.grid(True)
|
|
97
|
+
plt.tight_layout()
|
|
98
|
+
plt.show()
|