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.
- bbstrader/__init__.py +11 -2
- bbstrader/__main__.py +6 -1
- bbstrader/apps/_copier.py +43 -40
- bbstrader/btengine/backtest.py +33 -28
- bbstrader/btengine/data.py +105 -81
- bbstrader/btengine/event.py +21 -22
- bbstrader/btengine/execution.py +51 -24
- bbstrader/btengine/performance.py +23 -12
- bbstrader/btengine/portfolio.py +40 -30
- bbstrader/btengine/scripts.py +13 -12
- bbstrader/btengine/strategy.py +396 -134
- bbstrader/compat.py +4 -3
- bbstrader/config.py +20 -36
- bbstrader/core/data.py +76 -48
- bbstrader/core/scripts.py +22 -21
- bbstrader/core/utils.py +13 -12
- bbstrader/metatrader/account.py +51 -26
- bbstrader/metatrader/analysis.py +30 -16
- bbstrader/metatrader/copier.py +75 -40
- bbstrader/metatrader/trade.py +29 -39
- bbstrader/metatrader/utils.py +5 -4
- bbstrader/models/nlp.py +83 -66
- bbstrader/trading/execution.py +45 -22
- bbstrader/tseries.py +158 -166
- {bbstrader-0.3.5.dist-info → bbstrader-0.3.7.dist-info}/METADATA +7 -21
- bbstrader-0.3.7.dist-info/RECORD +62 -0
- bbstrader-0.3.7.dist-info/top_level.txt +3 -0
- docs/conf.py +56 -0
- tests/__init__.py +0 -0
- tests/engine/__init__.py +1 -0
- tests/engine/test_backtest.py +58 -0
- tests/engine/test_data.py +536 -0
- tests/engine/test_events.py +300 -0
- tests/engine/test_execution.py +219 -0
- tests/engine/test_portfolio.py +308 -0
- tests/metatrader/__init__.py +0 -0
- tests/metatrader/test_account.py +1769 -0
- tests/metatrader/test_rates.py +292 -0
- tests/metatrader/test_risk_management.py +700 -0
- tests/metatrader/test_trade.py +439 -0
- bbstrader-0.3.5.dist-info/RECORD +0 -49
- bbstrader-0.3.5.dist-info/top_level.txt +0 -1
- {bbstrader-0.3.5.dist-info → bbstrader-0.3.7.dist-info}/WHEEL +0 -0
- {bbstrader-0.3.5.dist-info → bbstrader-0.3.7.dist-info}/entry_points.txt +0 -0
- {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(
|
|
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(
|
|
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__(
|
|
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
|
-
|
|
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:
|
|
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
|
|
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(
|
|
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
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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
|
-
|
|
774
|
-
|
|
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
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
-
|
|
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
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
>>>
|
|
872
|
-
|
|
873
|
-
...
|
|
874
|
-
...
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
...
|
|
878
|
-
...
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
>>>
|
|
882
|
-
>>>
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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
|
|
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(
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
-
|
|
1137
|
-
|
|
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
|
-
|
|
1142
|
-
|
|
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.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: Simplified Investment & Trading Toolkit
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
[](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,,
|