bbstrader 0.3.5__py3-none-any.whl → 0.3.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.

Files changed (45) hide show
  1. bbstrader/__init__.py +11 -2
  2. bbstrader/__main__.py +6 -1
  3. bbstrader/apps/_copier.py +43 -40
  4. bbstrader/btengine/backtest.py +33 -28
  5. bbstrader/btengine/data.py +105 -81
  6. bbstrader/btengine/event.py +21 -22
  7. bbstrader/btengine/execution.py +51 -24
  8. bbstrader/btengine/performance.py +23 -12
  9. bbstrader/btengine/portfolio.py +40 -30
  10. bbstrader/btengine/scripts.py +13 -12
  11. bbstrader/btengine/strategy.py +396 -134
  12. bbstrader/compat.py +4 -3
  13. bbstrader/config.py +20 -36
  14. bbstrader/core/data.py +76 -48
  15. bbstrader/core/scripts.py +22 -21
  16. bbstrader/core/utils.py +13 -12
  17. bbstrader/metatrader/account.py +51 -26
  18. bbstrader/metatrader/analysis.py +30 -16
  19. bbstrader/metatrader/copier.py +75 -40
  20. bbstrader/metatrader/trade.py +29 -39
  21. bbstrader/metatrader/utils.py +5 -4
  22. bbstrader/models/nlp.py +83 -66
  23. bbstrader/trading/execution.py +45 -22
  24. bbstrader/tseries.py +158 -166
  25. {bbstrader-0.3.5.dist-info → bbstrader-0.3.7.dist-info}/METADATA +7 -21
  26. bbstrader-0.3.7.dist-info/RECORD +62 -0
  27. bbstrader-0.3.7.dist-info/top_level.txt +3 -0
  28. docs/conf.py +56 -0
  29. tests/__init__.py +0 -0
  30. tests/engine/__init__.py +1 -0
  31. tests/engine/test_backtest.py +58 -0
  32. tests/engine/test_data.py +536 -0
  33. tests/engine/test_events.py +300 -0
  34. tests/engine/test_execution.py +219 -0
  35. tests/engine/test_portfolio.py +308 -0
  36. tests/metatrader/__init__.py +0 -0
  37. tests/metatrader/test_account.py +1769 -0
  38. tests/metatrader/test_rates.py +292 -0
  39. tests/metatrader/test_risk_management.py +700 -0
  40. tests/metatrader/test_trade.py +439 -0
  41. bbstrader-0.3.5.dist-info/RECORD +0 -49
  42. bbstrader-0.3.5.dist-info/top_level.txt +0 -1
  43. {bbstrader-0.3.5.dist-info → bbstrader-0.3.7.dist-info}/WHEEL +0 -0
  44. {bbstrader-0.3.5.dist-info → bbstrader-0.3.7.dist-info}/entry_points.txt +0 -0
  45. {bbstrader-0.3.5.dist-info → bbstrader-0.3.7.dist-info}/licenses/LICENSE +0 -0
bbstrader/tseries.py CHANGED
@@ -5,7 +5,7 @@ some simple time series analysis in financial markets.
5
5
 
6
6
  import pprint
7
7
  import warnings
8
- from typing import List, Tuple, Union
8
+ from typing import Any, Dict, List, Optional, Tuple, Union
9
9
 
10
10
  import matplotlib.pyplot as plt
11
11
  import numpy as np
@@ -24,7 +24,6 @@ from statsmodels.tsa.vector_ar.var_model import VAR
24
24
  from statsmodels.tsa.vector_ar.vecm import coint_johansen
25
25
  from tqdm import tqdm
26
26
 
27
-
28
27
  __all__ = [
29
28
  "run_kalman_filter",
30
29
  "KalmanFilterModel",
@@ -95,7 +94,7 @@ def get_corr(tickers: Union[List[str], Tuple[str, ...]], start: str, end: str) -
95
94
  )
96
95
 
97
96
 
98
- def plot_price_series(df: pd.DataFrame, ts1: str, ts2: str):
97
+ def plot_price_series(df: pd.DataFrame, ts1: str, ts2: str) -> None:
99
98
  """
100
99
  Plot both time series on the same line graph for
101
100
  the specified date range.
@@ -118,7 +117,7 @@ def plot_price_series(df: pd.DataFrame, ts1: str, ts2: str):
118
117
  plt.show()
119
118
 
120
119
 
121
- def plot_scatter_series(df: pd.DataFrame, ts1: str, ts2: str):
120
+ def plot_scatter_series(df: pd.DataFrame, ts1: str, ts2: str) -> None:
122
121
  """
123
122
  Plot a scatter plot of both time series for
124
123
  via the provided DataFrame.
@@ -147,7 +146,7 @@ def plot_scatter_series(df: pd.DataFrame, ts1: str, ts2: str):
147
146
  plt.show()
148
147
 
149
148
 
