import logging
from datetime import datetime, timedelta
import argparse
import pytz
import pandas as pd
import numpy as np
import requests
import json
import os
import sys
from firstock import firstock

# Define global variables for start and end times (Format: YYYY-MM-DD HH:MM)
GLOBAL_START_TIME_STR = "2026-10-01 14:05"
GLOBAL_END_TIME_STR = "2026-10-06 15:30"

# Offset for buying CE and PE intially
INITIAL_BUY_SELL_OFFSET = 100

#profit booking controls
GLOBAL_INITIAL_PROFIT_BOOKING_OFFSET = 50           #only for the first entry after intial
GLOBAL_CONTINUATION_PROFIT_BOOKING_OFFSET = 50      #successive profit booking after intial profit (2nd onwards)
GLOBAL_RETRACEMENT_PROFIT_BOOKING_OFFSET = 72       # profit booking after winning side changes 

# DEFENCE FOR LOOSING SIDE  (250-->100)
GLOBAL_DEFENCE_CLOSE_MAX_ALLOWED_DISTANCE = 250
GLOBAL_DEFENCE_CLOSE_MAX_DIST_ADJUSTMENT = 150


pd.set_option('future.no_silent_downcasting', True)

# Configure standard logger
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
)
logger = logging.getLogger(__name__)

# Suppress noisy external library debug logs
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("requests").setLevel(logging.WARNING)

STATE_FILE = "grid_state.json"

def _send_entry_request(url, params, endpoint_name):
    try:
        response = requests.get(url, params=params, timeout=5)
        response.raise_for_status()
    except requests.RequestException as e:
        logger.error(f"[{endpoint_name}] API Request failed: {e}")

def create_trend_entry(tick_time_str, instrument, close_price, signal, lot_count=1, trigger_type=None):
    if not hasattr(create_trend_entry, "counter"):
        create_trend_entry.counter = 0
    create_trend_entry.counter += 1
    
    modified_tick_time = f"{tick_time_str}:{create_trend_entry.counter:03d}"

    logger.debug(f"create_trend_entry called -> Time: {modified_tick_time}, Instrument: {instrument}, Price: {close_price}, Signal: {signal}, Lots: {lot_count}, Trigger: {trigger_type}")
    
    api_instrument = instrument
    if not api_instrument.startswith('NIFTY') and not api_instrument.startswith('SENSEX'):
        if api_instrument.replace('CE', '').replace('PE', '').isdigit():
            api_instrument = f'NIFTY{api_instrument}'
    
    try:
        close_price = int(round(float(close_price)))
    except (ValueError, TypeError) as err:
        logger.error(f"Failed to parse close_price: {err}")
        return
    
    remarks_val = str(trigger_type).replace('_', '-') if trigger_type else 'MB'
    params = {
        'tickTime': str(modified_tick_time),
        'instrument': str(api_instrument),
        'closePrice': str(close_price),
        'signal': str(signal),
        'orderType': str(lot_count),
        'remarks': remarks_val
    }
    endpoints = [
        ("Multibagger", "http://143.244.141.41/php/createEntriesMft.php"),
        ("LastSupper", "http://139.59.6.25/php/createEntriesMft.php"),
        ("GoodFriday", "http://68.183.85.105/php/createEntriesMft.php"),
    ]
    for endpoint_name, url in endpoints:
        _send_entry_request(url, params, endpoint_name)

