auto_trade_sys/update_stop_loss_orders.py
薇薇安 3d350ebea6 1
2026-02-14 11:36:55 +08:00

216 lines
8.3 KiB
Python

#!/usr/bin/env python3
import asyncio
import logging
import sys
import os
from pathlib import Path
from typing import List, Dict, Optional
# Add project root to sys.path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
sys.path.insert(0, str(project_root / 'trading_system'))
sys.path.insert(0, str(project_root / 'backend'))
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("UpdateSL")
# Import system modules
try:
from trading_system.binance_client import BinanceClient
from trading_system.risk_manager import RiskManager
from trading_system import config
from backend.database.connection import Database
from backend.security.crypto import decrypt_str
# Ensure config is loaded (defaults updated to 0.5)
from trading_system.config import _init_config_manager
_init_config_manager()
except ImportError as e:
logger.error(f"Import failed: {e}")
sys.exit(1)
async def process_account(account_id, name, api_key, api_secret):
logger.info(f"=== Processing Account {account_id}: {name} ===")
if not api_key or not api_secret:
logger.warning(f"Account {name} missing keys. Skipping.")
return
# Initialize Client with explicit keys
client = BinanceClient(api_key=api_key, api_secret=api_secret)
# Connect to Binance
try:
# Debug proxy settings
logger.info(f"Proxy Settings - HTTP: {os.environ.get('HTTP_PROXY')}, HTTPS: {os.environ.get('HTTPS_PROXY')}")
# Increase timeout to 60s and retries
await client.connect(timeout=60, retries=5)
except Exception as e:
logger.error(f"Failed to connect to Binance for account {name}: {e}")
return
# Initialize RiskManager
# Note: RiskManager uses global config for ATR settings.
# Since we updated config.py default ATR_STOP_LOSS_MULTIPLIER to 0.5,
# and account 1 DB config is also 0.5, this is safe.
risk_manager = RiskManager(client)
try:
atr_multiplier = float(config.TRADING_CONFIG.get('ATR_STOP_LOSS_MULTIPLIER', 0.5))
logger.info(f"Target ATR Multiplier: {atr_multiplier}")
# Get Open Positions
try:
positions = await client.get_open_positions()
logger.info(f"Found {len(positions)} open positions.")
except Exception as e:
logger.error(f"Failed to get positions: {e}")
return
for position in positions:
symbol = position['symbol']
amt = float(position['positionAmt'])
if amt == 0:
continue
# Determine side from amount
if amt > 0:
real_side = 'BUY'
else:
real_side = 'SELL'
entry_price = float(position['entryPrice'])
mark_price = float(position['markPrice'])
leverage = int(position['leverage'])
quantity = abs(amt)
logger.info(f"Processing {symbol} ({real_side}): Entry={entry_price}, Mark={mark_price}, Amt={amt}")
# Fetch Klines for ATR
try:
# Get 1h klines for ATR calculation
klines = await client.get_klines(symbol, '1h', limit=100)
if not klines:
logger.warning(f"{symbol} - Failed to get klines, skipping.")
continue
new_stop_loss = risk_manager.get_stop_loss_price(
entry_price=entry_price,
side=real_side,
quantity=quantity,
leverage=leverage,
stop_loss_pct=None, # Force ATR usage
klines=klines
)
if not new_stop_loss:
logger.warning(f"{symbol} - Could not calculate new stop loss.")
continue
logger.info(f"{symbol} - New ATR Stop Loss Price: {new_stop_loss}")
# Check if triggered
triggered = False
if real_side == 'BUY':
if mark_price <= new_stop_loss:
triggered = True
else: # SELL
if mark_price >= new_stop_loss:
triggered = True
if triggered:
logger.warning(f"{symbol} - Price already beyond new stop loss! Executing MARKET CLOSE.")
try:
await client.place_order(
symbol=symbol,
side='SELL' if real_side == 'BUY' else 'BUY',
type='MARKET',
quantity=quantity,
reduceOnly=True
)
logger.info(f"{symbol} - Market Close Order Placed.")
await client.cancel_all_orders(symbol)
logger.info(f"{symbol} - Cancelled all open orders.")
except Exception as e:
logger.error(f"{symbol} - Failed to close position: {e}")
else:
# Update Stop Loss Order
logger.info(f"{symbol} - Updating Stop Loss Order to {new_stop_loss}")
# Cancel existing STOP_MARKET orders
try:
open_orders = await client.get_open_orders(symbol)
for order in open_orders:
if order['type'] == 'STOP_MARKET':
await client.cancel_order(symbol, order['orderId'])
logger.info(f"{symbol} - Cancelled old STOP_MARKET order {order['orderId']}")
except Exception as e:
logger.error(f"{symbol} - Failed to cancel orders: {e}")
# Place new STOP_MARKET order
try:
stop_side = 'SELL' if real_side == 'BUY' else 'BUY'
await client.place_order(
symbol=symbol,
side=stop_side,
type='STOP_MARKET',
stopPrice=new_stop_loss,
quantity=quantity,
reduceOnly=True,
workingType='MARK_PRICE'
)
logger.info(f"{symbol} - Placed new STOP_MARKET order at {new_stop_loss}")
except Exception as e:
logger.error(f"{symbol} - Failed to place new stop loss: {e}")
except Exception as e:
logger.error(f"Error processing {symbol}: {e}")
import traceback
traceback.print_exc()
finally:
await client.close()
async def main():
logger.info("Starting Multi-Account Stop Loss Update Script...")
db = Database()
with db.get_connection() as conn:
with conn.cursor() as cursor:
# Get active accounts
cursor.execute("SELECT id, name, api_key_enc, api_secret_enc FROM accounts WHERE status='active'")
accounts = cursor.fetchall()
logger.info(f"Found {len(accounts)} active accounts.")
for acc in accounts:
# Assuming fetchall returns dicts because Database uses DictCursor usually,
# or we check type. backend/database/connection.py ensures DictCursor if possible?
# Let's handle both dict and tuple/list just in case, but based on previous tools, it returns dicts.
# But wait, previous `check_atr_db` output showed dicts.
acc_id = acc['id']
name = acc['name']
api_key_enc = acc['api_key_enc']
api_secret_enc = acc['api_secret_enc']
api_key = decrypt_str(api_key_enc)
api_secret = decrypt_str(api_secret_enc)
await process_account(acc_id, name, api_key, api_secret)
logger.info("All accounts processed.")
if __name__ == "__main__":
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(main())