150
- def plot_residuals(df: pd.DataFrame):
149
+ def plot_residuals(df: pd.DataFrame) -> None:
151
150
  """
152
151
  Plot the residuals of OLS procedure for both
153
152
  time series.
@@ -300,7 +299,7 @@ def run_hurst_test(symbol: str, start: str, end: str):
300
299
  )
301
300
 
302
301
 
303
- def test_cointegration(ticker1, ticker2, start, end):
302
+ def test_cointegration(ticker1: str, ticker2: str, start: str, end: str) -> None:
304
303
  warnings.warn(
305
304
  "`test_cointegration` is deprecated, see statsmodels.tsa.stattools.coint instead.",
306
305
  DeprecationWarning,
@@ -308,13 +307,15 @@ def test_cointegration(ticker1, ticker2, start, end):
308
307
 
309
308
 
310
309
  def run_coint_test(tickers: List[str], start: str, end: str) -> None:
311
- test_cointegration()
310
+ test_cointegration(tickers[0], tickers[1], start, end)
312
311
 
313
312
 
314
313
  # *********************************
315
314
  # KALMAN FILTER *
316
315
  # *********************************
317
- def draw_date_coloured_scatterplot(etfs, prices):
316
+ def draw_date_coloured_scatterplot(
317
+ etfs: Union[List[str], Tuple[str, ...]], prices: pd.DataFrame
318
+ ) -> None:
318
319
  """
319
320
  Create a scatterplot of the two ETF prices, which is
320
321
  coloured by the date of the price to indicate the
@@ -342,7 +343,9 @@ def draw_date_coloured_scatterplot(etfs, prices):
342
343
  plt.show()
343
344
 
344
345
 
345
- def calc_slope_intercept_kalman(etfs, prices):
346
+ def calc_slope_intercept_kalman(
347
+ etfs: Union[List[str], Tuple[str, ...]], prices: pd.DataFrame
348
+ ) -> Tuple[np.ndarray, np.ndarray]:
346
349
  """
347
350
  Utilize the Kalman Filter from the filterpy library
348
351
  to calculate the slope and intercept of the regressed
@@ -372,7 +375,7 @@ def calc_slope_intercept_kalman(etfs, prices):
372
375
  return np.array(state_means), np.array(state_covs)
373
376
 
374
377
 
375
- def draw_slope_intercept_changes(prices, state_means):
378
+ def draw_slope_intercept_changes(prices: pd.DataFrame, state_means: np.ndarray) -> None:
376
379
  """
377
380
  Plot the slope and intercept of the regressed ETF prices
378
381
  between the two ETFs, with the changing values of the
@@ -439,7 +442,9 @@ class KalmanFilterModel:
439
442
  You can learn more here https://en.wikipedia.org/wiki/Kalman_filter
440
443
  """
441
444
 
442
- def __init__(self, tickers: List | Tuple, **kwargs):
445
+ def __init__(
446
+ self, tickers: Union[List[str], Tuple[str, ...]], **kwargs: Any
447
+ ) -> None:
443
448
  """
444
449
  Initializes the Kalman Filter strategy.
445
450
 
@@ -453,7 +458,8 @@ class KalmanFilterModel:
453
458
  self.tickers = tickers
454
459
  assert self.tickers is not None
455
460
 
456
- self.R = None
461
+ self.R: Optional[np.ndarray] = None
462
+ self.C: Optional[np.ndarray] = None
457
463
  self.theta = np.zeros(2)
458
464
  self.P = np.zeros((2, 2))
459
465
  self.delta = kwargs.get("delta", 1e-4)
@@ -462,7 +468,7 @@ class KalmanFilterModel:
462
468
  self.latest_prices = np.array([-1.0, -1.0])
463
469
  self.kf = self._init_kalman()
464
470
 
465
- def _init_kalman(self):
471
+ def _init_kalman(self) -> KalmanFilter:
466
472
  """
467
473
  Initializes and returns a Kalman Filter configured
468
474
  for the trading strategy. The filter is set up with initial
@@ -478,9 +484,7 @@ class KalmanFilterModel:
478
484
 
479
485
  return kf
480
486
 
481
- Array = np.ndarray
482
-
483
- def calc_slope_intercep(self, prices: Array) -> Tuple:
487
+ def calc_slope_intercep(self, prices: np.ndarray) -> Tuple[float, float]:
484
488
  """
485
489
  Calculates and returns the slope and intercept
486
490
  of the relationship between the provided prices using the Kalman Filter.
@@ -501,7 +505,7 @@ class KalmanFilterModel:
501
505
 
502
506
  return slope, intercept
503
507
 
504
- def calculate_etqt(self, prices: Array) -> Tuple:
508
+ def calculate_etqt(self, prices: np.ndarray) -> Optional[Tuple[float, float]]:
505
509
  """
506
510
  Calculates the ``forecast error`` and ``standard deviation`` of the predictions
507
511
  using the Kalman Filter.
@@ -531,7 +535,7 @@ class KalmanFilterModel:
531
535
  # The prior value of the states {\theta_t} is
532
536
  # distributed as a multivariate Gaussian with
533
537
  # mean a_t and variance-covariance {R_t}
534
- if self.R is not None:
538
+ if self.R is not None and self.C is not None:
535
539
  self.R = self.C + self.wt
536
540
  else:
537
541
  self.R = np.zeros((2, 2))
@@ -555,7 +559,7 @@ class KalmanFilterModel:
555
559
  At = self.R.dot(F.T) / Qt
556
560
  self.theta = self.theta + At.flatten() * et
557
561
  self.C = self.R - At * F.dot(self.R)
558
- return (et[0], sqrt_Qt.flatten()[0])
562
+ return (et, sqrt_Qt.flatten()[0])
559
563
  else:
560
564
  return None
561
565
 
@@ -568,7 +572,7 @@ class OrnsteinUhlenbeck:
568
572
  )
569
573
 
570
574
 
571
- def remove_correlated_assets(df: pd.DataFrame, cutoff=0.99):
575
+ def remove_correlated_assets(df: pd.DataFrame, cutoff: float = 0.99) -> pd.DataFrame:
572
576
  """
