Dear all ,
mene badi mahnat karke ye alogo code banaya he
NIFTY 200 Stock EQ
Timeframe: 5-minute intraday chart,
Entry Base: EMA 9 & EMA 26 crossover,
Trade Confirmation Filters:Volume > 20-candle average,RSI > 56 (BUY) / RSI < 44 (SELL),ADX > 23 (strong trend),DI+ > DI− (BUY) / DI− > DI+ (SELL)
Stop Loss: Recent swing low (BUY) / swing high (SELL),
Target: Stop-loss ke equal (1:1 Risk-Reward)
Exit Conditions: Target hit, Stop-loss hit, Opposite EMA crossover (inverse signal)
Trailing SL: EMA 26 + ATR based (profit me aane ke baad)
Risk & Control: Fixed loss per trade, Max daily trades limit
Ye code sare conformation ke sath trade me enter hota hu fir bhi din ke always 4 ke 4 trade loss me close hota hai . muje samaj nahi ata ki code wrong hai / strategy , koi bata sakata hai to usko mera aabhar manata hu , din jara sa profit nahi aur itane sare loss & fix loss (daily loss + data api + brokerage + 1 jan se static IP price ) me ye sochata hu ki EQ me hal hai to Option me kya hota hoga , kya app profit me hote ho algo trading me ? me mera code share kiya he
So Say to time Algos trading bye bye
import time
import traceback
from Dhan_Tradehull import Tradehull
import pandas as pd
from pprint import pprint
import talib
import pandas_ta as ta
import xlwings as xw
from datetime import datetime, time as dt_time
import json
import os
import winsound
import requests
import joblib # Import for AI Model
# --- Load Credentials from config.json ---
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, 'config.json')
with open(config_path, 'r') as f:
creds = json.load(f)
def log_error_to_json(stock_name, error_context, error):
"""Logs an error to a JSON file and sends a Telegram alert."""
error_log_file = "errors.json"
timestamp = str(datetime.now())
error_message_full = str(error)
traceback_full = traceback.format_exc()
new_error = {
"timestamp": timestamp,
"stock": stock_name,
"context": error_context,
"error_message": error_message_full,
"traceback": traceback_full
}
try:
with open(error_log_file, 'r') as f:
errors = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
errors = []
errors.append(new_error)
with open(error_log_file, 'w') as f:
json.dump(errors, f, indent=4)
# --- Load Watchlist from stocks.json ---
def load_stocks_from_json(filename="stocks.json"):
"""Loads the stock list from a JSON file located in the script's directory."""
try:
stocks_path = os.path.join(script_dir, filename)
with open(stocks_path, 'r') as f:
data = json.load(f)
return data.get('stocks', [])
except FileNotFoundError:
print(f"Error: {filename} not found. Please ensure it exists in the script directory.")
return []
pre_market_watchlist = load_stocks_from_json()
watchlist = []
print("--- Starting Pre-Market Scan ---")
try:
if not pre_market_watchlist:
print("!!! Error: Watchlist is empty. Please check stocks.json !!!")
else:
print(f"Fetching LTP for {len(pre_market_watchlist)} stocks in one go...")
all_ltp_data = tsl.get_ltp_data(names=pre_market_watchlist)
if not all_ltp_data:
print("Could not fetch any LTP data. Exiting pre-market scan.")
else:
for name, ltp in all_ltp_data.items():
if ltp is None:
print(f"\tCould not get LTP for {name}, skipping.")
continue
# Price Filter
if 150 < ltp < 5000:
watchlist.append(name)
print(f"\t>>> Selected {name} for trading (Price: {ltp:.2f})")
else:
print(f"\tSkipping {name} due to price filter (Price: {ltp:.2f})")
except Exception as e:
print("\n" + "!"*60)
print(f"--- ERROR during pre-market scan: {e} ---")
print("!"*60 + "\n")
log_error_to_json("N/A", "Pre-Market Scan", e)
print(f"--- Pre-Market Scan Complete. {len(watchlist)} stocks selected. ---")
print(f"Final Watchlist: {watchlist}\n")
CONFIG = {
"leverage": 5.0, # Intraday leverage provided by the broker
"fixed_loss_per_trade": 100, # Risk a fixed 100 INR per trade
"max_daily_trades": 4, # Maximum number of trades to take per day
"max_risk_pct": 0.009, # Max risk as % of LTP. 0.006 = 0.6%. Trade skipped if SL is wider.
"rr_ratio": 1, # Risk:Reward Ratio for target calculation (e.g., 1:1)
"trade_direction": "BOTH" # "LONG" for buys only, "SHORT" for sells only, "BOTH" for both
,"min_trade_value_inr": 4000, # Minimum trade value in INR to consider taking a trade
"max_daily_loss_inr": 250, # Stop trading if daily loss exceeds this amount (e.g., 250 INR)
"target_daily_profit": 200, # Target daily profit to stop trading
"swing_lookback_period": 6 # Lookback period for calculating swing low/high for SL
,"tsl_activation_r": 0.5 # Activate TSL after price moves 0.5R in profit
}
single_order = {'name':None, 'date':None , 'entry_time': None, 'entry_price': None, 'buy_sell': None, 'qty': None, 'sl': None,
'exit_time': None, 'exit_price': None, 'pnl': None, 'remark': None, 'traded':None, 'entry_trigger': None,
'entry_rsi': None, 'entry_volume': None, 'entry_atr': None, # Entry indicators
'mfe': 0, 'mae': 0, 'mfe_total': 0, 'mae_total': 0, 'breakeven_triggered': False}
orderbook = {}
excel_file_path = 'Live Trade Data.xlsx'
try:
wb = xw.Book(excel_file_path)
# Ensure sheets exist
if 'Live_Trading' not in [s.name for s in wb.sheets]:
wb.sheets.add('Live_Trading')
if 'completed_orders' not in [s.name for s in wb.sheets]:
wb.sheets.add('completed_orders')
if 'Skipped_Trades' not in [s.name for s in wb.sheets]:
wb.sheets.add('Skipped_Trades')
except FileNotFoundError:
wb = xw.Book()
wb.sheets.add('Live_Trading')
wb.sheets.add('completed_orders')
wb.sheets.add('Skipped_Trades')
wb.sheets['Sheet1'].delete() # remove default sheet
wb.save(excel_file_path)
live_Trading = wb.sheets['Live_Trading']
completed_orders_sheet = wb.sheets['completed_orders']
skipped_trades_sheet = wb.sheets['Skipped_Trades']
completed_orders = []
skipped_trades = []
live_Trading.range("A2:Z100").value = None
for name in watchlist:
orderbook[name] = single_order.copy()
print("Performing initial fill of the Live_Trading sheet with all watchlist stocks...")
initial_df = pd.DataFrame(orderbook).T
live_Trading.range('A1').value = initial_df
trades_today_count = 0
current_day_tracker = datetime.now().date()
def save_state():
"""Saves the current state of the bot to a JSON file."""
state = {
"orderbook": orderbook,
"completed_orders": completed_orders,
"skipped_trades": skipped_trades,
"trades_today_count": trades_today_count,
"current_day_tracker": str(current_day_tracker)
}
with open("EMA_stratergy_state.json", "w") as f:
json.dump(state, f, indent=4)
update_excel_sheets()
print("--- State Saved ---")
def load_state():
"""Loads the bot's state from a JSON file."""
try:
with open("EMA_stratergy_state.json") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return None
def update_excel_sheets():
global wb, live_Trading, completed_orders_sheet, skipped_trades_sheet
try:
orderbook_df = pd.DataFrame(orderbook).T
if not orderbook_df.empty and 'traded' in orderbook_df.columns and not orderbook_df['traded'].isnull().all():
orderbook_df_sorted = orderbook_df.sort_values(by='traded', ascending=False, na_position='last')
live_Trading.range('A1').value = orderbook_df_sorted
else:
live_Trading.range('A1').value = orderbook_df
completed_orders_df = pd.DataFrame(completed_orders)
for col in ['mfe', 'mae', 'mfe_total', 'mae_total']:
if col not in completed_orders_df.columns:
completed_orders_df[col] = 0
else:
completed_orders_df[col] = completed_orders_df[col].fillna(0)
if completed_orders_sheet.range('A1').value is None:
completed_orders_sheet.range('A1').value = completed_orders_df
else:
last_row = completed_orders_sheet.range('A' + str(completed_orders_sheet.cells.last_cell.row)).end('up').row
existing_count = max(0, last_row - 1)
if len(completed_orders_df) > existing_count:
new_rows = completed_orders_df.iloc[existing_count:]
completed_orders_sheet.range(f'A{last_row + 1}').options(index=False, header=False).value = new_rows
elif len(completed_orders_df) < existing_count:
completed_orders_sheet.clear_contents()
completed_orders_sheet.range('A1').value = completed_orders_df
skipped_trades_df = pd.DataFrame(skipped_trades)
skipped_trades_sheet.range('A1').value = skipped_trades_df
wb.save()
except Exception as e:
print("\n" + "!"*60)
print(f"--- ERROR updating Excel sheets: {e} ---")
print("!"*60 + "\n")
log_error_to_json("N/A", "update_excel_sheets", e)
if "RPC server" in str(e) or "disconnected" in str(e):
print("--- Excel disconnected. Attempting to reconnect... ---")
try:
wb = xw.Book(excel_file_path)
live_Trading = wb.sheets['Live_Trading']
completed_orders_sheet = wb.sheets['completed_orders']
skipped_trades_sheet = wb.sheets['Skipped_Trades']
print("--- Excel Reconnected Successfully. ---")
except Exception as rec_e:
print(f"--- Reconnection failed: {rec_e} ---")
def calculate_indicators(df):
df['ema9'] = talib.EMA(df['close'], timeperiod=9)
df['ema26'] = talib.EMA(df['close'], timeperiod=26)
df['rsi14'] = talib.RSI(df['close'], timeperiod=14)
df['atr14'] = talib.ATR(df['high'], df['low'], df['close'], timeperiod=14)
df['volume_avg_20'] = df['volume'].rolling(window=20).mean()
df['adx14'] = talib.ADX(df['high'], df['low'], df['close'], timeperiod=14)
df['plus_di'] = talib.PLUS_DI(df['high'], df['low'], df['close'], timeperiod=14) # Trend Direction
df['minus_di'] = talib.MINUS_DI(df['high'], df['low'], df['close'], timeperiod=14) # Trend Direction
return df
def get_entry_signal(df):
Returns 'BUY', 'SELL', or None.
"""
last_candle = df.iloc[-2]
prev_candle = df.iloc[-3]
is_bullish_crossover = prev_candle['ema9'] < prev_candle['ema26'] and last_candle['ema9'] > last_candle['ema26']
if CONFIG["trade_direction"] in ["LONG", "BOTH"] and is_bullish_crossover:
is_volume_strong = last_candle['volume'] > (last_candle['volume_avg_20'])
is_rsi_strong = last_candle['rsi14'] > 56
is_trend_strong = last_candle['adx14'] > 23 and last_candle['plus_di'] > last_candle['minus_di'] # ADX Direction Check
if is_volume_strong and is_rsi_strong and is_trend_strong:
print(f"--- 🔥 STRONG BUY SIGNAL (EMA Cross + Vol Spike + DI+) ---")
return 'BUY'
# --- SELL Signal Logic ---
is_bearish_crossover = prev_candle['ema9'] > prev_candle['ema26'] and last_candle['ema9'] < last_candle['ema26']
if CONFIG["trade_direction"] in ["SHORT", "BOTH"] and is_bearish_crossover:
is_volume_strong = last_candle['volume'] > (last_candle['volume_avg_20'] * 1.5)
is_rsi_weak = last_candle['rsi14'] < 44
is_trend_strong = last_candle['adx14'] > 23 and last_candle['minus_di'] > last_candle['plus_di'] # ADX Direction Check
if is_volume_strong and is_rsi_weak and is_trend_strong:
print(f"--- 🔥 STRONG SELL SIGNAL (EMA Cross + Vol Spike + DI-) ---")
return 'SELL'
return None
def get_inverse_exit_signal(df):
"""
Analyzes the last two candles for a simple EMA crossover to be used as an exit signal.
This does NOT check for RSI, Volume, or ADX filters.
Returns 'BUY', 'SELL', or None.
"""
last_candle = df.iloc[-2]
prev_candle = df.iloc[-3]
if prev_candle['ema9'] < prev_candle['ema26'] and last_candle['ema9'] > last_candle['ema26']:
return 'BUY'
if prev_candle['ema9'] > prev_candle['ema26'] and last_candle['ema9'] < last_candle['ema26']:
return 'SELL'
return None
def calculate_sl_tg(signal, ltp, df):
lookback = CONFIG["swing_lookback_period"]
recent_candles = df.iloc[-(lookback + 1):-1]
if signal == 'BUY':
stop_loss_price = round(recent_candles['low'].min() - 0.05, 1)
risk_per_share = ltp - stop_loss_price
target_price = round(ltp + (risk_per_share * CONFIG["rr_ratio"]), 1)
elif signal == 'SELL':
stop_loss_price = round(recent_candles['high'].max() + 0.05, 1)
risk_per_share = stop_loss_price - ltp
target_price = round(ltp - (risk_per_share * CONFIG["rr_ratio"]), 1)
else:
return None, None
return stop_loss_price, target_price
def manage_open_position(name, ltp, df):
pass
def _log_and_skip_trade(name, context, reason, signal=None, price=None, **kwargs):
"""Helper function to log a skipped trade without saving state immediately."""
print(f"Skipping {name}: {context} - {reason}")
entry = {'date': str(datetime.now().date()), 'name': name, 'time': str(datetime.now().time())[:8], 'context': context, 'reason': reason, 'signal': signal, 'price': price}
entry.update(kwargs) # Add extra indicators (RSI, ADX, etc.) if provided
skipped_trades.append(entry)
def calculate_position_and_validate(name, ltp, stop_loss_price, signal=None):
"""Calculates quantity based on fixed loss and validates margin. Returns qty or None."""
risk_per_share = abs(ltp - stop_loss_price)
if risk_per_share <= 0:
_log_and_skip_trade(name, 'Validation', f'Invalid risk per share: {risk_per_share:.2f}', signal=signal, price=ltp)
return None
if (risk_per_share / ltp) > CONFIG["max_risk_pct"]:
_log_and_skip_trade(name, 'Validation', f'SL is too wide ({risk_per_share/ltp:.2%} > {CONFIG["max_risk_pct"]:.2%})', signal=signal, price=ltp)
return None
qty = int(CONFIG["fixed_loss_per_trade"] / risk_per_share)
if qty == 0:
_log_and_skip_trade(name, 'Validation', f'Qty is 0 (Risk/share {risk_per_share:.2f} too high)', signal=signal, price=ltp)
return None
trade_value = qty * ltp
if trade_value < CONFIG["min_trade_value_inr"]:
_log_and_skip_trade(name, 'Validation', f"Trade value {trade_value:.2f} < min {CONFIG['min_trade_value_inr']}", signal=signal, price=ltp)
return None
margin_available = tsl.get_balance()
margin_required = ((qty * ltp) / CONFIG["leverage"]) + 300
if margin_available < margin_required:
_log_and_skip_trade(name, 'Validation', f'Insufficient margin (Avail: {margin_available:.2f}, Req: {margin_required:.2f})', signal=signal, price=ltp)
return None
return qty
def place_trade(name, buy_sell_label, qty, stop_loss_price, target_price):
"""Places entry and SL orders and updates the orderbook."""
global trades_today_count
# --- Final Safety Checks Before Placement ---
if trades_today_count >= CONFIG["max_daily_trades"]:
print(f"!!! STOPPING TRADE for {name}: Max daily trades ({CONFIG['max_daily_trades']}) limit reached. !!!")
return
if orderbook[name]['traded'] == "yes":
print(f"!!! STOPPING TRADE for {name}: Position already exists. !!!")
return
transaction_type = 'BUY' if buy_sell_label == 'BUY' else 'SELL'
sl_transaction_type = 'SELL' if buy_sell_label == 'BUY' else 'BUY'
try:
entry_orderid = tsl.order_placement(tradingsymbol=name, exchange='NSE', quantity=qty, price=0, trigger_price=0, order_type='MARKET', transaction_type=transaction_type, trade_type='MIS')
entry_price = None
if entry_orderid:
time.sleep(1) # Wait 1 second
entry_price = tsl.get_executed_price(orderid=entry_orderid)
if not entry_price: # If still not found, retry after 2 more seconds
time.sleep(2)
entry_price = tsl.get_executed_price(orderid=entry_orderid)
if not entry_price:
if entry_orderid:
status = tsl.get_order_status(orderid=entry_orderid)
raise Exception(f"Order placed but price not found. Status: {status}")
else:
raise Exception("Order placement failed (No Order ID returned).")
sl_orderid = tsl.order_placement(tradingsymbol=name, exchange='NSE', quantity=qty, price=0, trigger_price=stop_loss_price, order_type='STOPMARKET', transaction_type=sl_transaction_type, trade_type='MIS')
orderbook[name].update({
'entry_orderid': entry_orderid, 'entry_price': entry_price, 'tg': target_price,
'sl': stop_loss_price, 'sl_orderid': sl_orderid, 'traded': "yes"
})
print(f" >>> {buy_sell_label} Trade Placed for {name}: Entry @ {entry_price:.2f}, Target @ {target_price:.2f}, SL @ {stop_loss_price:.2f}")
winsound.Beep(1000, 500) # Beep for successful order placement
trades_today_count += 1
print(f"--- Trade Count Updated: {trades_today_count}/{CONFIG['max_daily_trades']} ---")
save_state()
except Exception as e:
print("\n" + "!"*60)
print(f"--- ERROR IN ENTRY ORDER for {name} ---")
print(traceback.format_exc())
print("!"*60 + "\n")
log_error_to_json(name, "place_trade", e)
orderbook[name] = single_order.copy() # Reset on failure
def close_position(name, remark, exit_price):
orderbook[name]['exit_time'] = str(datetime.now().time())[:8]
orderbook[name]['exit_price'] = exit_price
pnl_multiplier = 1 if orderbook[name]['buy_sell'] == 'BUY' else -1
orderbook[name]['pnl'] = round((exit_price - orderbook[name]['entry_price']) * orderbook[name]['qty'] * pnl_multiplier, 1)
orderbook[name]['remark'] = remark
completed_orders.append(orderbook[name].copy())
orderbook[name] = single_order.copy()
if "TG_hit" in remark:
winsound.Beep(1500, 1000) # Shorter beep
elif "SL_hit" in remark:
winsound.Beep(500, 1000) # Lower pitch beep for SL hit
save_state()
print("--- Attempting to load previous state ---")
loaded_state = load_state()
if loaded_state:
loaded_date = datetime.strptime(loaded_state.get("current_day_tracker"), '%Y-%m-%d').date()
# Always load historical completed and skipped trades
completed_orders = loaded_state.get("completed_orders", [])
skipped_trades = loaded_state.get("skipped_trades", [])
if loaded_date == datetime.now().date():
# If it's the same day, restore the full state
orderbook = loaded_state.get("orderbook", orderbook)
trades_today_count = loaded_state.get("trades_today_count", trades_today_count)
print("--- State successfully loaded for today! ---")
# Ensure all watchlist stocks are in the orderbook
for stock_name in watchlist:
if stock_name not in orderbook:
orderbook[stock_name] = single_order.copy()
else:
# Patch existing orders with new keys (like mfe, mae) if missing in old state
for key, val in single_order.items():
if key not in orderbook[stock_name]:
orderbook[stock_name][key] = val
else:
print("--- Saved state is from a previous day. Resetting daily counters but keeping historical trades. ---")
# Daily counters are already reset at initialization, so we just need to log this.
update_excel_sheets()
while True:
current_dt = datetime.now()
print(f"starting while Loop {current_dt.strftime('%Y-%m-%d %H:%M:%S')}\n\n")
live_broker_orderbook = tsl.get_orderbook()
total_pnl = tsl.get_live_pnl()
current_balance = tsl.get_balance()
# print("--- Live Broker Orderbook ---")
# pprint(live_broker_orderbook)
# print("-----------------------------\n")
print(f"Live PNL: {total_pnl}, Balance: {current_balance}")
if current_dt.date() != current_day_tracker:
print("New day detected. Resetting daily trade counter.")
trades_today_count = 0
current_day_tracker = current_dt.date()
current_time = current_dt.time()
# if current_time < dt_time(9, 45):
# print(f"Waiting for market to open. Current time: {current_time.strftime('%H:%M:%S')}")
# time.sleep(5)
# continue
# if current_time > dt_time(14, 50):
# order_details = tsl.cancel_all_orders()
# print(f"Market over Closing all trades !! Bye Bye See you Tomorrow", current_time)
# update_excel_sheets()
# save_state() # Final save before exiting
# send_telegram_message(f"🛑 Market Over! Bot stopping. Final PnL: {total_pnl}")
# time.sleep(1)
# break
all_ltp = tsl.get_ltp_data(names=watchlist)
open_positions = [name for name, details in orderbook.items() if details.get('traded') == 'yes']
if total_pnl is not None and total_pnl >= CONFIG["target_daily_profit"]:
print(f"🎉 Daily Profit Target Hit: {total_pnl} >= {CONFIG['target_daily_profit']} INR. Closing all positions and exiting.")
for name in open_positions:
try:
if orderbook[name].get('sl_orderid'):
tsl.cancel_order(OrderID=orderbook[name]['sl_orderid'])
bought = orderbook[name]['buy_sell'] == "BUY"
txn_type = 'SELL' if bought else 'BUY'
tsl.order_placement(name, 'NSE', orderbook[name]['qty'], 0, 0, 'MARKET', txn_type, 'MIS')
exit_price = all_ltp.get(name, orderbook[name]['entry_price']) if all_ltp else orderbook[name]['entry_price']
close_position(name, "Daily_Target_Hit", exit_price)
except Exception as e:
print(f"Error closing {name}: {e}")
save_state()
update_excel_sheets()
break
if trades_today_count >= CONFIG["max_daily_trades"]:
print(f"Max daily trades ({CONFIG['max_daily_trades']}) reached. Managing open positions only.")
if not open_positions: # If no positions are open
print("All trades for the day are complete. Shutting down.")
save_state()
update_excel_sheets() # Final update before exiting
send_telegram_message(f"🛑 Max Daily Trades Reached ({trades_today_count}). Bot stopping.")
break # Exit the main while loop
def process_stock(name):
print(f"Scanning {name} {current_dt.strftime('%Y-%m-%d %H:%M:%S')}")
try:
if any(order.get('name') == name and order.get('date') == str(current_dt.date()) for order in completed_orders):
return # Use return to exit the function for this stock
chart_5m = tsl.get_historical_data(tradingsymbol=name, exchange='NSE', timeframe="5")
if chart_5m is None or len(chart_5m) < 35:
_log_and_skip_trade(name, "Data Check", "Insufficient historical data")
return
chart_5m = calculate_indicators(chart_5m)
if orderbook[name]['traded'] == "yes":
ltp = all_ltp.get(name)
if ltp is None:
print(f"Could not get LTP for open position {name}. Skipping management.")
return
bought = orderbook[name]['buy_sell'] == "BUY"
sold = orderbook[name]['buy_sell'] == "SELL"
current_pnl_per_share = (ltp - orderbook[name]['entry_price']) if bought else (orderbook[name]['entry_price'] - ltp)
if current_pnl_per_share > orderbook[name]['mfe']:
orderbook[name]['mfe'] = current_pnl_per_share
if current_pnl_per_share < orderbook[name]['mae']:
orderbook[name]['mae'] = current_pnl_per_share
orderbook[name]['mfe_total'] = round(orderbook[name]['mfe'] * orderbook[name]['qty'], 2)
orderbook[name]['mae_total'] = round(orderbook[name]['mae'] * orderbook[name]['qty'], 2)
last_candle = chart_5m.iloc[-2]
prev_candle = chart_5m.iloc[-3]
sl_hit = tsl.get_order_status(orderid=orderbook[name]['sl_orderid']) == "TRADED"
tg_hit = (ltp >= orderbook[name]['tg']) if bought else (ltp <= orderbook[name]['tg'])
if sl_hit:
exit_price = tsl.get_executed_price(orderid=orderbook[name]['sl_orderid']) or orderbook[name]['sl']
close_position(name, "Bought_SL_hit" if bought else "Sold_SL_hit", exit_price)
return
elif tg_hit:
tsl.cancel_order(OrderID=orderbook[name]['sl_orderid'])
time.sleep(1)
txn_type = 'SELL' if bought else 'BUY'
square_off_order = tsl.order_placement(name, 'NSE', orderbook[name]['qty'], 0, 0, 'MARKET', txn_type, 'MIS')
exit_price = tsl.get_executed_price(orderid=square_off_order) or ltp
close_position(name, "Bought_TG_hit" if bought else "Sold_TG_hit", exit_price)
return
# --- INVERSE SIGNAL EXIT LOGIC ---
entry_time_str = orderbook[name]['entry_time']
try:
entry_dt = datetime.combine(current_dt.date(), datetime.strptime(entry_time_str, "%H:%M:%S").time())
seconds_since_entry = (current_dt - entry_dt).total_seconds()
except Exception:
seconds_since_entry = 601 # Default to allow check if time parsing fails
if seconds_since_entry > 600:
inverse_signal = get_inverse_exit_signal(chart_5m) # Use the simpler exit signal function
if inverse_signal and ((bought and inverse_signal == 'SELL') or (sold and inverse_signal == 'BUY')):
print(f"--- INVERSE SIGNAL DETECTED for {name}. Exiting position. ---")
tsl.cancel_order(OrderID=orderbook[name]['sl_orderid'])
time.sleep(1) # Wait for cancellation to process
txn_type = 'SELL' if bought else 'BUY'
square_off_order = tsl.order_placement(name, 'NSE', orderbook[name]['qty'], 0, 0, 'MARKET', txn_type, 'MIS')
exit_price = tsl.get_executed_price(orderid=square_off_order) or ltp
close_position(name, "Inverse_Signal_Exit", exit_price)
return
else: # Only trail if no other exit condition was met
try:
initial_risk = abs(orderbook[name]['entry_price'] - orderbook[name]['sl'])
current_pnl_per_share = (ltp - orderbook[name]['entry_price']) if bought else (orderbook[name]['entry_price'] - ltp)
if current_pnl_per_share >= (initial_risk * CONFIG["tsl_activation_r"]):
if bought:
new_trailing_sl = round(last_candle['ema26'] - (last_candle['atr14'] * 0.8), 1)
# We only trail up, never down. And SL should not be above LTP.
if new_trailing_sl > orderbook[name]['sl'] and new_trailing_sl < ltp:
print(f"--- TRAILING SL (BUY) for {name}: Old SL: {orderbook[name]['sl']:.2f}, New SL: {new_trailing_sl:.2f} ---")
new_sl_orderid = tsl.modify_order(order_id=orderbook[name]['sl_orderid'], quantity=orderbook[name]['qty'], price=0, trigger_price=new_trailing_sl, order_type='STOPMARKET')
if new_sl_orderid:
orderbook[name].update({'sl_orderid': new_sl_orderid, 'sl': new_trailing_sl})
save_state()
elif sold:
new_trailing_sl = round(last_candle['ema26'] + (last_candle['atr14'] * 0.8), 1)
# We only trail down, never up. And SL should not be below LTP.
if new_trailing_sl < orderbook[name]['sl'] and new_trailing_sl > ltp:
print(f"--- TRAILING SL (SELL) for {name}: Old SL: {orderbook[name]['sl']:.2f}, New SL: {new_trailing_sl:.2f} ---")
new_sl_orderid = tsl.modify_order(order_id=orderbook[name]['sl_orderid'], quantity=orderbook[name]['qty'], price=0, trigger_price=new_trailing_sl, order_type='STOPMARKET')
if new_sl_orderid:
orderbook[name].update({'sl_orderid': new_sl_orderid, 'sl': new_trailing_sl})
save_state()
except Exception as e:
log_error_to_json(name, "Trailing_SL", e)
elif orderbook[name]['traded'] is None:
if trades_today_count >= CONFIG["max_daily_trades"]:
return
signal = get_entry_signal(chart_5m)
if not signal:
return
# Daily loss limit check
todays_pnl = sum(t.get('pnl', 0) for t in completed_orders if t.get('date') == str(current_dt.date()))
if todays_pnl < -CONFIG["max_daily_loss_inr"]:
_log_and_skip_trade(name, 'Risk Management', 'Daily loss limit reached', signal=signal, price=all_ltp.get(name))
return
ltp = all_ltp.get(name)
if ltp is None or ltp == 0:
_log_and_skip_trade(name, 'Data Fetch', 'Could not get LTP', signal=signal)
return
stop_loss_price, target_price = calculate_sl_tg(signal, ltp, chart_5m)
qty = calculate_position_and_validate(name, ltp, stop_loss_price, signal=signal)
if qty:
last_candle = chart_5m.iloc[-2] entry_rsi=last_candle['rsi14'], entry_adx=last_candle['adx14'], entry_atr=last_candle['atr14'])
return
orderbook[name].update({
'name': name, 'date': str(current_dt.date()), 'entry_time': str(current_dt.time())[:8],
'buy_sell': signal, 'qty': qty,
'entry_rsi': round(last_candle['rsi14'], 2), 'entry_volume': int(last_candle['volume']),
'entry_atr': round(last_candle['atr14'], 2), 'entry_adx': round(last_candle['adx14'], 2)
})
place_trade(name, signal, qty, stop_loss_price, target_price)
except Exception as e:
print("\n" + "!"*60)
print(f"--- FATAL ERROR in main loop for {name}: {e} ---")
print(traceback.format_exc())
print("!"*60 + "\n")
log_error_to_json(name, "Main Loop", e)
time.sleep(2) # 2-second delay to prevent API rate limits
# --- Main Loop: Manage open positions and scan for new trades ---
open_positions = [name for name, details in orderbook.items() if details.get('traded') == 'yes']
other_stocks = [name for name in watchlist if name not in open_positions]
# 1. Always process open positions first
if open_positions:
print("--- Managing Open Positions ---")
for name in open_positions:
process_stock(name)
# 2. Scan for new trades only if the daily limit has not been reached
if trades_today_count < CONFIG["max_daily_trades"]:
chunk_size = 20
for i in range(0, len(other_stocks), chunk_size):
stock_chunk = other_stocks[i:i + chunk_size]
print(f"\n--- Scanning batch of {len(stock_chunk)} stocks for new entries... ---")
for name in stock_chunk:
process_stock(name)
else:
print(f"--- Max daily trades ({trades_today_count}/{CONFIG['max_daily_trades']}) reached. Skipping scan for new entries. ---")
save_state() # Save state once at the end of the main loop
print(f"--- Scan cycle finished. Waiting for next cycle. ---")
time.sleep(5)