bbstrader 0.1.9__py3-none-any.whl → 0.1.91__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/__ini__.py +4 -2
- bbstrader/btengine/__init__.py +5 -5
- bbstrader/btengine/backtest.py +51 -10
- bbstrader/btengine/data.py +147 -55
- bbstrader/btengine/event.py +4 -1
- bbstrader/btengine/execution.py +125 -23
- bbstrader/btengine/performance.py +4 -7
- bbstrader/btengine/portfolio.py +34 -13
- bbstrader/btengine/strategy.py +466 -6
- bbstrader/config.py +111 -0
- bbstrader/metatrader/__init__.py +4 -4
- bbstrader/metatrader/account.py +348 -53
- bbstrader/metatrader/rates.py +232 -27
- bbstrader/metatrader/risk.py +34 -23
- bbstrader/metatrader/trade.py +321 -165
- bbstrader/metatrader/utils.py +2 -53
- bbstrader/models/factors.py +0 -0
- bbstrader/models/ml.py +0 -0
- bbstrader/models/optimization.py +0 -0
- bbstrader/trading/__init__.py +1 -1
- bbstrader/trading/execution.py +268 -164
- bbstrader/trading/scripts.py +57 -0
- bbstrader/trading/strategies.py +41 -65
- bbstrader/tseries.py +274 -39
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.91.dist-info}/METADATA +11 -3
- bbstrader-0.1.91.dist-info/RECORD +31 -0
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.91.dist-info}/WHEEL +1 -1
- bbstrader-0.1.9.dist-info/RECORD +0 -26
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.91.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.9.dist-info → bbstrader-0.1.91.dist-info}/top_level.txt +0 -0
bbstrader/tseries.py
CHANGED
|
@@ -6,32 +6,35 @@ tasks such as cointegration testing, volatility modeling,
|
|
|
6
6
|
and filter-based estimation to assist in trading strategy development,
|
|
7
7
|
market analysis, and financial data exploration.
|
|
8
8
|
"""
|
|
9
|
-
import numpy as np
|
|
10
|
-
import pandas as pd
|
|
11
9
|
import pprint
|
|
12
10
|
import warnings
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pandas as pd
|
|
13
|
+
from tqdm import tqdm
|
|
13
14
|
import yfinance as yf
|
|
14
|
-
from arch import arch_model
|
|
15
|
-
from statsmodels.tsa.arima.model import ARIMA
|
|
16
15
|
import pmdarima as pm
|
|
17
|
-
import
|
|
16
|
+
import seaborn as sns
|
|
18
17
|
import statsmodels.api as sm
|
|
18
|
+
import matplotlib.pyplot as plt
|
|
19
19
|
import statsmodels.tsa.stattools as ts
|
|
20
|
-
from numpy import cumsum, log, polyfit, sqrt, std, subtract
|
|
21
|
-
from numpy.random import randn
|
|
22
20
|
from hurst import compute_Hc
|
|
21
|
+
from arch import arch_model
|
|
23
22
|
from scipy.optimize import minimize
|
|
24
23
|
from filterpy.kalman import KalmanFilter
|
|
24
|
+
from pykalman import KalmanFilter as PyKalmanFilter
|
|
25
25
|
from statsmodels.tsa.vector_ar.vecm import coint_johansen
|
|
26
26
|
from statsmodels.graphics.tsaplots import plot_acf
|
|
27
|
+
from statsmodels.tsa.stattools import adfuller, coint
|
|
28
|
+
from statsmodels.tsa.arima.model import ARIMA
|
|
29
|
+
from statsmodels.tsa.vector_ar.var_model import VAR
|
|
30
|
+
from sklearn.model_selection import GridSearchCV
|
|
31
|
+
from sklearn.tree import DecisionTreeClassifier
|
|
32
|
+
from sklearn.linear_model import LogisticRegressionCV
|
|
33
|
+
from statsmodels.stats.diagnostic import acorr_ljungbox
|
|
27
34
|
from itertools import combinations
|
|
28
35
|
from typing import Union, List, Tuple
|
|
29
|
-
from statsmodels.stats.diagnostic import acorr_ljungbox
|
|
30
|
-
from arch.utility.exceptions import ConvergenceWarning as ArchWarning
|
|
31
|
-
from statsmodels.tools.sm_exceptions import ConvergenceWarning as StatsWarning
|
|
32
36
|
warnings.filterwarnings("ignore")
|
|
33
|
-
|
|
34
|
-
warnings.filterwarnings("ignore", category=ArchWarning, module='arch')
|
|
37
|
+
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
__all__ = [
|
|
@@ -117,18 +120,23 @@ def fit_best_arima(window_data: Union[pd.Series, np.ndarray]):
|
|
|
117
120
|
stepwise=True
|
|
118
121
|
)
|
|
119
122
|
final_order = model.order
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
from arch.utility.exceptions import ConvergenceWarning as ArchWarning
|
|
124
|
+
from statsmodels.tools.sm_exceptions import ConvergenceWarning as StatsWarning
|
|
125
|
+
with warnings.catch_warnings():
|
|
126
|
+
warnings.filterwarnings("ignore", category=StatsWarning, module='statsmodels')
|
|
127
|
+
warnings.filterwarnings("ignore", category=ArchWarning, module='arch')
|
|
128
|
+
try:
|
|
129
|
+
best_arima_model = ARIMA(
|
|
130
|
+
window_data + 1e-5, order=final_order, missing='drop').fit()
|
|
131
|
+
return best_arima_model
|
|
132
|
+
except np.linalg.LinAlgError:
|
|
133
|
+
# Catch specific linear algebra errors
|
|
134
|
+
print("LinAlgError occurred, skipping this data point.")
|
|
135
|
+
return None
|
|
136
|
+
except Exception as e:
|
|
137
|
+
# Catch any other unexpected errors and log them
|
|
138
|
+
print(f"An error occurred: {e}")
|
|
139
|
+
return None
|
|
132
140
|
|
|
133
141
|
|
|
134
142
|
def fit_garch(window_data: Union[pd.Series, np.ndarray]):
|
|
@@ -684,10 +692,10 @@ def _hurst(ts):
|
|
|
684
692
|
lags = range(2, 100)
|
|
685
693
|
|
|
686
694
|
# Calculate the array of the variances of the lagged differences
|
|
687
|
-
tau = [sqrt(std(subtract(ts[lag:], ts[:-lag]))) for lag in lags]
|
|
695
|
+
tau = [np.sqrt(np.std(np.subtract(ts[lag:], ts[:-lag]))) for lag in lags]
|
|
688
696
|
|
|
689
697
|
# Use a linear fit to estimate the Hurst Exponent
|
|
690
|
-
poly = polyfit(log(lags), log(tau), 1)
|
|
698
|
+
poly = np.polyfit(np.log(lags), np.log(tau), 1)
|
|
691
699
|
|
|
692
700
|
# Return the Hurst exponent from the polyfit output
|
|
693
701
|
return poly[0] * 2.0
|
|
@@ -721,9 +729,9 @@ def run_hurst_test(symbol: str, start: str, end: str):
|
|
|
721
729
|
data = yf.download(symbol, start=start, end=end)
|
|
722
730
|
|
|
723
731
|
# Create a Geometric Brownian Motion, Mean-Reverting, and Trending Series
|
|
724
|
-
gbm = log(cumsum(randn(100000))+1000)
|
|
725
|
-
mr = log(randn(100000)+1000)
|
|
726
|
-
tr = log(cumsum(randn(100000)+1)+1000)
|
|
732
|
+
gbm = np.log(np.cumsum(np.random.randn(100000))+1000)
|
|
733
|
+
mr = np.log(np.random.randn(100000)+1000)
|
|
734
|
+
tr = np.log(np.cumsum(np.random.randn(100000)+1)+1000)
|
|
727
735
|
|
|
728
736
|
# Output the Hurst Exponent for each of the series
|
|
729
737
|
print(f"\nHurst(GBM): {_hurst(gbm)}")
|
|
@@ -898,7 +906,7 @@ class KalmanFilterModel():
|
|
|
898
906
|
You can learn more here https://en.wikipedia.org/wiki/Kalman_filter
|
|
899
907
|
"""
|
|
900
908
|
|
|
901
|
-
def __init__(self, tickers:
|
|
909
|
+
def __init__(self, tickers: List | Tuple, **kwargs):
|
|
902
910
|
"""
|
|
903
911
|
Initializes the Kalman Filter strategy.
|
|
904
912
|
|
|
@@ -907,7 +915,7 @@ class KalmanFilterModel():
|
|
|
907
915
|
A list or tuple of ticker symbols representing financial instruments.
|
|
908
916
|
|
|
909
917
|
kwargs : Keyword arguments for additional parameters,
|
|
910
|
-
|
|
918
|
+
specifically `delta` and `vt`
|
|
911
919
|
"""
|
|
912
920
|
self.tickers = tickers
|
|
913
921
|
assert self.tickers is not None
|
|
@@ -936,7 +944,8 @@ class KalmanFilterModel():
|
|
|
936
944
|
|
|
937
945
|
return kf
|
|
938
946
|
|
|
939
|
-
|
|
947
|
+
Array = np.ndarray
|
|
948
|
+
def calc_slope_intercep(self, prices: Array) -> Tuple:
|
|
940
949
|
"""
|
|
941
950
|
Calculates and returns the slope and intercept
|
|
942
951
|
of the relationship between the provided prices using the Kalman Filter.
|
|
@@ -957,8 +966,8 @@ class KalmanFilterModel():
|
|
|
957
966
|
intercept = kf.x.copy().flatten()[1]
|
|
958
967
|
|
|
959
968
|
return slope, intercept
|
|
960
|
-
|
|
961
|
-
def calculate_etqt(self, prices:
|
|
969
|
+
|
|
970
|
+
def calculate_etqt(self, prices: Array) -> Tuple:
|
|
962
971
|
"""
|
|
963
972
|
Calculates the forecast error and standard deviation of the predictions
|
|
964
973
|
using the Kalman Filter.
|
|
@@ -1144,21 +1153,19 @@ class OrnsteinUhlenbeck():
|
|
|
1144
1153
|
) / sigma**2 + 0.5 * n * np.log(2 * np.pi * sigma**2)
|
|
1145
1154
|
return neg_ll
|
|
1146
1155
|
|
|
1147
|
-
def simulate_process(self,
|
|
1156
|
+
def simulate_process(self, returns=None, n=100, p=None):
|
|
1148
1157
|
"""
|
|
1149
1158
|
Simulates the OU process multiple times .
|
|
1150
1159
|
|
|
1151
1160
|
Args:
|
|
1152
|
-
|
|
1161
|
+
returns (np.ndarray): Historical returns.
|
|
1153
1162
|
n (int): Number of simulations to perform.
|
|
1154
1163
|
p (int): Number of time steps.
|
|
1155
1164
|
|
|
1156
1165
|
Returns:
|
|
1157
1166
|
np.ndarray: 2D array representing simulated processes.
|
|
1158
1167
|
"""
|
|
1159
|
-
if
|
|
1160
|
-
returns = rts
|
|
1161
|
-
else:
|
|
1168
|
+
if returns is None:
|
|
1162
1169
|
returns = self.returns
|
|
1163
1170
|
if p is not None:
|
|
1164
1171
|
T = p
|
|
@@ -1180,3 +1187,231 @@ class OrnsteinUhlenbeck():
|
|
|
1180
1187
|
self.sigma_hat * dW_matrix[:, t]
|
|
1181
1188
|
)
|
|
1182
1189
|
return simulations_matrix
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
def remove_correlated_assets(df: pd.DataFrame, cutoff=.99):
|
|
1193
|
+
corr = df.corr().stack()
|
|
1194
|
+
corr = corr[corr < 1]
|
|
1195
|
+
to_check = corr[corr.abs() > cutoff].index
|
|
1196
|
+
keep, drop = set(), set()
|
|
1197
|
+
for s1, s2 in to_check:
|
|
1198
|
+
if s1 not in keep:
|
|
1199
|
+
if s2 not in keep:
|
|
1200
|
+
keep.add(s1)
|
|
1201
|
+
drop.add(s2)
|
|
1202
|
+
else:
|
|
1203
|
+
drop.add(s1)
|
|
1204
|
+
else:
|
|
1205
|
+
keep.discard(s2)
|
|
1206
|
+
drop.add(s2)
|
|
1207
|
+
return df.drop(drop, axis=1)
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
def check_stationarity(df: pd.DataFrame):
|
|
1211
|
+
results = []
|
|
1212
|
+
for ticker, prices in df.items():
|
|
1213
|
+
results.append([ticker, adfuller(prices, regression='ct')[1]])
|
|
1214
|
+
return pd.DataFrame(results, columns=['ticker', 'adf']).sort_values('adf')
|
|
1215
|
+
|
|
1216
|
+
|
|
1217
|
+
def remove_stationary_assets(df: pd.DataFrame, pval=.05):
|
|
1218
|
+
test_result = check_stationarity(df)
|
|
1219
|
+
stationary = test_result.loc[test_result.adf <= pval, 'ticker'].tolist()
|
|
1220
|
+
return df.drop(stationary, axis=1).sort_index()
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
def select_assets(df: pd.DataFrame, n=100, start=None, end=None):
|
|
1224
|
+
idx = pd.IndexSlice
|
|
1225
|
+
start = start or df.index.get_level_values('date').min()
|
|
1226
|
+
end = end or df.index.get_level_values('date').max()
|
|
1227
|
+
df = (df
|
|
1228
|
+
.loc[lambda df: ~df.index.duplicated()]
|
|
1229
|
+
.sort_index()
|
|
1230
|
+
.loc[idx[:, f'{start}':f'{end}'], :]
|
|
1231
|
+
.assign(dv=lambda df: df.close.mul(df.volume)))
|
|
1232
|
+
|
|
1233
|
+
# select n assets with the highest average trading volume
|
|
1234
|
+
# we are taking a shortcut to simplify; should select
|
|
1235
|
+
# based on historical only, e.g. yearly rolling avg
|
|
1236
|
+
most_traded = (df.groupby(level='ticker')
|
|
1237
|
+
.dv.mean()
|
|
1238
|
+
.nlargest(n=n).index)
|
|
1239
|
+
|
|
1240
|
+
df = (df.loc[idx[most_traded, :], 'close']
|
|
1241
|
+
.unstack('ticker')
|
|
1242
|
+
.ffill(limit=5) # fill up to five values
|
|
1243
|
+
.dropna(axis=1)) # remove assets with any missing values
|
|
1244
|
+
|
|
1245
|
+
df = remove_correlated_assets(df)
|
|
1246
|
+
return remove_stationary_assets(df).sort_index()
|
|
1247
|
+
|
|
1248
|
+
def compute_pair_metrics(security: pd.Series, candidates: pd.DataFrame):
|
|
1249
|
+
security = security.div(security.iloc[0])
|
|
1250
|
+
ticker = security.name
|
|
1251
|
+
candidates = candidates.div(candidates.iloc[0])
|
|
1252
|
+
spreads = candidates.sub(security, axis=0)
|
|
1253
|
+
n, m = spreads.shape
|
|
1254
|
+
X = np.ones(shape=(n, 2))
|
|
1255
|
+
X[:, 1] = np.arange(1, n + 1)
|
|
1256
|
+
|
|
1257
|
+
# compute drift
|
|
1258
|
+
drift = ((np.linalg.inv(X.T @ X) @ X.T @ spreads).iloc[1]
|
|
1259
|
+
.to_frame('drift'))
|
|
1260
|
+
|
|
1261
|
+
# compute volatility
|
|
1262
|
+
vol = spreads.std().to_frame('vol')
|
|
1263
|
+
|
|
1264
|
+
# return correlation
|
|
1265
|
+
corr_ret = (candidates.pct_change()
|
|
1266
|
+
.corrwith(security.pct_change())
|
|
1267
|
+
.to_frame('corr_ret'))
|
|
1268
|
+
|
|
1269
|
+
# normalized price series correlation
|
|
1270
|
+
corr = candidates.corrwith(security).to_frame('corr')
|
|
1271
|
+
metrics = drift.join(vol).join(corr).join(corr_ret).assign(n=n)
|
|
1272
|
+
|
|
1273
|
+
tests = []
|
|
1274
|
+
# run cointegration tests
|
|
1275
|
+
for candidate, prices in tqdm(candidates.items()):
|
|
1276
|
+
df = pd.DataFrame({'s1': security, 's2': prices})
|
|
1277
|
+
var = VAR(df.values)
|
|
1278
|
+
lags = var.select_order() # select VAR order
|
|
1279
|
+
k_ar_diff = lags.selected_orders['aic']
|
|
1280
|
+
# Johansen Test with constant Term and estd. lag order
|
|
1281
|
+
cj0 = coint_johansen(df, det_order=0, k_ar_diff=k_ar_diff)
|
|
1282
|
+
# Engle-Granger Tests
|
|
1283
|
+
t1, p1 = coint(security, prices, trend='c')[:2]
|
|
1284
|
+
t2, p2 = coint(prices, security, trend='c')[:2]
|
|
1285
|
+
tests.append([ticker, candidate, t1, p1, t2, p2,
|
|
1286
|
+
k_ar_diff, *cj0.lr1])
|
|
1287
|
+
columns = ['s1', 's2', 't1', 'p1', 't2', 'p2', 'k_ar_diff', 'trace0', 'trace1']
|
|
1288
|
+
tests = pd.DataFrame(tests, columns=columns).set_index('s2')
|
|
1289
|
+
return metrics.join(tests)
|
|
1290
|
+
|
|
1291
|
+
CRITICAL_VALUES = {
|
|
1292
|
+
0: {.9: 13.4294, .95: 15.4943, .99: 19.9349},
|
|
1293
|
+
1: {.9: 2.7055, .95: 3.8415, .99: 6.6349}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
def find_cointegrated_pairs(securities: pd.DataFrame, candidates: pd.DataFrame,
|
|
1297
|
+
n=None, start=None, stop=None):
|
|
1298
|
+
trace0_cv = CRITICAL_VALUES[0][.95] # critical value for 0 cointegration relationships
|
|
1299
|
+
trace1_cv = CRITICAL_VALUES[1][.95] # critical value for 1 cointegration relationship
|
|
1300
|
+
spreads = []
|
|
1301
|
+
if start is not None and stop is not None:
|
|
1302
|
+
securities = securities.loc[str(start): str(stop), :]
|
|
1303
|
+
candidates = candidates.loc[str(start): str(stop), :]
|
|
1304
|
+
for i, (ticker, prices) in enumerate(securities.items(), 1):
|
|
1305
|
+
df = compute_pair_metrics(prices, candidates)
|
|
1306
|
+
spreads.append(df.set_index('s1', append=True))
|
|
1307
|
+
spreads = pd.concat(spreads)
|
|
1308
|
+
spreads.index.names = ['s2', 's1']
|
|
1309
|
+
spreads = spreads.swaplevel()
|
|
1310
|
+
spreads['t'] = spreads[['t1', 't2']].min(axis=1)
|
|
1311
|
+
spreads['p'] = spreads[['p1', 'p2']].min(axis=1)
|
|
1312
|
+
spreads['joh_sig'] = ((spreads.trace0 > trace0_cv) &
|
|
1313
|
+
(spreads.trace1 > trace1_cv)).astype(int)
|
|
1314
|
+
spreads['eg_sig'] = (spreads.p < .05).astype(int)
|
|
1315
|
+
spreads['s1_dep'] = spreads.p1 < spreads.p2
|
|
1316
|
+
spreads['coint'] = (spreads.joh_sig & spreads.eg_sig).astype(int)
|
|
1317
|
+
# select top n pairs
|
|
1318
|
+
if n is not None:
|
|
1319
|
+
top_pairs = (spreads.query('coint == 1')
|
|
1320
|
+
.sort_values('t', ascending=False)
|
|
1321
|
+
.head(n))
|
|
1322
|
+
else:
|
|
1323
|
+
top_pairs = spreads.query('coint == 1')
|
|
1324
|
+
return top_pairs
|
|
1325
|
+
|
|
1326
|
+
def analyze_cointegrated_pairs(spreads: pd.DataFrame, plot_coint=False, cosstab=False,
|
|
1327
|
+
heuristics=False, log_reg=False, decis_tree=False):
|
|
1328
|
+
if plot_coint:
|
|
1329
|
+
trace0_cv = CRITICAL_VALUES[0][.95]
|
|
1330
|
+
spreads = spreads.reset_index()
|
|
1331
|
+
sns.scatterplot(x=np.log1p(spreads.t.abs()),
|
|
1332
|
+
y=np.log1p(spreads.trace1),
|
|
1333
|
+
hue='coint', data=spreads[spreads.trace0>trace0_cv]);
|
|
1334
|
+
fig, axes = plt.subplots(ncols=4, figsize=(20, 5))
|
|
1335
|
+
for i, heuristic in enumerate(['drift', 'vol', 'corr', 'corr_ret']):
|
|
1336
|
+
sns.boxplot(x='coint', y=heuristic, data=spreads, ax=axes[i])
|
|
1337
|
+
fig.tight_layout();
|
|
1338
|
+
if heuristics:
|
|
1339
|
+
spreads = spreads.reset_index()
|
|
1340
|
+
h = spreads.groupby(spreads.coint)[
|
|
1341
|
+
['drift', 'vol', 'corr']].describe().stack(level=0).swaplevel().sort_index()
|
|
1342
|
+
print(h)
|
|
1343
|
+
if log_reg:
|
|
1344
|
+
y = spreads.coint
|
|
1345
|
+
X = spreads[['drift', 'vol', 'corr', 'corr_ret']]
|
|
1346
|
+
log_reg = LogisticRegressionCV(Cs=np.logspace(-10, 10, 21),
|
|
1347
|
+
class_weight='balanced',
|
|
1348
|
+
scoring='roc_auc')
|
|
1349
|
+
log_reg.fit(X=X, y=y)
|
|
1350
|
+
Cs = log_reg.Cs_
|
|
1351
|
+
scores = pd.DataFrame(log_reg.scores_[True], columns=Cs).mean()
|
|
1352
|
+
scores.plot(logx=True);
|
|
1353
|
+
res = f'C:{np.log10(scores.idxmax()):.2f}, AUC: {scores.max():.2%}'
|
|
1354
|
+
print(res)
|
|
1355
|
+
print(log_reg.coef_)
|
|
1356
|
+
if decis_tree:
|
|
1357
|
+
model = DecisionTreeClassifier(class_weight='balanced')
|
|
1358
|
+
decision_tree = GridSearchCV(model,
|
|
1359
|
+
param_grid={'max_depth': list(range(1, 10))},
|
|
1360
|
+
cv=5,
|
|
1361
|
+
scoring='roc_auc')
|
|
1362
|
+
y = spreads.coint
|
|
1363
|
+
X = spreads[['drift', 'vol', 'corr', 'corr_ret']]
|
|
1364
|
+
decision_tree.fit(X, y)
|
|
1365
|
+
res = f'{decision_tree.best_score_:.2%}, Depth: {decision_tree.best_params_["max_depth"]}'
|
|
1366
|
+
print(res)
|
|
1367
|
+
if cosstab:
|
|
1368
|
+
pd.set_option('display.float_format', lambda x: f'{x:.2%}')
|
|
1369
|
+
print(pd.crosstab(spreads.eg_sig, spreads.joh_sig))
|
|
1370
|
+
print(pd.crosstab(spreads.eg_sig, spreads.joh_sig, normalize=True))
|
|
1371
|
+
|
|
1372
|
+
|
|
1373
|
+
def select_candidate_pairs(pairs: pd.DataFrame):
|
|
1374
|
+
candidates = pairs.query('coint == 1').copy()
|
|
1375
|
+
candidates['y'] = candidates.apply(lambda x: x.s1 if x.s1_dep else x.s2, axis=1)
|
|
1376
|
+
candidates['x'] = candidates.apply(lambda x: x.s2 if x.s1_dep else x.s1, axis=1)
|
|
1377
|
+
candidates.drop(['s1_dep', 's1', 's2'], axis=1)
|
|
1378
|
+
return candidates[['x', 'y']].to_dict(orient='records')
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
def KFSmoother(self, prices: pd.Series | np.ndarray) -> pd.Series | np.ndarray:
|
|
1382
|
+
"""Estimate rolling mean using Kalman Smoothing."""
|
|
1383
|
+
kf = PyKalmanFilter(
|
|
1384
|
+
transition_matrices=np.eye(1),
|
|
1385
|
+
observation_matrices=np.eye(1),
|
|
1386
|
+
initial_state_mean=0,
|
|
1387
|
+
initial_state_covariance=1,
|
|
1388
|
+
observation_covariance=1,
|
|
1389
|
+
transition_covariance=0.05
|
|
1390
|
+
)
|
|
1391
|
+
if isinstance(prices, pd.Series):
|
|
1392
|
+
state_means, _ = kf.filter(prices.values)
|
|
1393
|
+
return pd.Series(state_means.flatten(), index=prices.index)
|
|
1394
|
+
elif isinstance(prices, np.ndarray):
|
|
1395
|
+
state_means, _ = kf.filter(prices)
|
|
1396
|
+
return state_means.flatten()
|
|
1397
|
+
|
|
1398
|
+
|
|
1399
|
+
def KFHedgeRatio(self, x: pd.Series, y: pd.Series) -> np.ndarray:
|
|
1400
|
+
"""Estimate Hedge Ratio using Kalman Filter."""
|
|
1401
|
+
delta = 1e-3
|
|
1402
|
+
trans_cov = delta / (1 - delta) * np.eye(2)
|
|
1403
|
+
obs_mat = np.expand_dims(np.vstack([[x], [np.ones(len(x))]]).T, axis=1)
|
|
1404
|
+
|
|
1405
|
+
kf = PyKalmanFilter(
|
|
1406
|
+
n_dim_obs=1, n_dim_state=2,
|
|
1407
|
+
initial_state_mean=[0, 0],
|
|
1408
|
+
initial_state_covariance=np.ones((2, 2)),
|
|
1409
|
+
transition_matrices=np.eye(2),
|
|
1410
|
+
observation_matrices=obs_mat,
|
|
1411
|
+
observation_covariance=2,
|
|
1412
|
+
transition_covariance=trans_cov
|
|
1413
|
+
)
|
|
1414
|
+
state_means, _ = kf.filter(y.values)
|
|
1415
|
+
# Indexing with [:, 0] in state_means[:, 0] extracts only the first state variable of
|
|
1416
|
+
# each Kalman Filter estimate, which is the estimated hedge ratio.
|
|
1417
|
+
return -state_means[:, 0]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bbstrader
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.91
|
|
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/
|
|
@@ -34,12 +34,20 @@ Requires-Dist: seaborn
|
|
|
34
34
|
Requires-Dist: statsmodels
|
|
35
35
|
Requires-Dist: matplotlib
|
|
36
36
|
Requires-Dist: filterpy
|
|
37
|
+
Requires-Dist: pykalman
|
|
37
38
|
Requires-Dist: pytest
|
|
38
39
|
Requires-Dist: CurrencyConverter
|
|
39
40
|
Requires-Dist: tabulate
|
|
41
|
+
Requires-Dist: python-dotenv
|
|
40
42
|
Requires-Dist: ipython
|
|
41
|
-
Requires-Dist:
|
|
42
|
-
Requires-Dist:
|
|
43
|
+
Requires-Dist: QuantStats
|
|
44
|
+
Requires-Dist: exchange-calendars
|
|
45
|
+
Requires-Dist: tqdm
|
|
46
|
+
Requires-Dist: scikit-learn
|
|
47
|
+
Requires-Dist: notify-py
|
|
48
|
+
Requires-Dist: python-telegram-bot
|
|
49
|
+
Provides-Extra: mt5
|
|
50
|
+
Requires-Dist: MetaTrader5 ; extra == 'mt5'
|
|
43
51
|
|
|
44
52
|
# Simplified Investment & Trading Toolkit
|
|
45
53
|

|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
bbstrader/__ini__.py,sha256=rCTy-3g2RlDAgIZ7cSET9-I74MwuCXpp-xGVTFS8NNc,482
|
|
2
|
+
bbstrader/config.py,sha256=_AD_Cd-w5zyabm1CBPNGhzcZuSjThB7jyzTcjbrIlUQ,3618
|
|
3
|
+
bbstrader/tseries.py,sha256=qJKLxHnPOjB7dXon-ITK7vU1fAuvl8evzET6lSSnijQ,53572
|
|
4
|
+
bbstrader/btengine/__init__.py,sha256=OaXZTjgDwqWrjPq-CNE4kJkmriKXt9t5pIghW1MDTeo,2911
|
|
5
|
+
bbstrader/btengine/backtest.py,sha256=HDuCNUETLUBZ0R8AVLeT5gZVVk8UQ6oeReUtDvUFhIk,13681
|
|
6
|
+
bbstrader/btengine/data.py,sha256=zeF3O7_vPT3NvAxlAcUS9OoxF09XhypDhGiVRml3U1A,17622
|
|
7
|
+
bbstrader/btengine/event.py,sha256=DQTmUQ4l4yZgL47hW1dGv__CecNF-rDqsyFst5kCQvA,8453
|
|
8
|
+
bbstrader/btengine/execution.py,sha256=gijnRvknNi921TjtF9wSyBG_nI0e2cda2NKIm3LJh1k,10222
|
|
9
|
+
bbstrader/btengine/performance.py,sha256=bKwj1_CSygvggLKTXPASp2eWhDdwyCf06ayUaXwdh4E,10655
|
|
10
|
+
bbstrader/btengine/portfolio.py,sha256=uf1fx0RuTTQNNxiYmCYW_sh6sBWbtNq4uIRusKA5MdY,15399
|
|
11
|
+
bbstrader/btengine/strategy.py,sha256=ktTzlJzSTo9zhZsILwj2vtPHjAkI-aSnKA8PcT3nF6Y,23206
|
|
12
|
+
bbstrader/metatrader/__init__.py,sha256=OLVOB_EieEb1P72I8V4Vem8kQWJ__D_L3c_wfwqY-9k,211
|
|
13
|
+
bbstrader/metatrader/account.py,sha256=hVH83vnAdfMOzUsF9PiWelqxa7HaLSTpCVlUEePnSZg,53912
|
|
14
|
+
bbstrader/metatrader/rates.py,sha256=xWTsM8PMPGdgwoPXcIRvHpJ1FKRIXHJcn4qrgmA7XRg,18737
|
|
15
|
+
bbstrader/metatrader/risk.py,sha256=8FcLY8pgV8_rxAcjx179sdqaMu66wl-fDFPZvdihfUw,25953
|
|
16
|
+
bbstrader/metatrader/trade.py,sha256=68hYIA01OPApRgGNdS1YFgM83ZemkCmv6mTXOCS_kWc,70052
|
|
17
|
+
bbstrader/metatrader/utils.py,sha256=BTaZun4DKWpCxBBzY0SLQqqz7n_7F_R1F59APfyaa3E,17666
|
|
18
|
+
bbstrader/models/__init__.py,sha256=6tAj9V9vgwesgPVMKznwRB3k8-Ec8Q73Di5p2UO0qlA,274
|
|
19
|
+
bbstrader/models/factors.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
bbstrader/models/ml.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
bbstrader/models/optimization.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
bbstrader/models/risk.py,sha256=Pm_WoGI-vtPW75fwo_7ptF2Br-xQYBwrAAOIgqDQmy8,15120
|
|
23
|
+
bbstrader/trading/__init__.py,sha256=3CCzV5rQbH8NthjDJhD0_2FABvpiCmkeC9cVeoW7bi4,438
|
|
24
|
+
bbstrader/trading/execution.py,sha256=5mGanLIB1nPfbXA9r_uKGDCkcV-ilVgZmssF7Oh4SeI,25186
|
|
25
|
+
bbstrader/trading/scripts.py,sha256=rQmnG_4F_MuUEc96RXpAQT4kXrC-FkscsgHKgDAR_-Y,1902
|
|
26
|
+
bbstrader/trading/strategies.py,sha256=ztKNL4Nmlb-4N8_cq0OJyn3E2cRcdKdKu3FeTbZrHsU,36402
|
|
27
|
+
bbstrader-0.1.91.dist-info/LICENSE,sha256=1EudjwwP2oTJy8Vh0e-Kzv8VZZU95y-t6c3DYhR51uc,1115
|
|
28
|
+
bbstrader-0.1.91.dist-info/METADATA,sha256=ZwHR-DYmIhULp_5dh48oQ_lGR0VYQnkGG8GcaQe1KP4,9901
|
|
29
|
+
bbstrader-0.1.91.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
30
|
+
bbstrader-0.1.91.dist-info/top_level.txt,sha256=Wwj322jZmxGZ6gD_TdaPiPLjED5ReObm5omerwlmZIg,10
|
|
31
|
+
bbstrader-0.1.91.dist-info/RECORD,,
|
bbstrader-0.1.9.dist-info/RECORD
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
bbstrader/__ini__.py,sha256=lKhqaZN2fuGU2DP3oufJ_XcFlznnyNTlhklWcOaTPMY,444
|
|
2
|
-
bbstrader/tseries.py,sha256=i6Vl5QeySMj51n4EchvAaGnHl1xQLdvO7Eka3QkP_Mg,43647
|
|
3
|
-
bbstrader/btengine/__init__.py,sha256=gNANXdJ_b4JQaHgqXTbRSZzJOdZRQop5258GQYIAY8s,2949
|
|
4
|
-
bbstrader/btengine/backtest.py,sha256=bUnuxHByddDkY2VzCTSBBOIbdRzsy9VXROFYL3VNwzE,12450
|
|
5
|
-
bbstrader/btengine/data.py,sha256=B2cqkZZGIhedqMHeZBNB6RHGyeP-TFGOSlMoZq4uFUw,14007
|
|
6
|
-
bbstrader/btengine/event.py,sha256=dY2DFwRPDCEdsX7GnLiwpth73tOpnbjgqIqbkNMZu1U,8286
|
|
7
|
-
bbstrader/btengine/execution.py,sha256=noat6ZxhbBYZQ0GLFYw78enp0vZCbeoGyOok4aSjWCA,5077
|
|
8
|
-
bbstrader/btengine/performance.py,sha256=STb5xJQiTa25xZdYYmjpKW46iWWcB_-6wB6PCPbhREI,10745
|
|
9
|
-
bbstrader/btengine/portfolio.py,sha256=LFsl38GbdYTJwd08ep1n4_1FPsRodQz1DHDPVSj-rBU,14882
|
|
10
|
-
bbstrader/btengine/strategy.py,sha256=uLQdsEzI8-86Cj7HWkL8blVFxT0sNxEcLVnkBil6ZLk,1550
|
|
11
|
-
bbstrader/metatrader/__init__.py,sha256=y4JLz05esm3PKerHMgtd3dmRpa7yUvWVuj_xOnlhXSA,261
|
|
12
|
-
bbstrader/metatrader/account.py,sha256=5f6emtVEvXmxUnlmqFpC4hhL8sxOzOrdp7_pwJhXPhw,44716
|
|
13
|
-
bbstrader/metatrader/rates.py,sha256=FyG4WWq1NaCdHNxEW_8u3eOt0Wms7E_U4FIOIcUDWco,9605
|
|
14
|
-
bbstrader/metatrader/risk.py,sha256=Nz3RqkVFNA5FeXIqINjmGUaj88YaYGw23XsVOvaHjB8,25831
|
|
15
|
-
bbstrader/metatrader/trade.py,sha256=M5ho8x_NRVCEWO_dmDCiJrhhbcfpnia6tOmQRfV7p4U,62967
|
|
16
|
-
bbstrader/metatrader/utils.py,sha256=xc5VYQ-eejpHIPJIhLi8vxAF5zjix5NSXywXu2Eb7LI,19186
|
|
17
|
-
bbstrader/models/__init__.py,sha256=6tAj9V9vgwesgPVMKznwRB3k8-Ec8Q73Di5p2UO0qlA,274
|
|
18
|
-
bbstrader/models/risk.py,sha256=Pm_WoGI-vtPW75fwo_7ptF2Br-xQYBwrAAOIgqDQmy8,15120
|
|
19
|
-
bbstrader/trading/__init__.py,sha256=3_PeIcuQ7znoCPdq8FqRmbCc0czNiernpIRkBXJZvg0,452
|
|
20
|
-
bbstrader/trading/execution.py,sha256=Y3iKLbgCNVLw2XTDUM8Mv6hlcO8H83VyMKlZgjAKX2c,20744
|
|
21
|
-
bbstrader/trading/strategies.py,sha256=uYVeFeqVHHYrkgk4Eiziivj0iIO0RQKXCAoOSQ4XVRE,36972
|
|
22
|
-
bbstrader-0.1.9.dist-info/LICENSE,sha256=1EudjwwP2oTJy8Vh0e-Kzv8VZZU95y-t6c3DYhR51uc,1115
|
|
23
|
-
bbstrader-0.1.9.dist-info/METADATA,sha256=aJILSlgr7yOdyozYyZPseOUUy7WkJcgHUv-TnudXSvY,9660
|
|
24
|
-
bbstrader-0.1.9.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
25
|
-
bbstrader-0.1.9.dist-info/top_level.txt,sha256=Wwj322jZmxGZ6gD_TdaPiPLjED5ReObm5omerwlmZIg,10
|
|
26
|
-
bbstrader-0.1.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|