573
577
  Removes highly correlated assets from a DataFrame based on a specified correlation cutoff threshold.
574
578
  This is useful in financial data analysis to reduce redundancy and multicollinearity in portfolios or datasets.
@@ -613,7 +617,7 @@ def remove_correlated_assets(df: pd.DataFrame, cutoff=0.99):
613
617
  return df.drop(drop, axis=1)
614
618
 
615
619
 
616
- def check_stationarity(df: pd.DataFrame):
620
+ def check_stationarity(df: pd.DataFrame) -> pd.DataFrame:
617
621
  """
618
622
  Tests the stationarity of time-series data for each asset in the DataFrame
619
623
  using the Augmented Dickey-Fuller (ADF) test. Stationarity is a key property
@@ -646,7 +650,7 @@ def check_stationarity(df: pd.DataFrame):
646
650
  return pd.DataFrame(results, columns=["ticker", "adf"]).sort_values("adf")
647
651
 
648
652
 
649
- def remove_stationary_assets(df: pd.DataFrame, pval=0.05):
653
+ def remove_stationary_assets(df: pd.DataFrame, pval: float = 0.05) -> pd.DataFrame:
650
654
  """
651
655
  Filters out stationary assets from the DataFrame based on the p-value obtained
652
656
  from the Augmented Dickey-Fuller test.
@@ -678,7 +682,13 @@ def remove_stationary_assets(df: pd.DataFrame, pval=0.05):
678
682
  return df.drop(stationary, axis=1).sort_index()
679
683
 
680
684
 
681
- def select_assets(df: pd.DataFrame, n=100, start=None, end=None, rolling_window=None):
685
+ def select_assets(
686
+ df: pd.DataFrame,
687
+ n: int = 100,
688
+ start: Optional[str] = None,
689
+ end: Optional[str] = None,
690
+ rolling_window: Optional[int] = None,
691
+ ) -> pd.DataFrame:
682
692
  """
683
693
  Selects the top N assets based on the average trading volume from the input DataFrame.
684
694
  These assets are used as universe in which we can search cointegrated pairs for pairs trading strategies.
@@ -745,33 +755,41 @@ def select_assets(df: pd.DataFrame, n=100, start=None, end=None, rolling_window=
745
755
  return df.sort_index()
746
756
 
747
757
 
748
- def compute_pair_metrics(security: pd.Series, candidates: pd.DataFrame):
749
- """
750
- Calculates statistical and econometric metrics for a target security and a set of candidate securities.
751
- These metrics are useful in financial modeling and pairs trading strategies,
752
- providing information about drift, volatility, correlation, and cointegration.
758
+ def compute_pair_metrics(security: pd.Series, candidates: pd.DataFrame) -> pd.DataFrame:
759
+ """Calculate statistical and econometric metrics for a security pair.
753
760
 
754
- Args:
755
- security (pd.Series): A time-series of the target security's prices.
756
- The name of the Series should correspond to the security's identifier (e.g., ticker symbol).
757
- candidates (pd.DataFrame): A DataFrame where each column represents a time-series of prices
758
- for candidate securities to be evaluated against the target security.
761
+ These metrics are useful in financial modeling and pairs trading strategies,
762
+ providing information about drift, volatility, correlation, and
763
+ cointegration.
759
764
 
760
- Returns:
761
- pd.DataFrame: A DataFrame combining:
762
- Drift: Estimated drift of spreads between the target security and each candidate.
763
- Volatility: Standard deviation of spreads.
764
- Correlation:
765
- ``corr``: Correlation of normalized prices between the target and each candidate.
766
- ``corr_ret``: Correlation of returns (percentage change) between the target and each candidate.
767
- Cointegration metrics:
768
- Engle-Granger test statistics (``t1``, ``t2``) and p-values (``p1``, ``p2``).
769
- Johansen test trace statistics (``trace0``, ``trace1``) and selected lag order (``k_ar_diff``).
765
+ Parameters
766
+ ----------
767
+ security : pd.Series
768
+ A time-series of the target security's prices. The name of the
769
+ Series should correspond to the security's identifier (e.g., ticker
770
+ symbol).
771
+ candidates : pd.DataFrame
772
+ A DataFrame where each column represents a time-series of prices
773
+ for candidate securities to be evaluated against the target security.
774
+
775
+ Returns
776
+ -------
777
+ pd.DataFrame
778
+ A DataFrame containing the following metrics as columns:
779
+ * ``Drift``: Estimated drift of spreads.
780
+ * ``Volatility``: Standard deviation of spreads.
781
+ * ``corr``: Correlation of normalized prices.
782
+ * ``corr_ret``: Correlation of returns (percentage change).
783
+ * ``t1``, ``p1``: Engle-Granger test statistic and p-value.
784
+ * ``t2``, ``p2``: Engle-Granger test statistic and p-value (alternate form).
785
+ * ``trace0``, ``trace1``: Johansen test trace statistics.
786
+ * ``k_ar_diff``: Selected lag order for the Johansen test.
770
787
 
771
788
  References
772
789
  ----------
773
- Stefan Jansen (2020). Machine Learning for Algorithmic Trading - Second Edition.
774
- chapter 9, Time-Series Models for Volatility Forecasts and Statistical Arbitrage.
790
+ * [1] Jansen, S. (2020). Machine Learning for Algorithmic Trading -
791
+ Second Edition. Packt Publishing. Chapter 9, Time-Series Models
792
+ for Volatility Forecasts and Statistical Arbitrage.
775
793
  """
