nse-bot-backend / debug_trade.py
ash001's picture
Deploy from GitHub Actions to nse-bot-backend
fb6c788 verified
"""
Dump raw 5m OHLC that the backtest actually read for a given trade —
both the underlying stock and the option — so you can visually compare
against TradingView / Kite chart and spot data glitches.
Usage:
python debug_trade.py --symbol ANGELONE --option ANGELONE26APR315CE --date 2026-04-17 --around 12:30
Prints a window of bars around the time of interest, plus the exact
indication / confirmation / trigger bars so you can see what prices
strategy-1 saw.
"""
from __future__ import annotations
import argparse
from datetime import datetime, timedelta, time
from pathlib import Path
import pandas as pd
from history_utils import get_stock_5m, get_option_5m
from indicators_v2 import add_strategy_indicators_v2
def _fmt(df: pd.DataFrame, extra_cols=None) -> str:
base = ["timestamp", "open", "high", "low", "close", "volume"]
cols = [c for c in base if c in df.columns]
if extra_cols:
cols += [c for c in extra_cols if c in df.columns and c not in cols]
return df[cols].to_string(index=False)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--symbol", required=True, help="Underlying stock symbol (e.g., ANGELONE)")
ap.add_argument("--option", required=True, help="Option tradingsymbol (e.g., ANGELONE26APR315CE)")
ap.add_argument("--date", required=True, help="Trade date YYYY-MM-DD")
ap.add_argument("--around", default="12:30", help="Time of interest HH:MM (IST)")
ap.add_argument("--window-min", type=int, default=30, help="Minutes before/after to show (default 30)")
args = ap.parse_args()
day = pd.Timestamp(args.date).date()
around_h, around_m = [int(x) for x in args.around.split(":")]
around_ts = datetime.combine(day, time(around_h, around_m))
win = timedelta(minutes=args.window_min)
fetch_from = datetime.combine(day - timedelta(days=5), time(9, 15))
fetch_to = datetime.combine(day, time(15, 30))
print(f"=== UNDERLYING {args.symbol} on {args.date} ===")
stock = get_stock_5m(args.symbol, fetch_from, fetch_to)
if stock.empty:
print("(no stock data)")
else:
stock = add_strategy_indicators_v2(stock)
stock["timestamp"] = pd.to_datetime(stock["timestamp"])
day_stock = stock[stock["timestamp"].dt.date == day].copy()
day_stock["body_high"] = day_stock[["open", "close"]].max(axis=1)
day_stock["body_low"] = day_stock[["open", "close"]].min(axis=1)
ts_naive = day_stock["timestamp"].dt.tz_localize(None) if day_stock["timestamp"].dt.tz is not None else day_stock["timestamp"]
mask = (ts_naive >= (around_ts - win)) & (ts_naive <= (around_ts + win))
view = day_stock[mask]
print(_fmt(view, extra_cols=[
"body_high", "body_low",
"bb_phase", "ema5", "ema9", "ck_dir", "jghla_dir",
]))
print(f"\n=== OPTION {args.option} on {args.date} ===")
try:
opt = get_option_5m(args.option, fetch_from, fetch_to)
except Exception as e:
print(f"(error fetching option: {e})")
return
if opt.empty:
print("(no option data - possibly expired beyond retention window)")
return
opt["timestamp"] = pd.to_datetime(opt["timestamp"])
day_opt = opt[opt["timestamp"].dt.date == day].copy()
day_opt["body_high"] = day_opt[["open", "close"]].max(axis=1)
day_opt["body_low"] = day_opt[["open", "close"]].min(axis=1)
ts_naive = day_opt["timestamp"].dt.tz_localize(None) if day_opt["timestamp"].dt.tz is not None else day_opt["timestamp"]
mask = (ts_naive >= (around_ts - win)) & (ts_naive <= (around_ts + win))
view = day_opt[mask]
print(_fmt(view, extra_cols=["body_high", "body_low"]))
# Highlight the bars the backtest keyed off
print("\n=== KEY BARS BACKTEST USED ===")
ind_ts = around_ts - timedelta(minutes=5) # confirmation - 1 bar
conf_ts = around_ts # confirmation bar
trig_ts_a = around_ts + timedelta(minutes=5) # first trigger check
trig_ts_b = around_ts + timedelta(minutes=10) # second trigger check
print(f"indication bar : {ind_ts} (strategy-1 stop_loss = body_low of this option candle)")
print(f"confirmation bar : {conf_ts} (trigger level = body_high of this underlying candle)")
print(f"trigger bar(s) : {trig_ts_a} / {trig_ts_b} (entry fires on break; buy_price = body_high of option candle at that bar)")
if __name__ == "__main__":
main()