Please help , my code buy order placed successfully but SL order / Exit order not place what is missing/mistake , in coding
Thanks in advance
below code i run
import pandas as pd
from Dhan_Tradehull import Tradehull
import time
import json
from typing import List, Dict
import pandas as pd
import math
from datetime import datetime, timedelta, time as dt_time
import os
import pdb
import talib
import numpy as np
import pandas_ta as ta
import time
try:
with open('config.json', 'r') as f:
config = json.load(f)
client_id = config['client_id']
access_token = config['access_token']
except FileNotFoundError:
print("Error: config.json not found.client_id and access_token.")
exit()
tsl = Tradehull(client_id, access_token)
try:
with open('stocks.json', 'r') as f:
stocks_data = json.load(f)
stocks = stocks_data['stocks']
print(f"Successfully loaded {len(stocks)} stocks from stocks.json")
except FileNotFoundError:
print("Error: stocks.json not found. Please create it with a list of stock symbols.")
stocks = []
TIMEFRAME_MINUTES = "15"
TIMEFRAME_DHAN_API = str(TIMEFRAME_MINUTES)
CAPITAL_ALLOCATION_PERCENT = 0.50
RISK_REWARD_RATIO = 2.0
ATR_PERIOD = 14
ATR_MULTIPLIER = 1.5
MAX_DAILY_LOSS = -2000
MAX_DAILY_TRADES = 6
#RISK_PER_TRADE_PERCENT = 0.05
MAX_RISK_PER_TRADE_RUPEES = 500
MAX_CONCURRENT_TRADES = 4
def wait_for_next_candle(timeframe_minutes: int):
"""Calculates and waits for the start of the next market candle."""
now = datetime.now()
next_candle_minute = (now.minute // timeframe_minutes + 1) * timeframe_minutes
if next_candle_minute >= 60:
next_candle_time = now.replace(minute=0, second=5, microsecond=0) + timedelta(hours=1)
else:
next_candle_time = now.replace(minute=next_candle_minute, second=5, microsecond=0)
wait_seconds = (next_candle_time - now).total_seconds()
print(f"--- Syncing with market... Waiting {int(wait_seconds)} seconds for the next {timeframe_minutes}-min candle at {next_candle_time.strftime('%H:%M:%S')} ---")
time.sleep(max(0, wait_seconds)) # Ensure wait_seconds is not negative
def get_price_filtered_watchlist(full_watchlist):
print("--- [DEBUG] Entering get_price_filtered_watchlist ---")
print("--- Pre-filtering watchlist by price (₹25 - ₹5000) ---")
filtered_stocks = []
try:
print(f"--- [API-SEND] get_ltp_data for {len(full_watchlist)} stocks ---")
ltp_data = tsl.get_ltp_data(full_watchlist)
print(f"--- [API-RECV] get_ltp_data received: {ltp_data} ---")
if not ltp_data:
print("Could not fetch LTP data for watchlist. Using full list.")
return full_watchlist
for stock, price in ltp_data.items():
if 25 < price < 5000:
filtered_stocks.append(stock)
print(f"Price filter reduced watchlist from {len(full_watchlist)} to {len(filtered_stocks)} stocks.")
print("--- [DEBUG] Exiting get_price_filtered_watchlist (success) ---")
return filtered_stocks
except Exception as e:
print(f"--- [DEBUG] ERROR in get_price_filtered_watchlist: {e}. Using full list. ---")
return full_watchlist
def run_macd_strategy():
print("--- [DEBUG] Entering run_macd_strategy ---")
print("--- Starting MACD Crossover Strategy ---")
print("\n--- Performing Initial Daily Scan ---")
eligible_stocks = get_price_filtered_watchlist(stocks)
print("--- Initial Daily Scan Complete. Starting trading loop. ---")
if not eligible_stocks:
print("No eligible stocks found after price filtering. The script will wait.")
kill_switch_activated = False
trades_today_count = 0
current_day = datetime.today().date()
try:
print("--- [DEBUG] Attempting to fetch initial positions and order book. ---")
print("--- [API-SEND] get_positions ---")
positions_df = tsl.get_positions()
print(f"--- [API-RECV] get_positions received:\n{positions_df.to_string()} ---")
open_positions = {}
if positions_df is not None and not positions_df.empty:
# Filter for open MIS positions, which is what this bot trades.
mis_positions = positions_df[(positions_df['netQty'] != 0) & (positions_df['productType'] == 'MIS')]
for index, row in mis_positions.iterrows():
symbol = row['tradingSymbol']
qty = int(row['netQty'])
if symbol in open_positions: continue # Already processed
print(f"--- [DEBUG] Found existing position for {symbol}. Placing protective SL order. ---")
try:
# Fetch data to calculate SL
hist_data_sl = tsl.get_historical_data(tradingsymbol=symbol, exchange='NSE', timeframe=TIMEFRAME_DHAN_API)
if hist_data_sl is None or len(hist_data_sl) < 5:
print(f"WARNING: Not enough data for {symbol} to set SL. Position is unprotected.")
continue
# Calculate SL price (same logic as new trades)
stop_loss_price = hist_data_sl['low'].tail(5).min() - 0.05
# Place the SL order
sl_order_params = {
'tradingsymbol': symbol, 'exchange': 'NSE', 'quantity': abs(qty),
'order_type': 'STOP_LOSS', 'transaction_type': 'SELL', 'trade_type': 'MIS',
'price': 0, 'trigger_price': float(stop_loss_price)
}
print(f"--- [API-SEND] order_placement (SL) for existing position {symbol} with params:\n{sl_order_params} ---")
sl_order_id = tsl.order_placement(**sl_order_params)
print(f"--- [API-RECV] order_placement (SL) received order ID: {sl_order_id} ---")
if sl_order_id:
print(f"SUCCESS: Placed protective SL for existing position {symbol} with ID: {sl_order_id}")
open_positions[symbol] = {
'qty': abs(qty), 'buy_order_id': None, 'sl_order_id': sl_order_id,
'sl_price': stop_loss_price, 'direction': 'BUY' if qty > 0 else 'SELL'
}
else:
print(f"CRITICAL WARNING: Failed to place protective SL for {symbol}. Position is unprotected.")
except Exception as e_sl:
print(f"--- [DEBUG] ERROR placing protective SL for {symbol}: {e_sl} ---")
if open_positions:
print(f"Found existing open positions: {list(open_positions.keys())}")
print("--- [DEBUG] Successfully processed initial positions. ---")
except Exception as e:
print(f"--- [DEBUG] ERROR fetching initial positions: {e} ---")
open_positions = {}
while True:
print("\n--- [DEBUG] Top of the main trading loop. ---")
try:
current_time = datetime.now().time()
print(f"--- [DEBUG] Time check. Current time: {current_time.strftime('%H:%M:%S')} ---")
if current_time > dt_time(15, 30):
print("--- [DEBUG] Time is after 15:30. Shutting down. ---")
print("Market is closed (after 15:30). Stopping strategy for the day.")
break
if current_time > dt_time(15, 10):
print("--- [DEBUG] Time is after 15:10. Cancelling all orders and stopping for the day. ---")
print("--- [API-SEND] cancel_all_orders ---")
tsl.cancel_all_orders()
print("--- [API-RECV] cancel_all_orders sent. ---")
tsl.get_live_pnl()
print (live_pnl)
break
if not (dt_time(9, 40) <= current_time <= dt_time(15, 0)):
print(f"--- [DEBUG] Time {current_time.strftime('%H:%M:%S')} is outside the trading window (09:40 - 15:00). Waiting... ---")
time.sleep(60)
continue
if datetime.today().date() != current_day:
print("--- [DEBUG] New day detected. ---")
print("New day detected. Resetting daily limits and kill switch.")
current_day = datetime.today().date()
kill_switch_activated = False
trades_today_count = 0
print("\n--- Performing Initial Daily Scan for new day---")
eligible_stocks = get_price_filtered_watchlist(stocks)
print("--- Initial Daily Scan Complete. Starting trading loop. ---")
if kill_switch_activated:
print("--- [DEBUG] Kill switch is active, skipping trade logic. ---")
print(f"Kill switch is active. No new trades will be placed today. Waiting for next day.")
time.sleep(60 * 5)
continue
print("--- [DEBUG] Checking balance and P&L. ---")
print("--- [API-SEND] get_balance ---")
available_balance = tsl.get_balance()
print(f"--- [API-RECV] get_balance received: {available_balance} ---")
if not available_balance or available_balance == 0:
print("Could not fetch balance or balance is zero. Waiting...")
time.sleep(60)
continue
risk_from_capital = available_balance
risk_amount = min(risk_from_capital, MAX_RISK_PER_TRADE_RUPEES)
print("--- [API-SEND] get_live_pnl ---")
live_pnl = tsl.get_live_pnl()
print(f"--- [API-RECV] get_live_pnl received: {live_pnl} ---")
print(f"Current Day P&L: ₹{live_pnl:.2f}")
if live_pnl is not None and live_pnl < MAX_DAILY_LOSS:
print("--- [DEBUG] Max daily loss reached. Activating kill switch. ---")
print(f"!!! MAX DAILY LOSS of ₹{MAX_DAILY_LOSS} REACHED. ACTIVATING KILL SWITCH. !!!")
print("--- Closing all open positions. ---")
for symbol in list(open_positions.keys()):
print(f"--- Closing position for {symbol} due to kill switch. ---")
# First, cancel the pending SL order to avoid it being orphaned
trade_info = open_positions.get(symbol)
if trade_info and trade_info.get('sl_order_id'):
sl_order_id_to_cancel = trade_info['sl_order_id']
print(f"--- [API-SEND] cancel_order for SL ID: {sl_order_id_to_cancel} ---")
tsl.cancel_order(trade_info['sl_order_id'])
print(f"--- [API-RECV] cancel_order sent for {sl_order_id_to_cancel} ---")
print(f"--- [API-SEND] exit_position for {symbol} ---")
tsl.exit_position(tradingsymbol=symbol, exchange='NSE', trade_type='MIS')
print(f"--- [API-RECV] exit_position sent for {symbol} ---")
del open_positions[symbol]
kill_switch_activated = True
continue
if open_positions:
print("--- [DEBUG] Entering 'Manage Open Positions' block. ---")
print("--- Managing open positions ---")
for symbol, trade_info in list(open_positions.items()):
print(f"--- [API-SEND] get_historical_data for {symbol} (managing position) ---")
hist_data_15min = tsl.get_historical_data(tradingsymbol=symbol, exchange='NSE', timeframe=TIMEFRAME_DHAN_API)
print(f"--- [API-RECV] get_historical_data for {symbol} received {len(hist_data_15min) if hist_data_15min is not None else 0} rows ---")
if hist_data_15min is None or len(hist_data_15min) < 30: continue
macd, macdsignal, _ = talib.MACD(hist_data_15min['close'], fastperiod=12, slowperiod=26, signalperiod=9)
#atr = talib.ATR(hist_data_15min['high'], hist_data_15min['low'], hist_data_15min['close'], timeperiod=ATR_PERIOD)
previous_macd = macd.iloc[-2]
latest_macd = macd.iloc[-1]
previous_signal = macdsignal.iloc[-2]
latest_signal = macdsignal.iloc[-1]
# Exit condition: MACD bearish crossover (was bullish, now bearish)
if trade_info['direction'] == 'BUY' and previous_macd > previous_signal and latest_macd < latest_signal:
print(f"EXIT (MACD Crossover): Closing BUY on {symbol} at {hist_data_15min['close'].iloc[-1]}")
# First, cancel the pending SL order to avoid double execution
if trade_info.get('sl_order_id'):
sl_order_id_to_cancel = trade_info['sl_order_id']
print(f"--- [API-SEND] cancel_order for SL ID: {sl_order_id_to_cancel} ---")
tsl.cancel_order(sl_order_id_to_cancel)
print(f"--- [API-RECV] cancel_order sent for {sl_order_id_to_cancel} ---")
# Then, exit the position with a market order
print(f"--- [API-SEND] exit_position for {symbol} ---")
tsl.exit_position(
tradingsymbol=symbol, exchange='NSE', trade_type='MIS'
)
print(f"--- [API-RECV] exit_position sent for {symbol} ---")
del open_positions[symbol]
continue
# --- TRAILING STOP-LOSS LOGIC ---
# if not atr.empty:
# latest_close = hist_data_15min['close'].iloc[-1]
# latest_atr = atr.iloc[-1]
# potential_new_sl = latest_close - (latest_atr * ATR_MULTIPLIER)
# current_sl = trade_info['sl']
# # If new SL is higher than current SL, trail it up
# if potential_new_sl > current_sl:
# new_sl_price = round_to_tick(potential_new_sl)
# print(f"TRAILING SL for {symbol}: From {current_sl:.2f} to {new_sl_price:.2f}")
# tsl.modify_order(order_id=trade_info['orderId'], trigger_price=new_sl_price)
# open_positions[symbol]['sl'] = new_sl_price # Update local SL
if available_balance <= 0:
print("Balance is zero or less. Skipping scan for new opportunities.")
time.sleep(300)
continue
print("--- [DEBUG] Entering 'Scan for New Trades' block. ---")
print(f"\nScanning for new trades at {datetime.now().strftime('%H:%M:%S')}...")
for stock_symbol in eligible_stocks:
if trades_today_count >= MAX_DAILY_TRADES:
print("Max daily trades limit reached. No more trades today.")
print("--- [DEBUG] Max daily trades reached, breaking scan loop. ---")
break
if stock_symbol in open_positions.keys():
continue
# Check for concurrent trade limit
if len(open_positions) >= MAX_CONCURRENT_TRADES:
print(f"Max concurrent trades limit of {MAX_CONCURRENT_TRADES} reached. Will not open new positions now.")
break
try:
print(f"--- [API-SEND] get_historical_data for {stock_symbol} (scanning) ---")
hist_data = tsl.get_historical_data(tradingsymbol=stock_symbol, exchange='NSE', timeframe=TIMEFRAME_DHAN_API)
print(f"--- [API-RECV] get_historical_data for {stock_symbol} received {len(hist_data) if hist_data is not None else 0} rows ---")
if hist_data is None or len(hist_data) < 30:
print(f"Limited DATA {stock_symbol}")
continue
macd, macdsignal, macdhist = talib.MACD(hist_data['close'], fastperiod=12, slowperiod=26, signalperiod=9)
atr = talib.ATR(hist_data['high'], hist_data['low'], hist_data['close'], timeperiod=ATR_PERIOD)
rsi = talib.RSI(hist_data['close'], timeperiod=14)
hist_data.ta.supertrend(length=ATR_PERIOD, multiplier=3.0, append=True)
latest_close = hist_data['close'].iloc[-1]
latest_open = hist_data['open'].iloc[-1]
previous_close = hist_data['close'].iloc[-2]
previous_high = hist_data['high'].iloc[-2]
latest_atr = atr.iloc[-1]
latest_rsi = rsi.iloc[-1]
if pd.isna(latest_atr):
continue
if pd.isna(latest_rsi):
continue
latest_macd = macd.iloc[-1]
previous_macd = macd.iloc[-2]
latest_signal = macdsignal.iloc[-1]
previous_signal = macdsignal.iloc[-2]
st_col = f'SUPERT_{ATR_PERIOD}_3.0'
latest_supertrend = hist_data[st_col].iloc[-1]
previous_supertrend = hist_data[st_col].iloc[-2]
two_ago_supertrend = hist_data[st_col].iloc[-3]
two_ago_close = hist_data['close'].iloc[-3]
# Ensure all required indicator values are not NaN before proceeding
if pd.isna(latest_supertrend) or pd.isna(previous_supertrend) or pd.isna(two_ago_supertrend):
print(f"Skipping {stock_symbol} due to NaN in Supertrend indicator values.")
continue
trade_placed = False
# Entry condition: MACD Crossover confirmed by RSI, without the high breakout.
if previous_macd < previous_signal and latest_macd > latest_signal and latest_rsi >= 56 and latest_close > latest_open:
print(f"Signal Found: {stock_symbol} | MACD: {latest_macd:.2f} | RSI: {latest_rsi:.2f} | Close: {latest_close:.2f} | Signal: (Crossover + RSI + Bullish Candle)")
trade_placed = True
if trade_placed:
last_five_lows = hist_data['low'].tail(5)
# Set SL just below the lowest low of the last 5 candles for safety
stop_loss_price = last_five_lows.min() - 0.05
risk_per_share = latest_close - stop_loss_price
if risk_per_share <= 0: continue
print(f"--- [DEBUG] Risk Calc for {stock_symbol}: Risk/Share=₹{risk_per_share:.2f} (Entry:{latest_close:.2f} - SL:{stop_loss_price:.2f})")
# Calculate quantity based on risk
quantity = int(risk_amount / risk_per_share)
if quantity == 0:
print(f"Skipping {stock_symbol}, not enough capital for 1 share.")
continue
# Check if available margin is sufficient for the trade
required_margin = quantity * latest_close
if required_margin > available_balance:
print(f"Skipping {stock_symbol} due to low margin. Required: ~₹{required_margin:.2f}, Available: ₹{available_balance:.2f}")
continue
# Prepare and print the parameters for debugging
order_params = {
'tradingsymbol': stock_symbol,
'exchange': 'NSE',
'quantity': quantity,
'price': 0,
'trigger_price': 0,
'order_type': 'MARKET',
'transaction_type': 'BUY',
'trade_type': 'MIS'
}
# Use pandas to print the parameters in a clean table format
print("DEBUG: Attempting to place order with parameters:")
print(pd.DataFrame.from_dict(order_params, orient='index', columns=['Value']).to_string())
print(f"--- [API-SEND] order_placement (BUY) for {stock_symbol} ---")
order_id = tsl.order_placement(**order_params)
print(f"--- [API-RECV] order_placement (BUY) received order ID: {order_id} ---")
if order_id:
# --- Verify Order Status ---
print(f"--- [DEBUG] Verifying execution status for order ID: {order_id} ---")
order_confirmed = False
final_order_status = "UNKNOWN"
# Retry loop to confirm trade execution (e.g., 5 attempts over 10 seconds)
for i in range(5):
time.sleep(2)
status = tsl.get_order_status(order_id)
print(f"--- [API-RECV] Verification attempt {i+1}/5 for order {order_id}. Status: {status} ---")
if status and status.lower() == 'traded':
order_confirmed = True
final_order_status = status
break
if status and status.lower() in ['rejected', 'cancelled', 'expired']:
final_order_status = status
break
if order_confirmed:
print(f"SUCCESS: BUY Order for {stock_symbol} confirmed as TRADED.")
# --- Place Live Stop-Loss Order ---
print(f"--- [DEBUG] Placing SL order for {stock_symbol} at trigger price {stop_loss_price:.2f} ---")
sl_order_params = {
'tradingsymbol': stock_symbol, 'exchange': 'NSE', 'quantity': quantity,
'order_type': 'STOP_LOSS', 'transaction_type': 'SELL', 'trade_type': 'MIS',
'price': 0, # 0 for SL-Market order
'trigger_price': stop_loss_price
}
print(f"--- [API-SEND] order_placement (SL) for {stock_symbol} with params:\n{sl_order_params} ---") # Corrected print statement
sl_order_id = tsl.order_placement(**sl_order_params)
print(f"--- [API-RECV] order_placement (SL) received order ID: {sl_order_id} ---")
if sl_order_id:
print(f"SUCCESS: Placed SL order for {stock_symbol} with ID: {sl_order_id}")
trades_today_count += 1
print(f"Trades taken today: {trades_today_count}/{MAX_DAILY_TRADES}")
open_positions[stock_symbol] = {
'qty': quantity,
'buy_order_id': order_id,
'sl_order_id': sl_order_id,
'sl_price': stop_loss_price,
'direction': 'BUY'
}
else:
print(f"CRITICAL WARNING: Failed to place SL order for {stock_symbol}. Exiting position immediately for safety.")
print(f"--- [API-SEND] exit_position for {stock_symbol} (safety exit) ---")
tsl.exit_position(tradingsymbol=stock_symbol, exchange='NSE', trade_type='MIS')
print(f"--- [API-RECV] exit_position sent for {stock_symbol} (safety exit) ---")
else:
print(f"WARNING: Order for {stock_symbol} (ID: {order_id}) was not confirmed as TRADED. Final status: {final_order_status.upper()}. Will not place SL.")
except Exception as e:
print(f"--- [DEBUG] ERROR processing stock {stock_symbol}: {e} ---")
print("--- [DEBUG] Finished scanning all stocks. ---")
print(f"Current available balance after scan: ₹{available_balance:.2f}")
# Wait intelligently for the next 15-minute candle to form
wait_for_next_candle(int(TIMEFRAME_MINUTES))
except KeyboardInterrupt:
print("--- [DEBUG] KeyboardInterrupt detected. Exiting... ---")
print("Strategy stopped by user.")
break
except Exception as e:
print(f"--- [DEBUG] An unexpected error occurred in the main loop: {e} ---")
time.sleep(60)
if __name__ == "__main__":
run_macd_strategy()