class StockDataFetcher:
    def __init__(self, client_details):
        self.user_id = client_details[0]
        self.client_details = client_details
        self.ist = pytz.timezone('Asia/Kolkata')
        logger.debug(f"StockDataFetcher initialized for User ID: {self.user_id}")

    def login(self):
        logger.debug(f"Attempting login for user {self.user_id}...")
        try:
            response = firstock.login(*self.client_details)
            status = response.get("status") == "success"
            logger.debug(f"Login result status: {response.get('status')}")
            return status
        except Exception as e:
            logger.error(f"Exception during login: {e}")
            return False

    def fetch_data(self, symbol, interval_minutes=5, count=1000, elapsed=0):
        logger.debug(f"Fetching data for {symbol}, interval={interval_minutes}m, count={count}, elapsed={elapsed}")

        # Parse the global variable strings into datetime objects
        naive_start = datetime.strptime(GLOBAL_START_TIME_STR, "%Y-%m-%d %H:%M")
        naive_end = datetime.strptime(GLOBAL_END_TIME_STR, "%Y-%m-%d %H:%M")

        # Localize them using your IST timezone object
        start_time = self.ist.localize(naive_start)
        end_time = self.ist.localize(naive_end)

        logger.debug(f"Fetching data for start_time={start_time}, end_time={end_time}" )

        
        response = firstock.timePriceSeries(userId=self.user_id, exchange="NSE", tradingSymbol="NIFTY", 
                                            startTime=start_time.strftime("%H:%M:%S %d-%m-%Y"), 
                                            endTime=end_time.strftime("%H:%M:%S %d-%m-%Y"), interval=f"{interval_minutes}mi")
        if response.get("status") == "success":
            data = response.get("data", [])
            logger.debug(f"API returned {len(data)} raw candles.")
            if data:
                df = pd.DataFrame(data)
                df['datetime'] = pd.to_datetime(df['time'], format='%H:%M:%S %d-%m-%Y')
                for col in ['open', 'high', 'low', 'close', 'volume']:
                    df[col] = pd.to_numeric(df[col], errors='coerce')
                df = df.sort_values(by='datetime', ascending=True)
                df = df[(df['datetime'].dt.hour >= 9) & (df['datetime'].dt.hour <= 15)]
                logger.debug(f"Filtered dataframe contains {len(df)} rows.")
                return df.tail(count)
        else:
            logger.error(f"Failed to fetch series data: {response}")
        return pd.DataFrame()


def load_state(today_str):
    logger.debug(f"Checking state file '{STATE_FILE}'")
    if os.path.exists(STATE_FILE):
        try:
            with open(STATE_FILE, "r") as f:
                state = json.load(f)
            
            file_date = state.get("date", today_str)
            logger.debug(f"State loaded successfully with date: {file_date}")
            for side in ["ce_positions", "pe_positions"]:
                if side in state:
                    normalized = []
                    for p in state[side]:
                        strike = p["strike"]
                        created_at = p.get("created_at", f"{file_date} 09:15:00")
                        qty = p.get("qty", 1)
                        for _ in range(qty):
                            normalized.append({"strike": strike, "qty": 1, "created_at": created_at})
                    state[side] = normalized
            return state
        except Exception as e:
            logger.error(f"Failed to parse state file: {e}")
    else:
        logger.debug("No existing state file found.")
    return None

def save_state(state, today_str):
    state["date"] = today_str
    with open(STATE_FILE, "w") as f:
        json.dump(state, f, indent=4)

def round_to_nearest(val, base=50):
    return int(base * round(float(val) / base))

def get_positions_summary(state):
    pe_strikes = [p["strike"] for p in state.get("pe_positions", [])]
    ce_strikes = [p["strike"] for p in state.get("ce_positions", [])]
    
    pe_counts = {strike: pe_strikes.count(strike) for strike in sorted(set(pe_strikes), reverse=True)}
    ce_counts = {strike: ce_strikes.count(strike) for strike in sorted(set(ce_strikes))}
    
    summary_items = []
    for strike in sorted(set(pe_strikes)):
        summary_items.append(f"{strike}PE (x{pe_counts[strike]})")
    for strike in sorted(set(ce_strikes)):
        summary_items.append(f"{strike}CE (x{ce_counts[strike]})")
        
    return ", ".join(summary_items) if summary_items else "No Active Positions"

def record_action(state, tick_time_str, action_type, instrument, price, signal, lots, remarks):
    if "actions_history" not in state:
        state["actions_history"] = []
    
    positions_str = get_positions_summary(state)
    
    action_record = {
        "timestamp": str(tick_time_str),
        "action_type": str(action_type),
        "instrument": str(instrument),
        "price": float(price),
        "signal": str(signal),
        "lots": int(lots),
        "remarks": str(remarks),
        "positions_summary": positions_str
    }
    state["actions_history"].append(action_record)

