diff --git a/configuration/equity_single.cfg b/configuration/equity_single.cfg index d096b7b..9cc0050 100644 --- a/configuration/equity_single.cfg +++ b/configuration/equity_single.cfg @@ -2,7 +2,7 @@ "security_type": "EQUITY", "data_directory": "./data/equity", "datafiles": [ - "20250605.mktdata.ohlcv.db", + "20250606.mktdata.ohlcv.db", ], "db_table_name": "md_1min_bars", "exchange_id": "ALPACA", diff --git a/lib/pt_trading/fit_methods.py b/lib/pt_trading/fit_methods.py index 8aa088c..6088eb1 100644 --- a/lib/pt_trading/fit_methods.py +++ b/lib/pt_trading/fit_methods.py @@ -276,25 +276,27 @@ class SlidingFit(PairsTradingFitMethod): if len(pair.training_df_) < training_minutes: print( - f"{pair}: {self.curr_training_start_idx_} Not enough training data. Completing the job." + f"{pair}: current offset={self.curr_training_start_idx_}" + f" * Training data length={len(pair.training_df_)} < {training_minutes}" + " * Not enough training data. Completing the job." ) - if pair.user_data_["state"] == PairState.OPEN: - print( - f"{pair}: {self.curr_training_start_idx_} Position is not closed." - ) - # outstanding positions - # last_row_index = self.curr_training_start_idx_ + training_minutes - 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"], - ) + # if pair.user_data_["state"] == PairState.OPEN: + # print( + # f"{pair}: {self.curr_training_start_idx_} Position is not closed." + # ) + # # outstanding positions + # # last_row_index = self.curr_training_start_idx_ + training_minutes + # 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: @@ -367,6 +369,25 @@ class SlidingFit(PairsTradingFitMethod): pair.add_trades(close_trades) pair.user_data_["state"] = PairState.CLOSED + # Outstanding positions + if pair.user_data_["state"] == PairState.OPEN: + print( + f"{pair}: *** Position is NOT CLOSED. ***" + ) + # outstanding positions + # last_row_index = self.curr_training_start_idx_ + training_minutes + 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"], + ) + def _get_open_trades( self, pair: TradingPair, row: pd.Series, open_threshold: float ) -> Optional[pd.DataFrame]: @@ -386,15 +407,15 @@ class SlidingFit(PairsTradingFitMethod): 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 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 diff --git a/lib/pt_trading/results.py b/lib/pt_trading/results.py index 748bdea..8285f7f 100644 --- a/lib/pt_trading/results.py +++ b/lib/pt_trading/results.py @@ -526,6 +526,8 @@ class BacktestResult: day_return = 0 print(f"\n--- {filename} ---") + self.outstanding_positions = data["outstanding_positions"] + # Process each pair for pair, symbols in data["trades"].items(): pair_return = 0 diff --git a/research/notebooks/pt_sliding.ipynb b/research/notebooks/pt_sliding.ipynb index f679fc8..0bd8359 100644 --- a/research/notebooks/pt_sliding.ipynb +++ b/research/notebooks/pt_sliding.ipynb @@ -652,7 +652,14 @@ "********************************************************************************\n", "Pair COIN & MSTR (0) IS COINTEGRATED\n", "********************************************************************************\n", - "COIN & MSTR: 272 Not enough training data. Completing the job.\n", + "69\r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "COIN & MSTR: current offset=272 * Training data length=119 < 120 * Not enough training data. Completing the job.\n", "OPEN_TRADES: 2025-06-06 17:01:00 open_scaled_disequilibrium=np.float64(2.134862885140155)\n", "OPEN TRADES:\n", " time action symbol price disequilibrium \\\n", @@ -679,6 +686,13 @@ " scaled_disequilibrium pair status \n", "0 4.316094 COIN & MSTR OPEN \n", "1 4.316094 COIN & MSTR OPEN \n", + "COIN & MSTR: *** Position is NOT CLOSED. ***\n", + "COIN & MSTR: NO CLOSE SIGNAL FOUND - Position held until end of session\n", + " Open: 2025-06-06 19:41:00 | Last: 2025-06-06 15:30:00\n", + " COIN: BUY 3.97 shares @ $252.00 -> $251.94 | Value: $999.72\n", + " MSTR: SELL 2.68 shares @ $373.29 -> $376.41 | Value: $1008.36\n", + " Total Value: $2008.08\n", + " Disequilibrium: -1.4396 | Scaled: 0.9242\n", "***COIN & MSTR*** FINISHED ... 6\n", "Generated 6 trading signals\n", "\n", @@ -686,23 +700,6 @@ "\n", "================================================================================\n", "BACKTEST RESULTS\n", - "================================================================================\n", - "\n", - "Detailed Trading Signals:\n", - "Time Action Symbol Price Scaled Dis-eq \n", - "--------------------------------------------------------------------------------\n", - "2025-06-06 17:01:00 SELL COIN $253.02 2.135 \n", - "2025-06-06 17:01:00 BUY MSTR $377.33 2.135 \n", - "2025-06-06 18:17:00 BUY COIN $253.93 0.989 \n", - "2025-06-06 18:17:00 SELL MSTR $375.01 0.989 \n", - "2025-06-06 19:41:00 BUY COIN $252.00 4.316 \n", - "2025-06-06 19:41:00 SELL MSTR $373.29 4.316 \n", - "\n", - "====== NO OUTSTANDING POSITIONS ======\n", - "\n", - "====== GRAND TOTALS ACROSS ALL PAIRS ======\n", - "Total Realized PnL: 0.00%\n", - "\n", "================================================================================\n" ] } @@ -748,56 +745,9 @@ "print(\"BACKTEST RESULTS\")\n", "print(\"=\"*80)\n", "\n", - "assert pair.predicted_df_ is not None\n", - "\n", - "if pair_trades is not None and len(pair_trades) > 0:\n", - " # Print detailed results using BacktestResult methods\n", - " bt_result.print_single_day_results()\n", - " \n", - " # Print trading signal details\n", - " print(f\"\\nDetailed Trading Signals:\")\n", - " print(f\"{'Time':<20} {'Action':<15} {'Symbol':<10} {'Price':<12} {'Scaled Dis-eq':<15}\")\n", - " print(\"-\" * 80)\n", - " \n", - " for _, trade in pair_trades.head(10).iterrows(): # Show first 10 trades\n", - " time_str = str(trade['time'])[:19] \n", - " action_str = str(trade['action'])[:14]\n", - " symbol_str = str(trade['symbol'])[:9]\n", - " price_str = f\"${trade['price']:.2f}\"\n", - " diseq_str = f\"{trade.get('scaled_disequilibrium', 'N/A'):.3f}\" if 'scaled_disequilibrium' in trade else 'N/A'\n", - " \n", - " print(f\"{time_str:<20} {action_str:<15} {symbol_str:<10} {price_str:<12} {diseq_str:<15}\")\n", - " \n", - " if len(pair_trades) > 10:\n", - " print(f\"... and {len(pair_trades)-10} more trading signals\")\n", - " \n", - " # Print outstanding positions\n", - " bt_result.print_outstanding_positions()\n", - " \n", - " # Print grand totals\n", - " bt_result.print_grand_totals()\n", - " \n", - "else:\n", - " print(f\"\\nNo trading signals generated\")\n", - " print(f\"Backtest completed with no trades\")\n", - " \n", - " # Still print any outstanding information\n", - " print(f\"\\nConfiguration Summary:\")\n", - " print(f\" Pair: {SYMBOL_A} & {SYMBOL_B}\")\n", - " print(f\" Strategy: {FIT_METHOD_TYPE}\")\n", - " print(f\" Open threshold: {pt_bt_config['dis-equilibrium_open_trshld']}\")\n", - " print(f\" Close threshold: {pt_bt_config['dis-equilibrium_close_trshld']}\")\n", - " print(f\" Training window: {pt_bt_config['training_minutes']} minutes\")\n", - " \n", - " pass # TODO: Implement sliding fit cointegration check\n", - "print(\"\\n\" + \"=\"*80)\n" + "assert pair.predicted_df_ is not None\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] - }, { "cell_type": "markdown", "metadata": { @@ -3281,9 +3231,9 @@ }, "text/html": [ "