776
794
  security = security.div(security.iloc[0])
777
795
  ticker = security.name
@@ -823,123 +841,87 @@ __CRITICAL_VALUES = {
823
841
  def find_cointegrated_pairs(
824
842
  securities: pd.DataFrame,
825
843
  candidates: pd.DataFrame,
826
- n=None,
827
- start=None,
828
- stop=None,
829
- coint=False,
830
- ):
844
+ n: Optional[int] = None,
845
+ start: Optional[str] = None,
846
+ stop: Optional[str] = None,
847
+ coint: bool = False,
848
+ ) -> pd.DataFrame:
831
849
  """
832
850
  Identifies cointegrated pairs between a target set of securities and candidate securities
833
851
  based on econometric tests. The function evaluates statistical relationships,
834
852
  such as cointegration and Engle-Granger significance, to determine pairs suitable
835
853
  for financial strategies like pairs trading.
836
854
 
837
- Args:
838
- securities (`pd.DataFrame`): A DataFrame where each column represents the time-series
839
- prices of target securities to evaluate.
840
- candidates (`pd.DataFrame`): A DataFrame where each column represents the time-series
841
- prices of candidate securities to compare against the target securities.
842
- n (`int`, optional): The number of top pairs to return. If `None`, returns all pairs.
843
- start (`str`, optional): Start date for slicing the data (e.g., 'YYYY-MM-DD').
844
- stop (`str`, optional): End date for slicing the data (e.g., 'YYYY-MM-DD').
845
- coint (`bool`, optional, default=False):
846
- - If `True`, filters for pairs identified as cointegrated.
847
- - If `False`, returns all evaluated pairs.
855
+ Parameters
856
+ ----------
857
+ securities : pd.DataFrame
858
+ A DataFrame where each column represents the time-series
859
+ prices of target securities to evaluate.
860
+ candidates : pd.DataFrame
861
+ A DataFrame where each column represents the time-series
862
+ prices of candidate securities to compare against the target securities.
863
+ n : int, optional
864
+ The number of top pairs to return. If ``None``, returns all pairs.
865
+ start : str, optional
866
+ Start date for slicing the data (e.g., 'YYYY-MM-DD').
867
+ stop : str, optional
868
+ End date for slicing the data (e.g., 'YYYY-MM-DD').
869
+ coint : bool, optional, default=False
870
+ If ``True``, filters for pairs identified as cointegrated.
871
+ If ``False``, returns all evaluated pairs.
872
+
873
+ Returns
874
+ -------
875
+ pd.DataFrame
876
+ A DataFrame containing:
877
+
878
+ **Johansen and Engle-Granger cointegration metrics**
879
+ * ``t1``, ``t2`` : Engle-Granger test statistics for two directions.
880
+ * ``p1``, ``p2`` : Engle-Granger p-values for two directions.
881
+ * ``trace0``, ``trace1`` : Johansen test trace statistics for 0 and 1 cointegration relationships.
882
+
883
+ **Indicators and filters**
884
+ * ``joh_sig`` : Indicates Johansen cointegration significance.
885
+ * ``eg_sig`` : Indicates Engle-Granger significance (p-value < 0.05).
886
+ * ``s1_dep`` : Indicates whether the first series depends on the second (based on p-values).
887
+ * ``coint`` : Combined cointegration indicator (Johansen & Engle-Granger).
888
+
889
+ **Spread and ranking**
890
+ * ``t`` : Minimum of ``t1`` and ``t2``.
891
+ * ``p`` : Minimum of ``p1`` and ``p2``.
848
892
 
849
- Returns:
850
- - ``pd.DataFrame``: A DataFrame containing:
851
- - Johansen and Engle-Granger cointegration metrics:
852
- - `t1`, `t2`: Engle-Granger test statistics for two directions.
853
- - `p1`, `p2`: Engle-Granger p-values for two directions.
854
- - `trace0`, `trace1`: Johansen test trace statistics for 0 and 1 cointegration relationships.
855
- - Indicators and filters:
856
- - `joh_sig`: Indicates Johansen cointegration significance.
857
- - `eg_sig`: Indicates Engle-Granger significance (p-value < 0.05).
858
- - `s1_dep`: Indicates whether the first series depends on the second (based on p-values).
859
- - `coint`: Combined cointegration indicator (Johansen & Engle-Granger).
860
- - Spread and ranking:
861
- - `t`: Minimum of `t1` and `t2`.
862
- - `p`: Minimum of `p1` and `p2`.
863
893
  References
864
894
  ----------
865
- Stefan Jansen (2020). Machine Learning for Algorithmic Trading - Second Edition.
866
- chapter 9, Time-Series Models for Volatility Forecasts and Statistical Arbitrage.
895
+ Stefan Jansen (2020). *Machine Learning for Algorithmic Trading - Second Edition*.
896
+ Chapter 9, Time-Series Models for Volatility Forecasts and Statistical Arbitrage.
867
897
 
868
- Example:
869
- >>> import pandas as pd
870
-
871
- >>> # Sample Data
872
- >>> data_securities = {
873
- ... 'Security1': [100, 102, 101, 103, 105],
874
- ... 'Security2': [50, 52, 53, 51, 54]
875
- ... }
876
- >>> data_candidates = {
877
- ... 'Candidate1': [100, 101, 99, 102, 104],
878
- ... 'Candidate2': [200, 202, 201, 203, 205]
879
- ... }
880
-
881
- >>> securities = pd.DataFrame(data_securities, index=pd.date_range('2023-01-01', periods=5))
882
- >>> candidates = pd.DataFrame(data_candidates, index=pd.date_range('2023-01-01', periods=5))
883
-
884
- >>> # Find cointegrated pairs
885
- >>> top_pairs = find_cointegrated_pairs(securities, candidates, n=2, coint=True)
886
- >>> print(top_pairs)
887
-
888
- >>> | s1 | s2 | t | p | joh_sig | eg_sig | coint |
889
- >>> |----------|-----------|------|-------|---------|--------|-------|
890
- >>> | Security1| Candidate1| -3.5 | 0.01 | 1 | 1 | 1 |
891
- >>> | Security2| Candidate2| -2.9 | 0.04 | 1 | 1 | 1 |
898
+ Examples
899
+ --------
900
+ >>> import pandas as pd
901
+ >>> data_securities = {
902
+ ... 'Security1': [100, 102, 101, 103, 105],
903
+ ... 'Security2': [50, 52, 53, 51, 54]
904
+ ... }
905
+ >>> data_candidates = {
906
+ ... 'Candidate1': [100, 101, 99, 102, 104],
907
+ ... 'Candidate2': [200, 202, 201, 203, 205]
908
+ ... }
909
+ >>> securities = pd.DataFrame(data_securities, index=pd.date_range('2023-01-01', periods=5))
910
+ >>> candidates = pd.DataFrame(data_candidates, index=pd.date_range('2023-01-01', periods=5))
911
+ >>> top_pairs = find_cointegrated_pairs(securities, candidates, n=2, coint=True)
912
+ >>> print(top_pairs)
892
913
  """
