Add /close command and auto-flip logic with order cleanup
- Added /close Telegram command for full position closure - Updated /reduce to accept 10-100% (was 10-90%) - Implemented auto-flip logic: automatically closes opposite position when signal reverses - Fixed risk check to allow opposite direction trades (signal flips) - Enhanced Position Manager to cancel orders when removing trades - Added startup initialization for Position Manager (restores trades on restart) - Fixed analytics to show stopped-out trades (manual DB update for orphaned trade) - Updated reduce endpoint to route 100% closes through closePosition for proper cleanup - All position closures now guarantee TP/SL order cancellation on Drift
This commit is contained in:
@@ -232,8 +232,8 @@ async def reduce_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
if context.args and len(context.args) > 0:
|
||||
try:
|
||||
reduce_percent = int(context.args[0])
|
||||
if reduce_percent < 10 or reduce_percent > 90:
|
||||
await update.message.reply_text("❌ Reduce percent must be between 10 and 90")
|
||||
if reduce_percent < 10 or reduce_percent > 100:
|
||||
await update.message.reply_text("❌ Reduce percent must be between 10 and 100")
|
||||
return
|
||||
except ValueError:
|
||||
await update.message.reply_text("❌ Invalid reduce percent. Usage: /reduce [percent]")
|
||||
@@ -284,8 +284,91 @@ async def reduce_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
print(f"❌ Error: {e}", flush=True)
|
||||
await update.message.reply_text(f"❌ Error: {str(e)}")
|
||||
|
||||
async def close_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Handle /close command - close entire position and cancel all orders"""
|
||||
|
||||
# Only process from YOUR chat
|
||||
if update.message.chat_id != ALLOWED_CHAT_ID:
|
||||
await update.message.reply_text("❌ Unauthorized")
|
||||
return
|
||||
|
||||
print(f"🔴 /close command received", flush=True)
|
||||
|
||||
try:
|
||||
# First, get the current open position
|
||||
pos_response = requests.get(
|
||||
f"{TRADING_BOT_URL}/api/trading/positions",
|
||||
headers={'Authorization': f'Bearer {API_SECRET_KEY}'},
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if not pos_response.ok:
|
||||
await update.message.reply_text(f"❌ Error fetching positions: {pos_response.status_code}")
|
||||
return
|
||||
|
||||
pos_data = pos_response.json()
|
||||
positions = pos_data.get('positions', [])
|
||||
|
||||
if not positions:
|
||||
await update.message.reply_text("❌ No open positions to close")
|
||||
return
|
||||
|
||||
if len(positions) > 1:
|
||||
await update.message.reply_text("❌ Multiple positions open. Specify symbol or use /reduce")
|
||||
return
|
||||
|
||||
position = positions[0]
|
||||
symbol = position['symbol']
|
||||
direction = position['direction'].upper()
|
||||
entry = position['entryPrice']
|
||||
size = position['currentSize']
|
||||
|
||||
# Close position at market (100%)
|
||||
response = requests.post(
|
||||
f"{TRADING_BOT_URL}/api/trading/close",
|
||||
headers={'Authorization': f'Bearer {API_SECRET_KEY}'},
|
||||
json={'symbol': symbol, 'percentToClose': 100},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
print(f"📥 API Response: {response.status_code}", flush=True)
|
||||
|
||||
if not response.ok:
|
||||
data = response.json()
|
||||
await update.message.reply_text(f"❌ Error: {data.get('message', 'Unknown error')}")
|
||||
return
|
||||
|
||||
data = response.json()
|
||||
|
||||
if not data.get('success'):
|
||||
await update.message.reply_text(f"❌ {data.get('message', 'Failed to close position')}")
|
||||
return
|
||||
|
||||
# Build success message
|
||||
close_price = data.get('closePrice', 0)
|
||||
realized_pnl = data.get('realizedPnL', 0)
|
||||
|
||||
emoji = "💚" if realized_pnl > 0 else "❤️" if realized_pnl < 0 else "💛"
|
||||
|
||||
message = f"{emoji} *Position Closed*\n\n"
|
||||
message += f"*{symbol} {direction}*\n\n"
|
||||
message += f"*Entry:* ${entry:.4f}\n"
|
||||
message += f"*Exit:* ${close_price:.4f}\n"
|
||||
message += f"*Size:* ${size:.2f}\n\n"
|
||||
message += f"*P&L:* ${realized_pnl:.2f}\n\n"
|
||||
message += f"✅ Position closed at market\n"
|
||||
message += f"✅ All TP/SL orders cancelled"
|
||||
|
||||
await update.message.reply_text(message, parse_mode='Markdown')
|
||||
|
||||
print(f"✅ Position closed: {symbol} | P&L: ${realized_pnl:.2f}", flush=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}", flush=True)
|
||||
await update.message.reply_text(f"❌ Error: {str(e)}")
|
||||
|
||||
async def validate_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Handle /validate command - check if positions match settings"""
|
||||
"""Handle /validate command - check position consistency"""
|
||||
|
||||
# Only process from YOUR chat
|
||||
if update.message.chat_id != ALLOWED_CHAT_ID:
|
||||
@@ -434,6 +517,7 @@ def main():
|
||||
|
||||
# Add command handlers
|
||||
application.add_handler(CommandHandler("status", status_command))
|
||||
application.add_handler(CommandHandler("close", close_command))
|
||||
application.add_handler(CommandHandler("validate", validate_command))
|
||||
application.add_handler(CommandHandler("scale", scale_command))
|
||||
application.add_handler(CommandHandler("reduce", reduce_command))
|
||||
|
||||
Reference in New Issue
Block a user