def check_losing_side_defense(state, tick_time_str, close_price, shift_count=1):
    winning_side = state.get("current_winning_side")
    if not winning_side:
        return
    losing_side = "PE" if winning_side == "CE" else "CE"
    
    for _ in range(max(1, shift_count)):
        if losing_side == "PE":
            pe_positions = state.get("pe_positions", [])
            if not pe_positions:
                break
            deepest_pe = min([p["strike"] for p in pe_positions])
            distance = close_price - deepest_pe

            if distance >= GLOBAL_DEFENCE_CLOSE_MAX_ALLOWED_DISTANCE:
                logger.info(f"[{tick_time_str}] LOSING SIDE DEFENSE TRIGGERED (PE) -> Distance: {distance} >= 150. Pruning deepest PE strike: {deepest_pe}PE and rolling to 100 pts away.")
                
                pe_positions = sorted(pe_positions, key=lambda x: x.get("created_at", ""))
                to_remove_idx = -1
                for i, p in enumerate(pe_positions):
                    if p["strike"] == deepest_pe:
                        to_remove_idx = i
                        break
                if to_remove_idx != -1:
                    pe_positions.pop(to_remove_idx)
                    create_trend_entry(tick_time_str, f"{deepest_pe}PE", close_price, "SELL", lot_count=1, trigger_type="DEFENSE_CLOSE_PE")
                    record_action(state, tick_time_str, "DEFENSE_CLOSE", f"{deepest_pe}PE", close_price, "SELL", 1, "DEFENSE_CLOSE_PE")
                
                new_strike = round_to_nearest(close_price - GLOBAL_DEFENCE_CLOSE_MAX_DIST_ADJUSTMENT, 50)
                state["pe_positions"] = pe_positions
                state["pe_positions"].append({"strike": new_strike, "qty": 1, "created_at": tick_time_str})
                create_trend_entry(tick_time_str, f"{new_strike}PE", close_price, "BUY", lot_count=1, trigger_type="DEFENSE_OPEN_PE")
                record_action(state, tick_time_str, "DEFENSE_OPEN", f"{new_strike}PE", close_price, "BUY", 1, "DEFENSE_OPEN_PE")
                
                logger.info(f"[{tick_time_str}] DEFENSE ACTION DONE (PE) -> Positions: {get_positions_summary(state)}")
            else:
                break

        elif losing_side == "CE":
            ce_positions = state.get("ce_positions", [])
            if not ce_positions:
                break
            deepest_ce = max([p["strike"] for p in ce_positions])
            distance = deepest_ce - close_price
            
            if distance >= GLOBAL_DEFENCE_CLOSE_MAX_ALLOWED_DISTANCE:
                logger.info(f"[{tick_time_str}] LOSING SIDE DEFENSE TRIGGERED (CE) -> Distance: {distance} >= 150. Pruning deepest CE strike: {deepest_ce}CE and rolling to 100 pts away.")
                
                ce_positions = sorted(ce_positions, key=lambda x: x.get("created_at", ""))
                to_remove_idx = -1
                for i, p in enumerate(ce_positions):
                    if p["strike"] == deepest_ce:
                        to_remove_idx = i
                        break
                if to_remove_idx != -1:
                    ce_positions.pop(to_remove_idx)
                    create_trend_entry(tick_time_str, f"{deepest_ce}CE", close_price, "SELL", lot_count=1, trigger_type="DEFENSE_CLOSE_CE")
                    record_action(state, tick_time_str, "DEFENSE_CLOSE", f"{deepest_ce}CE", close_price, "SELL", 1, "DEFENSE_CLOSE_CE")
                
                new_strike = round_to_nearest(close_price + GLOBAL_DEFENCE_CLOSE_MAX_DIST_ADJUSTMENT, 50)
                state["ce_positions"] = ce_positions
                state["ce_positions"].append({"strike": new_strike, "qty": 1, "created_at": tick_time_str})
                create_trend_entry(tick_time_str, f"{new_strike}CE", close_price, "BUY", lot_count=1, trigger_type="DEFENSE_OPEN_CE")
                record_action(state, tick_time_str, "DEFENSE_OPEN", f"{new_strike}CE", close_price, "BUY", 1, "DEFENSE_OPEN_CE")
                
                logger.info(f"[{tick_time_str}] DEFENSE ACTION DONE (CE) -> Positions: {get_positions_summary(state)}")
            else:
                break