893
- trace0_cv = __CRITICAL_VALUES[0][
894
- 0.95
895
- ] # critical value for 0 cointegration relationships
896
- # critical value for 1 cointegration relationship
897
- trace1_cv = __CRITICAL_VALUES[1][0.95]
898
- spreads = []
899
- if start is not None and stop is not None:
900
- securities = securities.loc[str(start) : str(stop), :]
901
- candidates = candidates.loc[str(start) : str(stop), :]
902
- for i, (ticker, prices) in enumerate(securities.items(), 1):
903
- try:
904
- df = compute_pair_metrics(prices, candidates)
905
- spreads.append(df.set_index("s1", append=True))
906
- except np.linalg.LinAlgError:
907
- continue
908
- spreads = pd.concat(spreads)
909
- spreads.index.names = ["s2", "s1"]
910
- spreads = spreads.swaplevel()
911
- spreads["t"] = spreads[["t1", "t2"]].min(axis=1)
912
- spreads["p"] = spreads[["p1", "p2"]].min(axis=1)
913
- spreads["joh_sig"] = (
914
- (spreads.trace0 > trace0_cv) & (spreads.trace1 > trace1_cv)
915
- ).astype(int)
916
- spreads["eg_sig"] = (spreads.p < 0.05).astype(int)
917
- spreads["s1_dep"] = spreads.p1 < spreads.p2
918
- spreads["coint"] = (spreads.joh_sig & spreads.eg_sig).astype(int)
919
- # select top n pairs
920
- if coint:
921
- if n is not None:
922
- top_pairs = (
923
- spreads.query("coint == 1").sort_values("t", ascending=False).head(n)
924
- )
925
- else:
926
- top_pairs = spreads.query("coint == 1").sort_values("t", ascending=False)
927
- else:
928
- if n is not None:
929
- top_pairs = spreads.sort_values("t", ascending=False).head(n)
930
- else:
931
- top_pairs = spreads.sort_values("t", ascending=False)
932
- return top_pairs
914
+ ...
933
915
 
934
916
 
935
917
  def analyze_cointegrated_pairs(
936
918
  spreads: pd.DataFrame,
937
- plot_coint=True,
938
- crosstab=False,
939
- heuristics=False,
940
- log_reg=False,
941
- decis_tree=False,
942
- ):
919
+ plot_coint: bool = True,
920
+ crosstab: bool = False,
921
+ heuristics: bool = False,
922
+ log_reg: bool = False,
923
+ decis_tree: bool = False,
924
+ ) -> None:
943
925
  """
944
926
  Analyzes cointegrated pairs by visualizing, summarizing, and applying predictive models.
945
927
 
@@ -949,7 +931,7 @@ def analyze_cointegrated_pairs(
949
931
  Required columns: 'coint', 't', 'trace0', 'trace1', 'drift', 'vol', 'corr', 'corr_ret', 'eg_sig', 'joh_sig'.
950
932
  plot_coint (bool, optional):
951
933
  If True, generates scatterplots and boxplots to visualize cointegration characteristics.
952
- cosstab (bool, optional):
934
+ crosstab (bool, optional):
953
935
  If True, displays crosstabulations of Engle-Granger and Johansen test significance.
954
936
  heuristics (bool, optional):
955
937
  If True, prints descriptive statistics for drift, volatility, and correlation grouped by cointegration status.
@@ -978,7 +960,7 @@ def analyze_cointegrated_pairs(
978
960
  ... })
979
961
 
980
962
  >>> pairs = find_cointegrated_pairs(securities, candidates, n=2, coint=True)
981
- >>> analyze_cointegrated_pairs(pairs, plot_coint=True, cosstab=True, heuristics=True, log_reg=True, decis_tree=True
963
+ >>> analyze_cointegrated_pairs(pairs, plot_coint=True, crosstab=True, heuristics=True, log_reg=True, decis_tree=True)
982
964
  """
