diff --git a/src/pt_backtest_slide.py b/src/pt_backtest_slide.py index bc3a8e0..6629d60 100644 --- a/src/pt_backtest_slide.py +++ b/src/pt_backtest_slide.py @@ -16,8 +16,44 @@ UNSET_INT: int = sys.maxsize # ------------------------ Configuration ------------------------ # Default configuration -CONFIG: Dict = { - "exchange_id": "ALPACA", +CRYPTO_CONFIG: Dict = { + # --- Data retrieval + "data_directory": "./data/crypto", + "datafiles": [ + "20250519.mktdata.ohlcv.db", + ], + "db_table_name": "bnbspot_ohlcv_1min", + + # ----- Instruments + "exchange_id": "BNBSPOT", + "instrument_id_pfx": "PAIR-", + + "instruments": [ + "BTC-USDT", + "ETH-USDT", + "LTC-USDT", + ], + + "trading_hours": { + "begin_session": "00:00:00", + "end_session": "23:59:00", + "timezone": "UTC" + }, + + # ----- Model Settings + "price_column": "close", + "min_required_points": 30, + "zero_threshold": 1e-10, + "equilibrium_threshold_open": 5.0, + "equilibrium_threshold_close": 1.0, + "training_minutes": 120, + + # ----- Validation + "funding_per_pair": 2000.0, # USD +} +# ========================== EQUITIES +EQT_CONFIG: Dict = { + # --- Data retrieval "data_directory": "./data/equity", "datafiles": [ "20250508.alpaca_sim_md.db", @@ -30,6 +66,12 @@ CONFIG: Dict = { # "20250519.alpaca_sim_md.db", # "20250520.alpaca_sim_md.db" ], + "db_table_name": "md_1min_bars", + + # ----- Instruments + "exchange_id": "ALPACA", + "instrument_id_pfx": "STOCK-", + "instruments": [ "COIN", "GBTC", @@ -37,7 +79,14 @@ CONFIG: Dict = { "MSTR", "PYPL", ], - "trading_hours": {"begin_session": "14:30:00", "end_session": "21:00:00"}, + + "trading_hours": { + "begin_session": "9:30:00", + "end_session": "16:00:00", + "timezone": "America/New_York" + }, + + # ----- Model Settings "price_column": "close", "min_required_points": 30, "zero_threshold": 1e-10, @@ -45,33 +94,18 @@ CONFIG: Dict = { "equilibrium_threshold_close": 1.0, "training_minutes": 120, + # ----- Validation "funding_per_pair": 2000.0, } -# ====== later =================== -# # Try to load configuration from file, fall back to defaults if not found -# CONFIG_FILE = "config.json" -# try: -# with open(CONFIG_FILE, "r") as f: -# user_config = json.load(f) -# CONFIG.update(user_config) -# print(f"Loaded configuration from {CONFIG_FILE}") -# except (FileNotFoundError, json.JSONDecodeError) as e: -# print(f"Using default configuration. Error loading {CONFIG_FILE}: {str(e)}") -# # Create a default config file if it doesn't exist -# try: -# with open(CONFIG_FILE, "w") as f: -# json.dump(CONFIG, f, indent=4) -# print(f"Created default configuration file: {CONFIG_FILE}") -# except Exception as e: -# print(f"Warning: Could not create default config file: {str(e)}") -# ------------------------ Settings ------------------------ - +# ========================================================================== +CONFIG = EQT_CONFIG TRADES = {} TOTAL_UNREALIZED_PNL = 0.0 # Global variable to track total unrealized PnL TOTAL_REALIZED_PNL = 0.0 # Global variable to track total realized PnL OUTSTANDING_POSITIONS = [] # Global list to track outstanding positions with share quantities -class Pair: + +class TradingPair: symbol_a_: str symbol_b_: str price_column_: str @@ -81,17 +115,35 @@ class Pair: self.symbol_b_ = symbol_b self.price_column_ = price_column - def colnames(self) -> List[str]: return [f"{self.price_column_}_{self.symbol_a_}", f"{self.price_column_}_{self.symbol_b_}"] def __repr__(self) ->str: return f"{self.symbol_a_} & {self.symbol_b_}" +def convert_time_to_UTC(value: str, timezone: str): + + from zoneinfo import ZoneInfo + from datetime import datetime + + # Parse it to naive datetime object + local_dt = datetime.strptime(value, '%Y-%m-%d %H:%M:%S') + + zinfo = ZoneInfo(timezone) + result = local_dt.replace(tzinfo=zinfo) + + result = result.astimezone(ZoneInfo('UTC')) + result = result.strftime('%Y-%m-%d %H:%M:%S') + + return result + + + pass + def load_market_data(datafile: str) -> pd.DataFrame: from tools.data_loader import load_sqlite_to_dataframe - instrument_ids = ["\"" + "STOCK-" + instrument + "\"" for instrument in CONFIG["instruments"]] + instrument_ids = ["\"" + CONFIG["instrument_id_pfx"] + instrument + "\"" for instrument in CONFIG["instruments"]] exchange_id = CONFIG["exchange_id"] query = "select tstamp" @@ -105,7 +157,7 @@ def load_market_data(datafile: str) -> pd.DataFrame: query += ", num_trades" query += ", vwap" - query += " from md_1min_bars" + query += f" from {CONFIG['db_table_name']}" query += f" where exchange_id ='{exchange_id}'" query += f" and instrument_id in ({','.join(instrument_ids)})" @@ -113,8 +165,12 @@ def load_market_data(datafile: str) -> pd.DataFrame: # Trading Hours date_str = df["tstamp"][0][0:10] - start_time = f"{date_str} {CONFIG['trading_hours']['begin_session']}" - end_time = f"{date_str} {CONFIG['trading_hours']['end_session']}" + trading_hours = CONFIG['trading_hours'] + start_time = f"{date_str} {trading_hours['begin_session']}" + end_time = f"{date_str} {trading_hours['end_session']}" + + start_time = convert_time_to_UTC(start_time, trading_hours["timezone"]) + end_time = convert_time_to_UTC(end_time, trading_hours["timezone"]) # Perform boolean selection df = df[(df["tstamp"] >= start_time) & (df["tstamp"] <= end_time)] @@ -149,7 +205,7 @@ def transform_dataframe(df: pd.DataFrame, price_column: str): return result_df -def get_datasets(df: pd.DataFrame, training_minutes: int, pair: Pair) -> Tuple[pd.DataFrame, pd.DataFrame]: +def get_datasets(df: pd.DataFrame, training_minutes: int, pair: TradingPair) -> Tuple[pd.DataFrame, pd.DataFrame]: # Training dataset colname_a, colname_b = pair.colnames() df = df[["tstamp", colname_a, colname_b]] @@ -164,7 +220,7 @@ def get_datasets(df: pd.DataFrame, training_minutes: int, pair: Pair) -> Tuple[p return (training_df, testing_df) -def fit_VECM(training_pair_df, pair: Pair): +def fit_VECM(training_pair_df, pair: TradingPair): vecm_model = VECM(training_pair_df[pair.colnames()].reset_index(drop=True), coint_rank=1) vecm_fit = vecm_model.fit() @@ -174,7 +230,7 @@ def fit_VECM(training_pair_df, pair: Pair): return vecm_fit -def create_trading_signals(vecm_fit, testing_pair_df, pair: Pair) -> pd.DataFrame: +def create_trading_signals(vecm_fit, testing_pair_df, pair: TradingPair) -> pd.DataFrame: result_columns = [ "time", "action", @@ -401,7 +457,7 @@ def create_trading_signals(vecm_fit, testing_pair_df, pair: Pair) -> pd.DataFram columns=result_columns, ) -def run_single_pair(market_data: pd.DataFrame, price_column:str, pair: Pair) -> Optional[pd.DataFrame]: +def run_single_pair(market_data: pd.DataFrame, price_column:str, pair: TradingPair) -> Optional[pd.DataFrame]: colname_a = f"{price_column}_{pair.symbol_a_}" colname_b = f"{price_column}_{pair.symbol_b_}" training_pair_df, testing_pair_df = get_datasets(df=market_data, training_minutes=CONFIG["training_minutes"], pair=pair) @@ -447,16 +503,15 @@ def run_single_pair(market_data: pd.DataFrame, price_column:str, pair: Pair) -> return pair_trades -def add_trade(pair, symbol, action, price): - # Ensure we always use clean names without STOCK- prefix - pair = str(pair).replace("STOCK-", "") - symbol = symbol.replace("STOCK-", "") +def add_trade(pair_nm, symbol, action, price): + pair_nm = str(pair_nm) - if pair not in TRADES: - TRADES[pair] = {symbol: []} - if symbol not in TRADES[pair]: - TRADES[pair][symbol] = [] - TRADES[pair][symbol].append((action, price)) + + if pair_nm not in TRADES: + TRADES[pair_nm] = {symbol: []} + if symbol not in TRADES[pair_nm]: + TRADES[pair_nm][symbol] = [] + TRADES[pair_nm][symbol].append((action, price)) def collect_single_day_results(result): if result is None: @@ -469,7 +524,7 @@ def collect_single_day_results(result): action = row.action symbol = row.symbol price = row.price - add_trade(pair=row.pair, action=action, symbol=symbol, price=price) + add_trade(pair_nm=row.pair, action=action, symbol=symbol, price=price) def print_single_day_results(result): for pair, symbols in TRADES.items(): @@ -574,10 +629,8 @@ def run_pairs(summaries_df: pd.DataFrame, price_column: str) -> None: colname_b = stock_price_columns[b_index] symbol_a = colname_a[len(f"{price_column}-") :] - symbol_b = colname_b[len(f"{price_column}-") :].replace( - "STOCK-", "" - ) - pair = Pair(symbol_a, symbol_b, price_column) + symbol_b = colname_b[len(f"{price_column}-") :] + pair = TradingPair(symbol_a, symbol_b, price_column) single_pair_trades = run_single_pair(market_data=result_df, price_column=price_column, pair=pair) if len(single_pair_trades) > 0: