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.

Files changed (54) hide show
  1. {bbstrader-0.2.98/bbstrader.egg-info → bbstrader-0.2.991}/PKG-INFO +1 -3
  2. {bbstrader-0.2.98 → bbstrader-0.2.991}/README.md +0 -2
  3. bbstrader-0.2.991/bbstrader/__init__.py +19 -0
  4. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/backtest.py +7 -7
  5. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/event.py +27 -18
  6. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/execution.py +3 -3
  7. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/portfolio.py +3 -3
  8. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/strategy.py +8 -2
  9. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/core/data.py +98 -2
  10. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/account.py +1 -1
  11. bbstrader-0.2.991/bbstrader/metatrader/analysis.py +98 -0
  12. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/copier.py +71 -46
  13. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/trade.py +47 -55
  14. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/factors.py +97 -97
  15. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/nlp.py +9 -2
  16. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/trading/execution.py +144 -157
  17. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/trading/strategies.py +13 -5
  18. {bbstrader-0.2.98 → bbstrader-0.2.991/bbstrader.egg-info}/PKG-INFO +1 -3
  19. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader.egg-info/SOURCES.txt +2 -0
  20. {bbstrader-0.2.98 → bbstrader-0.2.991}/setup.py +1 -1
  21. {bbstrader-0.2.98 → bbstrader-0.2.991}/LICENSE +0 -0
  22. {bbstrader-0.2.98 → bbstrader-0.2.991}/MANIFEST.in +0 -0
  23. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/__ini__.py +0 -0
  24. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/__main__.py +0 -0
  25. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/__init__.py +0 -0
  26. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/data.py +0 -0
  27. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/performance.py +0 -0
  28. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/btengine/scripts.py +0 -0
  29. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/compat.py +0 -0
  30. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/config.py +0 -0
  31. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/core/__init__.py +0 -0
  32. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/core/utils.py +0 -0
  33. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/ibkr/__init__.py +0 -0
  34. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/ibkr/utils.py +0 -0
  35. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/__init__.py +0 -0
  36. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/rates.py +0 -0
  37. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/risk.py +0 -0
  38. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/scripts.py +0 -0
  39. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/metatrader/utils.py +0 -0
  40. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/__init__.py +0 -0
  41. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/ml.py +0 -0
  42. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/optimization.py +0 -0
  43. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/portfolio.py +0 -0
  44. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/models/risk.py +0 -0
  45. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/trading/__init__.py +0 -0
  46. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/trading/scripts.py +0 -0
  47. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/trading/utils.py +0 -0
  48. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader/tseries.py +0 -0
  49. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader.egg-info/dependency_links.txt +0 -0
  50. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader.egg-info/entry_points.txt +0 -0
  51. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader.egg-info/requires.txt +0 -0
  52. {bbstrader-0.2.98 → bbstrader-0.2.991}/bbstrader.egg-info/top_level.txt +0 -0
  53. {bbstrader-0.2.98 → bbstrader-0.2.991}/requirements.txt +0 -0
  54. {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.98
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
- ![bbstrader](https://github.com/bbalouki/bbstrader/blob/main/assets/bbstrader_logo.png?raw=true)
85
-
86
84
  [![Documentation Status](https://readthedocs.org/projects/bbstrader/badge/?version=latest)](https://bbstrader.readthedocs.io/en/latest/?badge=latest)
87
85
  [![PYPI Version](https://img.shields.io/pypi/v/bbstrader)](https://pypi.org/project/bbstrader/)
88
86
  [![PyPi status](https://img.shields.io/pypi/status/bbstrader.svg?maxAge=60)](https://pypi.python.org/pypi/bbstrader)
@@ -1,6 +1,4 @@
1
1
  # Simplified Investment & Trading Toolkit
2
- ![bbstrader](https://github.com/bbalouki/bbstrader/blob/main/assets/bbstrader_logo.png?raw=true)
3
-
4
2
  [![Documentation Status](https://readthedocs.org/projects/bbstrader/badge/?version=latest)](https://bbstrader.readthedocs.io/en/latest/?badge=latest)
5
3
  [![PYPI Version](https://img.shields.io/pypi/v/bbstrader)](https://pypi.org/project/bbstrader/)
6
4
  [![PyPi status](https://img.shields.io/pypi/status/bbstrader.svg?maxAge=60)](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 == "MARKET":
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 == "SIGNAL":
172
+ elif event.type == Events.SIGNAL:
173
173
  self.signals += 1
174
174
  self.portfolio.update_signal(event)
175
175
 
176
- elif event.type == "ORDER":
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 == "FILL":
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
- pass
357
+ raise NotImplementedError("cerebro engine is not supported yet")
358
358
  elif engine == "zipline":
359
359
  # TODO:
360
- pass
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 = "MARKET"
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 = "SIGNAL"
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 = "ORDER"
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
- def print_order(self):
130
- """
131
- Outputs the values within the Order.
132
- """
133
- print(
134
- "Order: Symbol=%s, Type=%s, Quantity=%s, Direction=%s, Price=%s"
135
- % (
136
- self.symbol,
137
- self.order_type,
138
- self.quantity,
139
- self.direction,
140
- self.price,
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 = "FILL"
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 == "ORDER":
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 == "ORDER":
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 == "FILL":
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 == "SIGNAL":
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 == "FILL":
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 Tuple([BookInfo(**entry._asdict()) for entry in book])
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()