nse-bot-backend / live_paper_scanner.py
ash001's picture
Deploy from GitHub Actions to nse-bot-backend
789e5eb verified
from pathlib import Path
from datetime import datetime, timedelta
import time as tm
import pandas as pd
from strategy_engine import scan_symbol_for_day, OUTPUT_COLUMNS
BASE_DIR = Path(__file__).resolve().parent
UNIVERSE_PATH = BASE_DIR / "option_stock_universe.csv"
OUT_DIR = BASE_DIR / "outputs"
OUT_DIR.mkdir(exist_ok=True)
MODE = "priority" # "priority" or "all"
VARIANT = "v2"
OUTPUT_FILE = OUT_DIR / f"live_paper_trades_{MODE}_{VARIANT}.csv"
def load_universe():
df = pd.read_csv(UNIVERSE_PATH)
df["symbol"] = df["symbol"].astype(str).str.upper()
df["sector"] = df["sector"].fillna("").astype(str)
df["priority_rank"] = pd.to_numeric(df["priority_rank"], errors="coerce").fillna(999)
if MODE == "priority":
df = df[df["priority_rank"] == 1].copy()
return df.reset_index(drop=True)
def now_ist():
return pd.Timestamp.now(tz="Asia/Kolkata")
def is_market_time(ts):
if ts.weekday() >= 5:
return False
t = ts.time()
return t >= datetime.strptime("09:30", "%H:%M").time() and t <= datetime.strptime("15:30", "%H:%M").time()
def next_5min_boundary(ts):
base = ts.floor("5min")
if ts == base:
return ts + pd.Timedelta(minutes=5)
return base + pd.Timedelta(minutes=5)
def load_existing():
if OUTPUT_FILE.exists():
df = pd.read_csv(OUTPUT_FILE)
if "BUY TIME" in df.columns:
df["BUY TIME"] = pd.to_datetime(df["BUY TIME"], errors="coerce")
return df
return pd.DataFrame(columns=OUTPUT_COLUMNS)
def dedupe(existing_df, new_df):
if new_df.empty:
return existing_df
combined = pd.concat([existing_df, new_df], ignore_index=True)
combined["BUY TIME"] = pd.to_datetime(combined["BUY TIME"], errors="coerce")
combined = combined.drop_duplicates(
subset=["STOCK NAME", "OPTION SYMBOL", "CALL/PUT", "BUY TIME"],
keep="first"
).sort_values("BUY TIME")
return combined.reset_index(drop=True)
def scan_once():
ts = now_ist()
trade_date = str(ts.date())
universe = load_universe()
option_cache = {}
frames = []
print(f"\n[{ts}] Scanning {len(universe)} symbols | mode={MODE} | variant={VARIANT}")
for _, row in universe.iterrows():
symbol = row["symbol"]
sector = row["sector"]
try:
df = scan_symbol_for_day(
stock_symbol=symbol,
sector=sector,
trade_date=trade_date,
variant=VARIANT,
option_cache=option_cache,
)
if not df.empty:
frames.append(df)
except Exception as e:
print(f"Error scanning {symbol}: {e}")
if frames:
new_df = pd.concat(frames, ignore_index=True)
else:
new_df = pd.DataFrame(columns=OUTPUT_COLUMNS)
existing_df = load_existing()
final_df = dedupe(existing_df, new_df)
final_df.to_csv(OUTPUT_FILE, index=False)
print(f"Saved {len(final_df)} total paper trades to {OUTPUT_FILE}")
def main():
print("Starting live paper scanner...")
print(f"Mode: {MODE}")
print(f"Variant: {VARIANT}")
print(f"Output: {OUTPUT_FILE}")
while True:
ts = now_ist()
if is_market_time(ts):
scan_once()
nxt = next_5min_boundary(now_ist()) + pd.Timedelta(seconds=10)
sleep_secs = max((nxt - now_ist()).total_seconds(), 5)
print(f"Sleeping {int(sleep_secs)} seconds until next cycle...")
tm.sleep(sleep_secs)
else:
print(f"[{ts}] Outside market time. Sleeping 60 seconds...")
tm.sleep(60)
if __name__ == "__main__":
main()