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:
mindesbunister
2025-10-27 23:27:48 +01:00
parent a07bf9f4b2
commit 9bf83260c4
7 changed files with 264 additions and 19 deletions

View File

@@ -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))