983
965
  if plot_coint:
984
966
  trace0_cv = __CRITICAL_VALUES[0][0.95]
@@ -1036,7 +1018,9 @@ def analyze_cointegrated_pairs(
1036
1018
  print(pd.crosstab(spreads.eg_sig, spreads.joh_sig, normalize=True))
1037
1019
 
1038
1020
 
1039
- def select_candidate_pairs(pairs: pd.DataFrame, period=False):
1021
+ def select_candidate_pairs(
1022
+ pairs: pd.DataFrame, period: bool = False
1023
+ ) -> List[Dict[str, Any]]:
1040
1024
  """
1041
1025
  Select candidate pairs from a DataFrame based on cointegration status.
1042
1026
 
@@ -1069,7 +1053,7 @@ def select_candidate_pairs(pairs: pd.DataFrame, period=False):
1069
1053
  return candidates[["x", "y"]].to_dict(orient="records")
1070
1054
 
1071
1055
 
1072
- def KFSmoother(prices: pd.Series | np.ndarray) -> pd.Series | np.ndarray:
1056
+ def KFSmoother(prices: Union[pd.Series, np.ndarray]) -> Union[pd.Series, np.ndarray]:
1073
1057
  """
1074
1058
  Estimate rolling mean using Kalman Smoothing.
1075
1059
 
@@ -1120,26 +1104,34 @@ def KFSmoother(prices: pd.Series | np.ndarray) -> pd.Series | np.ndarray:
1120
1104
  return state_means.flatten()
1121
1105
 
1122
1106
 
1123
- def KFHedgeRatio(x: pd.Series | np.ndarray, y: pd.Series | np.ndarray) -> np.ndarray:
1124
- """
1125
- Estimate Hedge Ratio using Kalman Filter.
1126
- Args:
1127
- x : pd.Series or np.ndarray
1128
- The independent variable, which can be either a pandas Series or a numpy array.
1129
- y : pd.Series or np.ndarray
1130
- The dependent variable, which can be either a pandas Series or a numpy array.
1107
+ def KFHedgeRatio(
1108
+ x: Union[pd.Series, np.ndarray], y: Union[pd.Series, np.ndarray]
1109
+ ) -> np.ndarray:
1110
+ """Estimate Hedge Ratio using Kalman Filter.
1131
1111
 
1132
- Returns:
1133
- np.ndarray
1134
- The estimated hedge ratio as a numpy array.
1112
+ This function uses a Kalman Filter to dynamically estimate the hedge ratio
1113
+ (beta) and intercept (alpha) for a pair of assets over time.
1114
+
1115
+ The function returns the negative of the first state variable (the hedge ratio)
1116
+ for each time step, which is a common convention in pairs trading.
1135
1117
 
1136
- The function returns the negative of the first state variable of each Kalman Filter estimate,
1137
- which represents the estimated hedge ratio.
1118
+ Parameters
1119
+ ----------
1120
+ x : pd.Series or np.ndarray
1121
+ The independent variable (e.g., price series of asset X).
1122
+ y : pd.Series or np.ndarray
1123
+ The dependent variable (e.g., price series of asset Y).
1124
+
1125
+ Returns
1126
+ -------
1127
+ np.ndarray
1128
+ The time-varying estimated hedge ratio as a numpy array.
1138
1129
 
1139
1130
  References
1140
1131
  ----------
1141
- Stefan Jansen (2020). Machine Learning for Algorithmic Trading - Second Edition.
1142
- chapter 9, Time-Series Models for Volatility Forecasts and Statistical Arbitrage.
1132
+ * [1] Jansen, S. (2020). Machine Learning for Algorithmic Trading -
1133
+ Second Edition. Packt Publishing. Chapter 9, Time-Series Models
1134
+ for Volatility Forecasts and Statistical Arbitrage.
1143
1135
  """
1144
1136
  if not isinstance(x, (np.ndarray, pd.Series)) or not isinstance(
1145
1137
  y, (np.ndarray, pd.Series)
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bbstrader
3
- Version: 0.3.5
3
+ Version: 0.3.7
4
4
  Summary: Simplified Investment & Trading Toolkit
5
- Home-page: https://github.com/bbalouki/bbstrader
6
- Download-URL: https://pypi.org/project/bbstrader/
7
- Author: Bertin Balouki SIMYELI
8
- Author-email: <bertin@bbstrader.com>
9
- Maintainer: Bertin Balouki SIMYELI
10
- License: MIT
5
+ Author-email: Bertin Balouki SIMYELI <bertin@bbs-trading.com>
6
+ Maintainer-email: Bertin Balouki SIMYELI <bertin@bbs-trading.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/bbalouki/bbstrader
9
+ Project-URL: Download, https://pypi.org/project/bbstrader/
11
10
  Project-URL: Documentation, https://bbstrader.readthedocs.io/en/latest/
12
11
  Project-URL: Source Code, https://github.com/bbalouki/bbstrader
13
12
  Keywords: Finance,Toolkit,Financial,Analysis,Fundamental,Quantitative,Database,Equities,Currencies,Economics,ETFs,Funds,Indices,Moneymarkets,Commodities,Futures,CFDs,Derivatives,Trading,Investing,Portfolio,Optimization,Performance
@@ -19,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Operating System :: Microsoft :: Windows
20
19
  Classifier: Operating System :: POSIX :: Linux
21
20
  Classifier: Operating System :: MacOS
21
+ Requires-Python: >=3.12
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: alphalens-reloaded>=0.4.6
@@ -58,21 +58,7 @@ Requires-Dist: vaderSentiment>=3.3.2
58
58
  Requires-Dist: yfinance>=0.2.65
59
59
  Provides-Extra: mt5
60
60
  Requires-Dist: MetaTrader5; extra == "mt5"
61
- Dynamic: author
62
- Dynamic: author-email
63
- Dynamic: classifier
64
- Dynamic: description
65
- Dynamic: description-content-type
66
- Dynamic: download-url
67
- Dynamic: home-page
68
- Dynamic: keywords
69
- Dynamic: license
70
61
  Dynamic: license-file
71
- Dynamic: maintainer
72
- Dynamic: project-url
73
- Dynamic: provides-extra
74
- Dynamic: requires-dist
75
- Dynamic: summary
76
62
 
77
63
  # Simplified Investment & Trading Toolkit
78
64
  [![Documentation Status](https://readthedocs.org/projects/bbstrader/badge/?version=latest)](https://bbstrader.readthedocs.io/en/latest/?badge=latest)
@@ -0,0 +1,62 @@
1
+ bbstrader/__init__.py,sha256=bsyhtdTioj1KZyaAnEN6oE0Wvq8z0HaFEzPe8yWyxFo,749
2
+ bbstrader/__main__.py,sha256=KtGegYzmlp78YGuDIW75ouxLyWaRPNC4nC-Qcg8wheE,2751
3
+ bbstrader/compat.py,sha256=5gj2Yycv3W50BfaPDqC3sKZOwTP2AbD_MFfiL8DJYqg,682
4
+ bbstrader/config.py,sha256=4n6jS0x1E2kEVvCxFaRI6HRaGnJqPz1hhUofquwvsMk,2959
5
+ bbstrader/tseries.py,sha256=O6RG2ame_i6jGT7nETizb3uy7pwxrToRRJDKCUNupoQ,42800
6
+ bbstrader/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ bbstrader/apps/_copier.py,sha256=61PBMo5f429pDJ-XchTivZ1Y0whFLl2ZtdyFHJfmsxQ,25970
8
+ bbstrader/btengine/__init__.py,sha256=y1btjaEfhWsH8vuE7mBRpP9Tu-Azt9REhuVYsPCAfBU,2955
9
+ bbstrader/btengine/backtest.py,sha256=4O0ZnkhJ1nu3zWaGCrXQVKhfqW46jsqdMIPmsUPLyYo,14979
10
+ bbstrader/btengine/data.py,sha256=03ahVmqXQefx8gaH16fLBP1rIikfKqmJ4PULm1Q29TU,28760
11
+ bbstrader/btengine/event.py,sha256=E_OvwAO23xl0FqH-oe5Dh83fDmSjuF_KS8kOi8SEhTc,8880
12
+ bbstrader/btengine/execution.py,sha256=RFH2xIhiUGv9nqyvupBNuCmw3rjRgO7h2ySP_3Ggei0,12182
13
+ bbstrader/btengine/performance.py,sha256=WT4jz5AnOr8Eenz6NNR6po8Q8brMDIJskCS0o3mWy40,12764
14
+ bbstrader/btengine/portfolio.py,sha256=bqcVuohDZqertLo6LEk7DIY6e3Npi0UKQUKawEmo5Ko,16695
15
+ bbstrader/btengine/scripts.py,sha256=5wRzyxGxA6C8cd_tRDrr-p_7_TtWWk0zIxzblZxP3M4,5178
16
+ bbstrader/btengine/strategy.py,sha256=D72lULYu1tth9KUm4RS7EtrJdDqa1EF4uH8wlqrTTYQ,44666
17
+ bbstrader/core/__init__.py,sha256=GIFzFSStPfE0XM2j7mDeZZQeMTh_AwPsDOQXwMVJLgw,97
18
+ bbstrader/core/data.py,sha256=KJCGSfTGJNfLWOAytfRM4-BVNV1M6QfvH4DWB-4v4cA,26146
19
+ bbstrader/core/scripts.py,sha256=cdw0X12-8IHsr8pg6x_fMsUg_UhFFWDy9lXY52Vpou8,5777
20
+ bbstrader/core/utils.py,sha256=8PsJm73LMJf1lCjfxDlqU32U4GhWPVcUTOyyETM7fLs,3124
21
+ bbstrader/ibkr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ bbstrader/ibkr/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ bbstrader/metatrader/__init__.py,sha256=A5Ye9tpc2sp9Xk5qjKw-EfYsoRcZtAt8nqvC3tCtZs8,333
24
+ bbstrader/metatrader/account.py,sha256=5YdvRggBxfZ6MQX70EFfwAZ7tuHvGzkwyEw1GgtiR4c,65501
25
+ bbstrader/metatrader/analysis.py,sha256=HuxYiKwa7VYmI_noqc4OAKSoC7bJpYm-OosQGZdMEnU,3694
26
+ bbstrader/metatrader/copier.py,sha256=VOo0RfuNoItc10CRGdX8MsUtcBBAzoY42sfolmvgptg,56052
27
+ bbstrader/metatrader/rates.py,sha256=w9mr6FB6E1zLcHCDtDGt-oMnw6sakIU6Qe3455KDsSg,20782
28
+ bbstrader/metatrader/risk.py,sha256=NhW8qtSg350Z6H9oLcDqOU_erqd_7Y7F5FwpfPN5Qso,27262
29
+ bbstrader/metatrader/scripts.py,sha256=8meq6_zz6jPSibNgtYtaO8Ba-uJZOoLkpqYUIjidk-U,4010
30
+ bbstrader/metatrader/trade.py,sha256=-tMbMBSTGAFJe9xK706uhR0Na8lJu7vfEhmud9Q3XnU,81003
31
+ bbstrader/metatrader/utils.py,sha256=Yt3mx8EgS6u1-irQG6uQn-LhC3kyWOVE00VepnDAtCI,20802
32
+ bbstrader/models/__init__.py,sha256=B-bn2h_SCK6gRAs2li6dDVnvV8jDT5suZimldk5xxcw,497
33
+ bbstrader/models/factors.py,sha256=J7yxtDr1sCTw1AI59kluF89e2b9HkpEXfFyIcfPHUCQ,13008
34
+ bbstrader/models/ml.py,sha256=NVN9zxRRDJn2S8KSgGBkiSHvdFjsDiaNsW2Y6rs51Io,50314
35
+ bbstrader/models/nlp.py,sha256=z_mbL58U5UGXc7ocVtvUR_zUYSk7Om2Gu7xXYBerxQY,32619
36
+ bbstrader/models/optimization.py,sha256=Fa4tdhynMmvKt5KHV9cH1TXmmJVJwU4QWpYkbeVq4aI,6395
37
+ bbstrader/models/portfolio.py,sha256=r-47Zrn2r7iKCHm5YVtwkbBJXAZGM3QYy-rXCWY9-Bg,8079
38
+ bbstrader/models/risk.py,sha256=MKCk53HtGIcivrNzH8Ikm5KMs1rXhFT5zkorUf30PyQ,506
39
+ bbstrader/trading/__init__.py,sha256=ycLyuuxN5SujqtzR9X0Q74UQfK93q2va-GGAXdr-KS8,457
40
+ bbstrader/trading/execution.py,sha256=P1SrV5CKhSbi2NhztPO3pEtGVHOXkfHEdWAVYzeaMWw,42086
41
+ bbstrader/trading/scripts.py,sha256=Tf5q33WqqygjpIv43_8nA82VZ3GM0qgb4Ggo3fHJ_wg,5744
42
+ bbstrader/trading/strategies.py,sha256=RZ6P4SfIyRW72v0OnPnrc4Hv8X00FdxR-_sD23xe_Pg,11756
43
+ bbstrader/trading/utils.py,sha256=57dKF9dcRu04oU2VRqydRrzW39dCW2wlDWhVt-sZdRw,1857
44
+ bbstrader-0.3.7.dist-info/licenses/LICENSE,sha256=ZwC_RqqGmOPBUiMDKqLyJZ5HBeHq53LpL7TMRzrJY8c,1094
45
+ docs/conf.py,sha256=q_Z8_iz-YDgHhe4PpCOAtvN5Q-2hHquliG07FDEXdjo,1686
46
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
+ tests/engine/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
48
+ tests/engine/test_backtest.py,sha256=OmHimBp518BBv0Eg-t_cIrBXMzfiYOGki5FW7kqFeFs,2136
49
+ tests/engine/test_data.py,sha256=jAVICMt4YTKF5PN6ZIxhQ9CuUjbw9_gIPAC4sNA9GsE,19907
50
+ tests/engine/test_events.py,sha256=is3v3mtQVKFVNyn-xF4F7yuBGA5vqgunqsoFgNJeH0c,11755
51
+ tests/engine/test_execution.py,sha256=Sk3d_Rl9T0GjArficd75hEeVyeB_7yOcTD8ltpN4UlY,7961
52
+ tests/engine/test_portfolio.py,sha256=Anx6TtgCdyyhqfoui2kwuX8X5gtisT2tL5yZNJOUbKw,9865
53
+ tests/metatrader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
+ tests/metatrader/test_account.py,sha256=UiAWP0IRd6a1kmA_pTcEuCZAJjtq0U_Q-PcOdd2l69A,68627
55
+ tests/metatrader/test_rates.py,sha256=dc43jAvkZby9TMJcwkcaEvH21jQudk1D8KQxWLlAYoQ,10424
56
+ tests/metatrader/test_risk_management.py,sha256=lMT0m7dmT3iWihTkEwpMoHdJPPWU3XJmqgCV4qKT-uw,31019
57
+ tests/metatrader/test_trade.py,sha256=gMyinOOgVC2SOV8xLMIKqUTwqXbSUuDGnhSmspXK9cw,15177
58
+ bbstrader-0.3.7.dist-info/METADATA,sha256=2l53CAyYDuXnRg0duIlsMFiGjDtGVOVYMrJwgfSWVzQ,26697
59
+ bbstrader-0.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
60
+ bbstrader-0.3.7.dist-info/entry_points.txt,sha256=0yDCbhbgHswOzJnY5wRSM_FjjyMHGvY7lJpSSVh0xtI,54
61
+ bbstrader-0.3.7.dist-info/top_level.txt,sha256=raTnmqZJ2B3Mvrhy_fV9szurE9yVctrKHBLZ1NJ5vnU,21
62
+ bbstrader-0.3.7.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ bbstrader
2
+ docs
3
+ tests