Compare commits

..

2 Commits

Author SHA1 Message Date
Oleg Sheynin
35a1cd748e notebook changes 2025-07-14 05:15:47 +00:00
Oleg Sheynin
3b003c7811 progress 2025-07-14 00:41:46 +00:00
7 changed files with 3271 additions and 120 deletions

View File

@ -7,16 +7,6 @@
"db_table_name": "md_1min_bars",
"exchange_id": "BNBSPOT",
"instrument_id_pfx": "PAIR-",
# "instruments": [
# "BTC-USDT",
# "BCH-USDT",
# "ETH-USDT",
# "LTC-USDT",
# "XRP-USDT",
# "ADA-USDT",
# "SOL-USDT",
# "DOT-USDT"
# ],
"trading_hours": {
"begin_session": "00:00:00",
"end_session": "23:59:00",
@ -29,5 +19,5 @@
"dis-equilibrium_close_trshld": 0.5,
"training_minutes": 120,
"funding_per_pair": 2000.0,
"fit_method_class": "pt_trading.fit_methods.StaticFit"
"fit_method_class": "pt_trading.fit_methods.SlidingFit"
}

View File

@ -3,7 +3,6 @@ from enum import Enum
from typing import Dict, Optional, cast
import pandas as pd # type: ignore[import]
from pt_trading.results import BacktestResult
from pt_trading.trading_pair import TradingPair
@ -64,6 +63,17 @@ class StaticFit(PairsTradingFitMethod):
colname_a, colname_b = pair.colnames()
predicted_df = pair.predicted_df_
if predicted_df is None:
# Return empty DataFrame with correct columns and dtypes
return pd.DataFrame(columns=self.TRADES_COLUMNS).astype({
"time": "datetime64[ns]",
"action": "string",
"symbol": "string",
"price": "float64",
"disequilibrium": "float64",
"scaled_disequilibrium": "float64",
"pair": "object"
})
open_threshold = config["dis-equilibrium_open_trshld"]
close_threshold = config["dis-equilibrium_close_trshld"]
@ -96,11 +106,11 @@ class StaticFit(PairsTradingFitMethod):
break
open_row = predicted_df.loc[open_row_index]
open_tstamp = open_row["tstamp"]
open_px_a = predicted_df.at[open_row_index, f"{colname_a}"]
open_px_b = predicted_df.at[open_row_index, f"{colname_b}"]
open_tstamp = predicted_df.at[open_row_index, "tstamp"]
open_disequilibrium = open_row["disequilibrium"]
open_scaled_disequilibrium = open_row["scaled_disequilibrium"]
open_px_a = open_row[f"{colname_a}"]
open_px_b = open_row[f"{colname_b}"]
abs_beta = abs(beta[1])
pred_px_b = predicted_df.loc[open_row_index][f"{colname_b}_pred"]
@ -129,9 +139,9 @@ class StaticFit(PairsTradingFitMethod):
last_row_index=last_row_index,
open_side_a=open_side_a,
open_side_b=open_side_b,
open_px_a=open_px_a,
open_px_b=open_px_b,
open_tstamp=open_tstamp,
open_px_a=float(open_px_a),
open_px_b=float(open_px_b),
open_tstamp=pd.Timestamp(open_tstamp),
)
# Return only open trades (no close trades)
@ -205,11 +215,21 @@ class StaticFit(PairsTradingFitMethod):
),
]
# Add tuples to data frame
return pd.DataFrame(
# Add tuples to data frame with explicit dtypes to avoid concatenation warnings
df = pd.DataFrame(
trd_signal_tuples,
columns=self.TRADES_COLUMNS, # type: ignore
columns=self.TRADES_COLUMNS,
)
# Ensure consistent dtypes
return df.astype({
"time": "datetime64[ns]",
"action": "string",
"symbol": "string",
"price": "float64",
"disequilibrium": "float64",
"scaled_disequilibrium": "float64",
"pair": "object"
})
def reset(self) -> None:
pass
@ -232,7 +252,16 @@ class SlidingFit(PairsTradingFitMethod):
print(f"***{pair}*** STARTING....")
pair.user_data_["state"] = PairState.INITIAL
pair.user_data_["trades"] = pd.DataFrame(columns=self.TRADES_COLUMNS)
# Initialize trades DataFrame with proper dtypes to avoid concatenation warnings
pair.user_data_["trades"] = pd.DataFrame(columns=self.TRADES_COLUMNS).astype({
"time": "datetime64[ns]",
"action": "string",
"symbol": "string",
"price": "float64",
"disequilibrium": "float64",
"scaled_disequilibrium": "float64",
"pair": "object"
})
pair.user_data_["is_cointegrated"] = False
training_minutes = config["training_minutes"]
@ -255,17 +284,17 @@ class SlidingFit(PairsTradingFitMethod):
)
# outstanding positions
# last_row_index = self.curr_training_start_idx_ + training_minutes
bt_result.handle_outstanding_position(
pair=pair,
pair_result_df=pair.predicted_df_,
last_row_index=0,
open_side_a=pair.user_data_["open_side_a"],
open_side_b=pair.user_data_["open_side_b"],
open_px_a=pair.user_data_["open_px_a"],
open_px_b=pair.user_data_["open_px_b"],
open_tstamp=pair.user_data_["open_tstamp"],
)
if pair.predicted_df_ is not None:
bt_result.handle_outstanding_position(
pair=pair,
pair_result_df=pair.predicted_df_,
last_row_index=0,
open_side_a=pair.user_data_["open_side_a"],
open_side_b=pair.user_data_["open_side_b"],
open_px_a=pair.user_data_["open_px_a"],
open_px_b=pair.user_data_["open_px_b"],
open_tstamp=pair.user_data_["open_tstamp"],
)
break
try:
@ -311,7 +340,10 @@ class SlidingFit(PairsTradingFitMethod):
def _create_trading_signals(
self, pair: TradingPair, config: Dict, bt_result: BacktestResult
) -> None:
assert pair.predicted_df_ is not None
if pair.predicted_df_ is None:
print(f"{pair.market_data_.iloc[0]['tstamp']} {pair}: No predicted data")
return
open_threshold = config["dis-equilibrium_open_trshld"]
close_threshold = config["dis-equilibrium_close_trshld"]
for curr_predicted_row_idx in range(len(pair.predicted_df_)):
@ -353,6 +385,16 @@ class SlidingFit(PairsTradingFitMethod):
open_scaled_disequilibrium = open_row["scaled_disequilibrium"]
open_px_a = open_row[f"{colname_a}"]
open_px_b = open_row[f"{colname_b}"]
# Ensure scalars for handle_outstanding_position
if isinstance(open_px_a, pd.Series):
open_px_a = open_px_a.iloc[0]
if isinstance(open_px_b, pd.Series):
open_px_b = open_px_b.iloc[0]
if isinstance(open_tstamp, pd.Series):
open_tstamp = open_tstamp.iloc[0]
open_px_a = float(open_px_a)
open_px_b = float(open_px_b)
open_tstamp = pd.Timestamp(open_tstamp)
if open_scaled_disequilibrium < open_threshold:
return None
@ -402,10 +444,21 @@ class SlidingFit(PairsTradingFitMethod):
pair,
),
]
return pd.DataFrame(
# Create DataFrame with explicit dtypes to avoid concatenation warnings
df = pd.DataFrame(
trd_signal_tuples,
columns=self.TRADES_COLUMNS, # type: ignore
columns=self.TRADES_COLUMNS,
)
# Ensure consistent dtypes
return df.astype({
"time": "datetime64[ns]",
"action": "string",
"symbol": "string",
"price": "float64",
"disequilibrium": "float64",
"scaled_disequilibrium": "float64",
"pair": "object"
})
def _get_close_trades(
self, pair: TradingPair, row: pd.Series, close_threshold: float
@ -449,11 +502,21 @@ class SlidingFit(PairsTradingFitMethod):
),
]
# Add tuples to data frame
return pd.DataFrame(
# Add tuples to data frame with explicit dtypes to avoid concatenation warnings
df = pd.DataFrame(
trd_signal_tuples,
columns=self.TRADES_COLUMNS, # type: ignore
columns=self.TRADES_COLUMNS,
)
# Ensure consistent dtypes
return df.astype({
"time": "datetime64[ns]",
"action": "string",
"symbol": "string",
"price": "float64",
"disequilibrium": "float64",
"scaled_disequilibrium": "float64",
"pair": "object"
})
def reset(self) -> None:
self.curr_training_start_idx_ = 0

View File

@ -41,6 +41,12 @@ def create_result_database(db_path: str) -> None:
Create the SQLite database and required tables if they don't exist.
"""
try:
# Create directory if it doesn't exist
db_dir = os.path.dirname(db_path)
if db_dir and not os.path.exists(db_dir):
os.makedirs(db_dir, exist_ok=True)
print(f"Created directory: {db_dir}")
conn = sqlite3.connect(db_path)
cursor = conn.cursor()

View File

@ -179,10 +179,39 @@ class TradingPair:
return result
def add_trades(self, trades: pd.DataFrame) -> None:
if self.user_data_["trades"] is None:
self.user_data_["trades"] = pd.DataFrame(trades)
if self.user_data_["trades"] is None or len(self.user_data_["trades"]) == 0:
# If trades is empty or None, just assign the new trades directly
self.user_data_["trades"] = trades.copy()
else:
self.user_data_["trades"] = pd.concat([self.user_data_["trades"], pd.DataFrame(trades)], ignore_index=True)
# Ensure both DataFrames have the same columns and dtypes before concatenation
existing_trades = self.user_data_["trades"]
# If existing trades is empty, just assign the new trades
if len(existing_trades) == 0:
self.user_data_["trades"] = trades.copy()
else:
# Ensure both DataFrames have the same columns
if set(existing_trades.columns) != set(trades.columns):
# Add missing columns to trades with appropriate default values
for col in existing_trades.columns:
if col not in trades.columns:
if col == "time":
trades[col] = pd.Timestamp.now()
elif col in ["action", "symbol"]:
trades[col] = ""
elif col in ["price", "disequilibrium", "scaled_disequilibrium"]:
trades[col] = 0.0
elif col == "pair":
trades[col] = None
else:
trades[col] = None
# Concatenate with explicit dtypes to avoid warnings
self.user_data_["trades"] = pd.concat(
[existing_trades, trades],
ignore_index=True,
copy=False
)
def get_trades(self) -> pd.DataFrame:
return self.user_data_["trades"] if "trades" in self.user_data_ else pd.DataFrame()

View File

@ -24,11 +24,14 @@ hjson>=3.0.2
html5lib>=1.1
httplib2>=0.20.2
idna>=3.3
ipython>=8.18.1
ipywidgets>=8.1.1
ifaddr>=0.1.7
IMDbPY>=2021.4.18
ipykernel>=6.29.5
jeepney>=0.7.1
jsonschema>=3.2.0
jupyter>=1.0.0
keyring>=23.5.0
launchpadlib>=1.10.16
lazr.restfulclient>=0.14.4
@ -42,15 +45,18 @@ more-itertools>=8.10.0
multidict>=6.0.4
mypy>=0.942
mypy-extensions>=0.4.3
nbformat>=5.10.2
netaddr>=0.8.0
######### netifaces>=0.11.0
numpy>=1.26.4,<2.3.0
oauthlib>=3.2.0
packaging>=23.1
pandas>=2.2.3
pathspec>=0.11.1
pexpect>=4.8.0
Pillow>=9.0.1
platformdirs>=3.2.0
plotly>=5.19.0
protobuf>=3.12.4
psutil>=5.9.0
ptyprocess>=0.7.0

File diff suppressed because one or more lines are too long