This commit is contained in:
Oleg Sheynin
2025-07-24 21:09:13 +00:00
parent a7b4777f76
commit af0a6f62a9
9 changed files with 247 additions and 6224 deletions
+4 -5
View File
@@ -121,7 +121,7 @@ def store_config_in_database(
config_file_path: str,
config: Dict,
fit_method_class: str,
datafiles: List[str],
datafiles: List[Tuple[str, str]],
instruments: List[Dict[str, str]],
) -> None:
"""
@@ -140,7 +140,7 @@ def store_config_in_database(
config_json = json.dumps(config, indent=2, default=str)
# Convert lists to comma-separated strings for storage
datafiles_str = ", ".join(datafiles)
datafiles_str = ", ".join([f"{datafile}" for _, datafile in datafiles])
instruments_str = ", ".join(
[
f"{inst['symbol']}:{inst['instrument_type']}:{inst['exchange_id']}"
@@ -613,7 +613,7 @@ class BacktestResult:
return current_value_a, current_value_b, total_current_value
def store_results_in_database(
self, db_path: str, datafile: str
self, db_path: str, day: str
) -> None:
"""
Store backtest results in the SQLite database.
@@ -623,8 +623,7 @@ class BacktestResult:
try:
# Extract date from datafile name (assuming format like 20250528.mktdata.ohlcv.db)
filename = os.path.basename(datafile)
date_str = filename.split(".")[0] # Extract date part
date_str = day
# Convert to proper date format
try:
+65 -38
View File
@@ -17,7 +17,7 @@ class PairState(Enum):
class CointegrationData:
EG_PVALUE_THRESHOLD = 0.05
tstamp_: pd.Timestamp
pair_: str
eg_pvalue_: float
@@ -63,7 +63,7 @@ class CointegrationData:
"johansen_cvt": self.johansen_cvt_,
"eg_is_cointegrated": self.eg_is_cointegrated_,
"johansen_is_cointegrated": self.johansen_is_cointegrated_,
}
}
def __repr__(self) -> str:
return f"CointegrationData(tstamp={self.tstamp_}, pair={self.pair_}, eg_pvalue={self.eg_pvalue_}, johansen_lr1={self.johansen_lr1_}, johansen_cvt={self.johansen_cvt_}, eg_is_cointegrated={self.eg_is_cointegrated_}, johansen_is_cointegrated={self.johansen_is_cointegrated_})"
@@ -86,7 +86,12 @@ class TradingPair(ABC):
# predicted_df_: Optional[pd.DataFrame]
def __init__(
self, config: Dict[str, Any], market_data: pd.DataFrame, symbol_a: str, symbol_b: str, price_column: str
self,
config: Dict[str, Any],
market_data: pd.DataFrame,
symbol_a: str,
symbol_b: str,
price_column: str,
):
self.symbol_a_ = symbol_a
self.symbol_b_ = symbol_b
@@ -102,25 +107,33 @@ class TradingPair(ABC):
)
self.market_data_ = self.market_data_.dropna().reset_index(drop=True)
self.market_data_['tstamp'] = pd.to_datetime(self.market_data_['tstamp'])
self.market_data_ = self.market_data_.sort_values('tstamp')
self.market_data_["tstamp"] = pd.to_datetime(self.market_data_["tstamp"])
self.market_data_ = self.market_data_.sort_values("tstamp")
def get_begin_index(self) -> int:
if "trading_hours" not in self.config_:
return 0
assert "timezone" in self.config_["trading_hours"]
assert "timezone" in self.config_["trading_hours"]
assert "begin_session" in self.config_["trading_hours"]
start_time = pd.to_datetime(self.config_["trading_hours"]["begin_session"]).tz_localize(self.config_["trading_hours"]["timezone"]).time()
mask = self.market_data_['tstamp'].dt.time >= start_time
start_time = (
pd.to_datetime(self.config_["trading_hours"]["begin_session"])
.tz_localize(self.config_["trading_hours"]["timezone"])
.time()
)
mask = self.market_data_["tstamp"].dt.time >= start_time
return int(self.market_data_.index[mask].min())
def get_end_index(self) -> int:
if "trading_hours" not in self.config_:
return 0
assert "timezone" in self.config_["trading_hours"]
assert "timezone" in self.config_["trading_hours"]
assert "end_session" in self.config_["trading_hours"]
end_time = pd.to_datetime(self.config_["trading_hours"]["end_session"]).tz_localize(self.config_["trading_hours"]["timezone"]).time()
mask = self.market_data_['tstamp'].dt.time <= end_time
end_time = (
pd.to_datetime(self.config_["trading_hours"]["end_session"])
.tz_localize(self.config_["trading_hours"]["timezone"])
.time()
)
mask = self.market_data_["tstamp"].dt.time <= end_time
return int(self.market_data_.index[mask].max())
def _transform_dataframe(self, df: pd.DataFrame) -> pd.DataFrame:
@@ -171,7 +184,7 @@ class TradingPair(ABC):
testing_start_index = training_start_index + training_minutes
self.training_df_ = self.market_data_.iloc[
training_start_index:testing_start_index, : training_minutes
training_start_index:testing_start_index, :training_minutes
].copy()
assert self.training_df_ is not None
self.training_df_ = self.training_df_.dropna().reset_index(drop=True)
@@ -199,7 +212,7 @@ class TradingPair(ABC):
else:
# 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()
@@ -213,22 +226,26 @@ class TradingPair(ABC):
trades[col] = pd.Timestamp.now()
elif col in ["action", "symbol"]:
trades[col] = ""
elif col in ["price", "disequilibrium", "scaled_disequilibrium"]:
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
[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()
return (
self.user_data_["trades"] if "trades" in self.user_data_ else pd.DataFrame()
)
def cointegration_check(self) -> Optional[pd.DataFrame]:
print(f"***{self}*** STARTING....")
@@ -237,17 +254,19 @@ class TradingPair(ABC):
curr_training_start_idx = 0
COINTEGRATION_DATA_COLUMNS = {
"tstamp" : "datetime64[ns]",
"pair" : "string",
"eg_pvalue" : "float64",
"johansen_lr1" : "float64",
"johansen_cvt" : "float64",
"eg_is_cointegrated" : "bool",
"johansen_is_cointegrated" : "bool",
"tstamp": "datetime64[ns]",
"pair": "string",
"eg_pvalue": "float64",
"johansen_lr1": "float64",
"johansen_cvt": "float64",
"eg_is_cointegrated": "bool",
"johansen_is_cointegrated": "bool",
}
# Initialize trades DataFrame with proper dtypes to avoid concatenation warnings
result: pd.DataFrame = pd.DataFrame(columns=[col for col in COINTEGRATION_DATA_COLUMNS.keys()]) #.astype(COINTEGRATION_DATA_COLUMNS)
result: pd.DataFrame = pd.DataFrame(
columns=[col for col in COINTEGRATION_DATA_COLUMNS.keys()]
) # .astype(COINTEGRATION_DATA_COLUMNS)
training_minutes = config["training_minutes"]
while True:
print(curr_training_start_idx, end="\r")
@@ -271,13 +290,16 @@ class TradingPair(ABC):
def to_stop_close_conditions(self, predicted_row: pd.Series) -> bool:
config = self.config_
if ("stop_close_conditions" not in config or config["stop_close_conditions"] is None) :
if (
"stop_close_conditions" not in config
or config["stop_close_conditions"] is None
):
return False
if "profit" in config["stop_close_conditions"]:
current_return = self._current_return(predicted_row)
#
# print(f"time={predicted_row['tstamp']} current_return={current_return}")
#
#
if current_return >= config["stop_close_conditions"]["profit"]:
print(f"STOP PROFIT: {current_return}")
self.user_data_["stop_close_state"] = PairState.CLOSE_STOP_PROFIT
@@ -288,9 +310,10 @@ class TradingPair(ABC):
self.user_data_["stop_close_state"] = PairState.CLOSE_STOP_LOSS
return True
return False
def on_open_trades(self, trades: pd.DataFrame) -> None:
if "close_trades" in self.user_data_: del self.user_data_["close_trades"]
if "close_trades" in self.user_data_:
del self.user_data_["close_trades"]
self.user_data_["open_trades"] = trades
def on_close_trades(self, trades: pd.DataFrame) -> None:
@@ -302,20 +325,25 @@ class TradingPair(ABC):
open_trades = self.user_data_["open_trades"]
if len(open_trades) == 0:
return 0.0
def _single_instrument_return(symbol: str) -> float:
instrument_open_trades = open_trades[open_trades["symbol"] == symbol]
instrument_open_price = instrument_open_trades["price"].iloc[0]
sign = -1 if instrument_open_trades["side"].iloc[0] == "SELL" else 1
sign = -1 if instrument_open_trades["side"].iloc[0] == "SELL" else 1
instrument_price = predicted_row[f"{self.price_column_}_{symbol}"]
instrument_return = sign * (instrument_price - instrument_open_price) / instrument_open_price
instrument_return = (
sign
* (instrument_price - instrument_open_price)
/ instrument_open_price
)
return float(instrument_return) * 100.0
instrument_a_return = _single_instrument_return(self.symbol_a_)
instrument_b_return = _single_instrument_return(self.symbol_b_)
return (instrument_a_return + instrument_b_return)
return instrument_a_return + instrument_b_return
return 0.0
def __repr__(self) -> str:
return self.name()
@@ -328,4 +356,3 @@ class TradingPair(ABC):
# @abstractmethod
# def predicted_df(self) -> Optional[pd.DataFrame]: ...
+1 -1
View File
@@ -5,7 +5,7 @@ from typing import Dict, List, cast
import pandas as pd
def load_sqlite_to_dataframe(db_path, query):
def load_sqlite_to_dataframe(db_path:str, query:str) -> pd.DataFrame:
try:
conn = sqlite3.connect(db_path)