def process_options_grid_strategy(result_df):
    logger.debug(f"Starting strategy processor with {len(result_df)} rows.")
    if result_df.empty:
        logger.debug("DataFrame is empty. Exiting.")
        return pd.DataFrame()

    action_logs = []
    today_str = str(result_df.iloc[0]['datetime'].date())
    state = load_state(today_str)

    logger.info("=" * 125)
    logger.info("DELTA-NEUTRAL OPTIONS GRID - INITIAL ENTRY & DYNAMIC TRIGGER ENGINE")
    logger.info("=" * 125)

    unprocessed_df = result_df

    # --------------------------------------------
    # STEP 1: INITIAL ENTRY (Happens ONCE per day)
    # --------------------------------------------
    if not state or not state.get("initial_entry_done"):
        initial_row = None
        
        for idx, row in result_df.iterrows():
            close_val = float(row['close'])
            remainder = close_val % 50
            if remainder <= 9 or remainder >= 41:
                initial_row = row
                break
                
        if initial_row is not None:
            dt = initial_row['datetime']
            tick_time_str = dt.strftime("%Y-%m-%d %H:%M:%S")
            t_str = dt.strftime('%H:%M')
            close_price = float(initial_row['close'])
            remainder = close_price % 50
            
            logger.debug(f"[{t_str}] Initial entry condition met (Remainder: {remainder:.2f}). Calculating strikes for Spot: {close_price}")
            
            offset = INITIAL_BUY_SELL_OFFSET
            initial_ce = round_to_nearest(close_price + offset, 50)
            initial_pe = round_to_nearest(close_price - offset, 50)
            base_action_price = round_to_nearest(close_price, 50)
            
            logger.debug(f"[{t_str}] Calculated Initial CE Strike: {initial_ce}, Initial PE Strike: {initial_pe}, Base Action Price: {base_action_price}")
            
            create_trend_entry(tick_time_str, f"{initial_ce}CE", close_price, "BUY", lot_count=3, trigger_type="INITIAL_ENTRY")
            create_trend_entry(tick_time_str, f"{initial_pe}PE", close_price, "BUY", lot_count=3, trigger_type="INITIAL_ENTRY")
            
            state = {
                "initial_entry_done": True,
                "spot_at_entry": close_price,
                "last_action_price": base_action_price,
                "current_winning_side": None,  # Neutral initial state until first breakout
                "last_processed_time": str(dt),
                "ce_positions": [
                    {"strike": initial_ce, "qty": 1, "created_at": tick_time_str},
                    {"strike": initial_ce, "qty": 1, "created_at": tick_time_str},
                    {"strike": initial_ce, "qty": 1, "created_at": tick_time_str}
                ],
                "pe_positions": [
                    {"strike": initial_pe, "qty": 1, "created_at": tick_time_str},
                    {"strike": initial_pe, "qty": 1, "created_at": tick_time_str},
                    {"strike": initial_pe, "qty": 1, "created_at": tick_time_str}
                ],
                "actions_history": []
            }
            
            record_action(state, tick_time_str, "INITIAL_ENTRY", f"{initial_ce}CE", close_price, "BUY", 3, "INITIAL_ENTRY")
            record_action(state, tick_time_str, "INITIAL_ENTRY", f"{initial_pe}PE", close_price, "BUY", 3, "INITIAL_ENTRY")
            
            save_state(state, today_str)
            logger.info(f"{t_str:<8} | INITIAL ENTRY DONE | Spot: {close_price} | Base Anchor: {base_action_price} | Positions: {get_positions_summary(state)}")
            
            unprocessed_df = result_df[result_df['datetime'] > dt]
        else:
            logger.warning("No candle met the initial entry condition (remainder <= 9 or >= 41) for the day.")
            return pd.DataFrame()
    else:
        last_saved_time = pd.to_datetime(state.get("last_processed_time"))
        if last_saved_time:
            unprocessed_df = result_df[result_df['datetime'] > last_saved_time]
            logger.info(f"Resumed from state file. Last processed time: {last_saved_time}. Remaining rows to process: {len(unprocessed_df)}")

    # -------------------------------------------------------------
    # STEP 2: DYNAMIC TRIGGER ENGINE (Two-Way Switch & Roll Loop)
    # -------------------------------------------------------------
    for idx, row in unprocessed_df.iterrows():
        dt = row['datetime']
        t_str = dt.strftime('%H:%M')
        close_price = float(row['close'])
        tick_time_str = dt.strftime("%Y-%m-%d %H:%M:%S")
        
        while True:
            winning_side = state.get("current_winning_side")
            last_action_price = state["last_action_price"]
            
            active_ce_strike = max([p["strike"] for p in state["ce_positions"]]) if state["ce_positions"] else "N/A"
            active_pe_strike = min([p["strike"] for p in state["pe_positions"]]) if state["pe_positions"] else "N/A"
            
            logger.info(f"[{t_str}] TRIGGER CHECK -> Spot: {close_price} | Winning Side: {winning_side} | Last Anchor: {last_action_price} | Active CE: {active_ce_strike}CE | Active PE: {active_pe_strike}PE")

            continuation_triggered = False
            retracement_triggered = False


            if winning_side is None:
                if close_price >= last_action_price + GLOBAL_INITIAL_PROFIT_BOOKING_OFFSET:
                    state["current_winning_side"] = "CE"
                    winning_side = "CE"
                    continuation_triggered = True
                    logger.info(f"[{t_str}] Initial breakout upward (+50 pts). Setting Winning Side to CE.")
                elif close_price <= last_action_price - GLOBAL_INITIAL_PROFIT_BOOKING_OFFSET:
                    state["current_winning_side"] = "PE"
                    winning_side = "PE"
                    continuation_triggered = True
                    logger.info(f"[{t_str}] Initial breakout downward (-50 pts). Setting Winning Side to PE.")
            elif winning_side == "CE":
                if close_price >= last_action_price + GLOBAL_CONTINUATION_PROFIT_BOOKING_OFFSET:
                    continuation_triggered = True
                elif close_price <= (last_action_price - GLOBAL_RETRACEMENT_PROFIT_BOOKING_OFFSET):
                    retracement_triggered = True
            else:
                if close_price <= last_action_price - GLOBAL_CONTINUATION_PROFIT_BOOKING_OFFSET:
                    continuation_triggered = True
                elif close_price >= (last_action_price + GLOBAL_RETRACEMENT_PROFIT_BOOKING_OFFSET):
                    retracement_triggered = True

            if continuation_triggered:
                logger.info(f"[{t_str}] CONTINUATION TRIGGER (+50 pts) met on {winning_side}. Spot: {close_price}, Last Anchor: {last_action_price}")
                side_key = "ce_positions" if winning_side == "CE" else "pe_positions"
                
                if state[side_key]:
                    state[side_key] = sorted(state[side_key], key=lambda x: x.get("created_at", ""))
                    old_pos = state[side_key].pop(0)
                    old_strike = old_pos["strike"]
                    
                    create_trend_entry(tick_time_str, f"{old_strike}{winning_side}", close_price, "SELL", lot_count=1, trigger_type=f"CONTINUATION_CLOSE_{winning_side}")
                    record_action(state, tick_time_str, "CONTINUATION_CLOSE", f"{old_strike}{winning_side}", close_price, "SELL", 1, f"CONTINUATION_CLOSE_{winning_side}")
                    
                    max_strike = max([p["strike"] for p in state[side_key]]) if state[side_key] else old_strike
                    new_strike = max_strike + 50 if winning_side == "CE" else max_strike - 50
                    
                    state[side_key].append({"strike": new_strike, "qty": 1, "created_at": tick_time_str})
                    create_trend_entry(tick_time_str, f"{new_strike}{winning_side}", close_price, "BUY", lot_count=1, trigger_type=f"CONTINUATION_OPEN_{winning_side}")
                    record_action(state, tick_time_str, "CONTINUATION_OPEN", f"{new_strike}{winning_side}", close_price, "BUY", 1, f"CONTINUATION_OPEN_{winning_side}")
                    
                    state["last_action_price"] = last_action_price + 50 if winning_side == "CE" else last_action_price - 50
                    
                    check_losing_side_defense(state, tick_time_str, close_price, shift_count=1)
                    
                    logger.info(f"[{t_str}] LAST DONE ACTION (CONTINUATION) -> Positions: {get_positions_summary(state)} | New Anchor: {state['last_action_price']}")
                    continue

            elif retracement_triggered:
                old_winning_side = winning_side
                new_winning_side = "PE" if winning_side == "CE" else "CE"
                logger.info(f"[{t_str}] RETRACEMENT TRIGGER met. Switching Winning Side from {winning_side} to {new_winning_side}. Spot: {close_price}, Last Anchor: {last_action_price}")
                
                state["current_winning_side"] = new_winning_side
                
                total_shifts = 1 
                new_side_key = "ce_positions" if new_winning_side == "CE" else "pe_positions"
                
                if state[new_side_key]:
                    state[new_side_key] = sorted(state[new_side_key], key=lambda x: x.get("created_at", ""))
                    old_pos = state[new_side_key].pop(0)
                    old_strike = old_pos["strike"]
                    
                    create_trend_entry(tick_time_str, f"{old_strike}{new_winning_side}", close_price, "SELL", lot_count=1, trigger_type=f"RETRACEMENT_CLOSE_{new_winning_side}")
                    record_action(state, tick_time_str, "RETRACEMENT_CLOSE", f"{old_strike}{new_winning_side}", close_price, "SELL", 1, f"RETRACEMENT_CLOSE_{new_winning_side}")
                    
                    max_strike = max([p["strike"] for p in state[new_side_key]]) if state[new_side_key] else old_strike
                    new_strike = max_strike + 50 if new_winning_side == "CE" else max_strike - 50
                    
                    state[new_side_key].append({"strike": new_strike, "qty": 1, "created_at": tick_time_str})
                    create_trend_entry(tick_time_str, f"{new_strike}{new_winning_side}", close_price, "BUY", lot_count=1, trigger_type=f"RETRACEMENT_OPEN_{new_winning_side}")
                    record_action(state, tick_time_str, "RETRACEMENT_OPEN", f"{new_strike}{new_winning_side}", close_price, "BUY", 1, f"RETRACEMENT_OPEN_{new_winning_side}")
                    
                    state["last_action_price"] = last_action_price - 75 if old_winning_side == "CE" else last_action_price + 75

                    current_anchor = state["last_action_price"]
                    if new_winning_side == "CE" and close_price >= current_anchor + 50:
                        total_shifts += 1
                        logger.info(f"[{t_str}] STACKED CONTINUATION TRIGGER detected alongside Retracement on {new_winning_side}.")
                        state[new_side_key] = sorted(state[new_side_key], key=lambda x: x.get("created_at", ""))
                        c_old_pos = state[new_side_key].pop(0)
                        c_old_strike = c_old_pos["strike"]
                        
                        create_trend_entry(tick_time_str, f"{c_old_strike}{new_winning_side}", close_price, "SELL", lot_count=1, trigger_type=f"CONTINUATION_CLOSE_{new_winning_side}")
                        record_action(state, tick_time_str, "CONTINUATION_CLOSE", f"{c_old_strike}{new_winning_side}", close_price, "SELL", 1, f"CONTINUATION_CLOSE_{new_winning_side}")
                        
                        c_max_strike = max([p["strike"] for p in state[new_side_key]]) if state[new_side_key] else c_old_strike
                        c_new_strike = c_max_strike + 50
                        
                        state[new_side_key].append({"strike": c_new_strike, "qty": 1, "created_at": tick_time_str})
                        create_trend_entry(tick_time_str, f"{c_new_strike}{new_winning_side}", close_price, "BUY", lot_count=1, trigger_type=f"CONTINUATION_OPEN_{new_winning_side}")
                        record_action(state, tick_time_str, "CONTINUATION_OPEN", f"{c_new_strike}{new_winning_side}", close_price, "BUY", 1, f"CONTINUATION_OPEN_{new_winning_side}")
                        
                        state["last_action_price"] = current_anchor + 50

                    elif new_winning_side == "PE" and close_price <= current_anchor - 50:
                        total_shifts += 1
                        logger.info(f"[{t_str}] STACKED CONTINUATION TRIGGER detected alongside Retracement on {new_winning_side}.")
                        state[new_side_key] = sorted(state[new_side_key], key=lambda x: x.get("created_at", ""))
                        c_old_pos = state[new_side_key].pop(0)
                        c_old_strike = c_old_pos["strike"]
                        
                        create_trend_entry(tick_time_str, f"{c_old_strike}{new_winning_side}", close_price, "SELL", lot_count=1, trigger_type=f"CONTINUATION_CLOSE_{new_winning_side}")
                        record_action(state, tick_time_str, "CONTINUATION_CLOSE", f"{c_old_strike}{new_winning_side}", close_price, "SELL", 1, f"CONTINUATION_CLOSE_{new_winning_side}")
                        
                        c_max_strike = max([p["strike"] for p in state[new_side_key]]) if state[new_side_key] else c_old_strike
                        c_new_strike = c_max_strike - 50
                        
                        state[new_side_key].append({"strike": c_new_strike, "qty": 1, "created_at": tick_time_str})
                        create_trend_entry(tick_time_str, f"{c_new_strike}{new_winning_side}", close_price, "BUY", lot_count=1, trigger_type=f"CONTINUATION_OPEN_{new_winning_side}")
                        record_action(state, tick_time_str, "CONTINUATION_OPEN", f"{c_new_strike}{new_winning_side}", close_price, "BUY", 1, f"CONTINUATION_OPEN_{new_winning_side}")
                        
                        state["last_action_price"] = current_anchor - 50
                    
                    check_losing_side_defense(state, tick_time_str, close_price, shift_count=total_shifts)
                    
                    logger.info(f"[{t_str}] LAST DONE ACTION (RETRACEMENT + STACK) -> Positions: {get_positions_summary(state)} | New Anchor: {state['last_action_price']}")
                    continue
            
            check_losing_side_defense(state, tick_time_str, close_price, shift_count=1)
            
            break

        state["last_processed_time"] = str(dt)
        save_state(state, today_str)

        action_logs.append({
            'datetime': dt,
            'spot': close_price,
            'winning_side': state["current_winning_side"],
            'total_ce_lots': len(state["ce_positions"]),
            'total_pe_lots': len(state["pe_positions"])
        })

    # Print Time-Sorted Actions History as a Table (Oldest First)
    if state and "actions_history" in state:
        sorted_actions = sorted(state["actions_history"], key=lambda x: x["timestamp"])
        
        df_actions = pd.DataFrame(sorted_actions)
        df_actions = df_actions.rename(columns={
            "timestamp": "Time",
            "action_type": "Action",
            "instrument": "Instrument",
            "price": "Price",
            "signal": "Signal",
            "lots": "Lots",
            "positions_summary": "Active Positions"
        })
        
        table_str = df_actions[["Time", "Action", "Instrument", "Price", "Signal", "Lots", "Active Positions"]].to_string(index=False)
        
        logger.info("=" * 160)
        logger.info("TIME-SORTED ACTIONS HISTORY & POSITION SUMMARY (PE Lowest to CE Highest, Oldest First):")
        logger.info("-" * 160)
        for line in table_str.split('\n'):
            logger.info(line)
        logger.info("=" * 160)

    logger.debug(f"Execution finished. Total action logs recorded: {len(action_logs)}")
    return pd.DataFrame(action_logs)

if __name__ == "__main__":
    logger.debug("Script execution started.")
    parser = argparse.ArgumentParser()
    parser.add_argument("--elapsed", type=int, default=0, help="Days elapsed for historical data fetch")
    parser.add_argument("--fresh", action="store_true", help="Remove existing state file and start fresh")
    args = parser.parse_args()
    logger.debug(f"Arguments parsed: elapsed={args.elapsed}, fresh={args.fresh}")
    
    if args.fresh:
        if os.path.exists(STATE_FILE):
            try:
                os.remove(STATE_FILE)
                logger.info(f"Fresh start requested via --fresh flag. Successfully removed old state file: {STATE_FILE}")
            except Exception as e:
                logger.error(f"Failed to remove state file {STATE_FILE}: {e}")
        else:
            logger.info(f"Fresh start requested, but state file '{STATE_FILE}' does not exist.")
    
    client_details = ['DB1485', 'ABcd$1234', '14121985', 'DB1485_API', 'c18634e5002598698626e3590b52c520']
    fetcher = StockDataFetcher(client_details)
    
    if fetcher.login():
        logger.debug("Login successful. Proceeding to fetch data...")
        df = fetcher.fetch_data('nifty', 1, 500, args.elapsed)
        if not df.empty:
            target_date = datetime.now(fetcher.ist).date() - timedelta(days=args.elapsed)
            logger.debug(f"Filtering dataframe for target date: {target_date}")
            result_df = df[df['datetime'].dt.date == target_date]
            logs_df = process_options_grid_strategy(result_df)
            if not logs_df.empty:
                logger.debug("Strategy logs generated successfully:")
                print(logs_df[['datetime', 'spot', 'winning_side', 'total_ce_lots', 'total_pe_lots']])
            else:
                logger.debug("Resulting logs dataframe is empty.")
        else:
            logger.debug("Fetched dataframe is empty.")
    else:
        logger.error("Login failed. Terminating.")