This commit is contained in:
薇薇安 2026-02-05 19:38:18 +08:00
parent 9be1c5777d
commit a38f5ff05d
5 changed files with 392 additions and 5 deletions

93
check_account_status.py Normal file
View File

@ -0,0 +1,93 @@
import asyncio
import sys
import os
from datetime import datetime
# Add path
sys.path.insert(0, '/Users/vivian/work/python/auto_trade_sys')
sys.path.insert(0, '/Users/vivian/work/python/auto_trade_sys/backend')
from trading_system.binance_client import BinanceClient
from config_manager import ConfigManager
async def check_account(account_id):
print(f"\n{'='*20} Checking Account {account_id} {'='*20}")
try:
# Initialize ConfigManager for this account
config_manager = ConfigManager(account_id=account_id)
api_key = config_manager.get('BINANCE_API_KEY')
api_secret = config_manager.get('BINANCE_API_SECRET')
if not api_key or not api_secret:
print(f"Skipping Account {account_id}: Missing API keys")
return
# Initialize client with specific keys
client = BinanceClient(api_key=api_key, api_secret=api_secret)
# Connect
await client.connect()
# Get account information
# Access the underlying AsyncClient
if not client.client:
print(f"Failed to connect to Binance for Account {account_id}")
return
# Check positions
print("=== Current Positions ===")
positions = await client.client.futures_position_information()
has_positions = False
for pos in positions:
symbol = pos['symbol']
amt = float(pos['positionAmt'])
if amt != 0:
has_positions = True
entry_price = float(pos['entryPrice'])
mark_price = float(pos['markPrice'])
unrealized_profit = float(pos['unRealizedProfit'])
# Calculate percentage
if entry_price > 0:
if amt > 0:
pnl_pct = (mark_price - entry_price) / entry_price * 100
else:
pnl_pct = (entry_price - mark_price) / entry_price * 100
else:
pnl_pct = 0
print(f"Symbol: {symbol}")
print(f" Amount: {amt}")
print(f" Entry Price: {entry_price}")
print(f" Mark Price: {mark_price}")
print(f" Unrealized PnL: {unrealized_profit:.2f} USDT ({pnl_pct:.2f}%)")
print("-" * 30)
if not has_positions:
print("No open positions.")
# Check open orders
print("\n=== Open Orders (for active positions) ===")
if has_positions:
for pos in positions:
if float(pos['positionAmt']) != 0:
symbol = pos['symbol']
orders = await client.get_open_orders(symbol)
if orders:
print(f"Orders for {symbol}:")
for order in orders:
print(f" Type: {order['type']}, Side: {order['side']}, Price: {order['price']}, StopPrice: {order.get('stopPrice', 'N/A')}")
else:
print(f"No open orders for {symbol}")
await client.close()
except Exception as e:
print(f"Error checking Account {account_id}: {e}")
async def main():
for account_id in [1, 2, 3, 4]:
await check_account(account_id)
if __name__ == "__main__":
asyncio.run(main())

78
check_trades.py Normal file
View File

@ -0,0 +1,78 @@
import pymysql
import os
from datetime import datetime
# DB Config
DB_HOST = 'localhost'
DB_USER = 'root'
DB_PASSWORD = '12345678'
DB_NAME = 'auto_trade_sys_new'
def connect_db():
return pymysql.connect(
host=DB_HOST,
user=DB_USER,
password=DB_PASSWORD,
database=DB_NAME,
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
def main():
try:
conn = connect_db()
cursor = conn.cursor()
print(f"Connected to {DB_NAME}")
# Check all databases
print("\n=== All Databases ===")
cursor.execute("SHOW DATABASES")
for db in cursor.fetchall():
print(db['Database'])
# 1. Check Open Positions for Account 2
print("\n=== Open Positions (Account 2) ===")
cursor.execute("""
SELECT id, symbol, side, entry_price, quantity, leverage, stop_loss_price, take_profit_price, created_at
FROM trades
WHERE status = 'open' AND account_id = 2
ORDER BY created_at DESC
""")
open_trades = cursor.fetchall()
if not open_trades:
print("No open positions found for Account 2.")
else:
for trade in open_trades:
entry_time = datetime.fromtimestamp(trade['created_at']).strftime('%Y-%m-%d %H:%M:%S')
print(f"ID: {trade['id']}, Symbol: {trade['symbol']}, Side: {trade['side']}, Entry: {trade['entry_price']}, SL: {trade['stop_loss_price']}, TP: {trade['take_profit_price']}, Time: {entry_time}")
# 2. Check Recent Closed Trades for Account 2
cursor.execute(f"SELECT id, symbol, side, status, pnl, pnl_percent, stop_loss_price, take_profit_price, entry_time, quantity, entry_price FROM trades WHERE account_id = 2 ORDER BY id DESC LIMIT 10")
trades = cursor.fetchall()
print(f"Account 2 recent trades:")
for t in trades:
# Handle entry_time conversion if it's a timestamp or datetime
entry_time = t.get('entry_time')
print(f"ID: {t['id']}, Symbol: {t['symbol']}, Side: {t['side']}, Status: {t['status']}, PnL: {t['pnl']}, PnL%: {t['pnl_percent']}, SL: {t['stop_loss_price']}, TP: {t['take_profit_price']}, Time: {entry_time}, Qty: {t['quantity']}, Price: {t['entry_price']}")
# 3. Check for the specific "All take profits" claim
print("\n=== Checking for Negative PnL with Take Profit Reason ===")
cursor.execute("""
SELECT COUNT(*) as count
FROM trades
WHERE status != 'open'
AND exit_reason = 'take_profit'
AND pnl < 0
""")
count = cursor.fetchone()['count']
print(f"Number of 'take_profit' trades with negative PnL: {count}")
conn.close()
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()

61
debug_sltp.py Normal file
View File

@ -0,0 +1,61 @@
import asyncio
import logging
import sys
from pathlib import Path
# Setup 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'))
# Mock config
from trading_system import config
config.TRADING_CONFIG = {
'STOP_LOSS_PERCENT': 0.03,
'TAKE_PROFIT_PERCENT': 0.05,
'ATR_STOP_LOSS_MULTIPLIER': 2.5,
'USE_ATR_STOP_LOSS': True
}
from trading_system.risk_manager import RiskManager
from trading_system.position_manager import PositionManager
# Mock classes
class MockClient:
pass
async def test_risk_manager():
print("Testing RiskManager...")
rm = RiskManager(None)
entry_price = 100.0
side = 'BUY'
quantity = 1.0
leverage = 10
stop_loss_pct = 0.03
# Case 1: No ATR, No Klines
sl = rm.get_stop_loss_price(entry_price, side, quantity, leverage, stop_loss_pct=stop_loss_pct)
print(f"Case 1 (Basic): SL = {sl}")
# Case 2: With ATR
atr = 2.0
sl_atr = rm.get_stop_loss_price(entry_price, side, quantity, leverage, stop_loss_pct=stop_loss_pct, atr=atr)
print(f"Case 2 (ATR={atr}): SL = {sl_atr}")
# Case 3: SELL side
side = 'SELL'
sl_sell = rm.get_stop_loss_price(entry_price, side, quantity, leverage, stop_loss_pct=stop_loss_pct)
print(f"Case 3 (SELL): SL = {sl_sell}")
# Case 4: Zero/None input
sl_none = rm.get_stop_loss_price(entry_price, side, quantity, leverage, stop_loss_pct=None)
print(f"Case 4 (None pct): SL = {sl_none}")
sl_zero = rm.get_stop_loss_price(entry_price, side, quantity, leverage, stop_loss_pct=0)
print(f"Case 4 (Zero pct): SL = {sl_zero}")
if __name__ == "__main__":
asyncio.run(test_risk_manager())

131
fix_db_positions.py Normal file
View File

@ -0,0 +1,131 @@
import asyncio
import sys
import os
from pathlib import Path
# Add project root to path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
sys.path.insert(0, str(project_root / 'backend'))
from database.connection import db
from database.models import Trade
from trading_system import config
from trading_system.risk_manager import RiskManager
async def fix_missing_sltp():
print("Checking for open trades with missing SL/TP across all accounts...")
all_open_trades = []
# Iterate through potential account IDs
for account_id in [1, 2, 3, 4]:
print(f"\nChecking Account {account_id}...")
try:
# Check if Trade.get_all supports account_id argument
trades = Trade.get_all(status='open', account_id=account_id)
except TypeError:
print(f"Warning: Trade.get_all might not support account_id. Checking default.")
trades = Trade.get_all(status='open')
if account_id > 1: break
if trades:
print(f"Found {len(trades)} open trades for Account {account_id}.")
all_open_trades.extend(trades)
else:
print(f"No open trades found for Account {account_id}.")
open_trades = all_open_trades
if not open_trades:
print("No open trades found.")
return
print(f"Found {len(open_trades)} open trades total.")
# Initialize RiskManager (lightweight)
# We don't need a real client if we just use basic calculation
rm = RiskManager(None)
updates = 0
for trade in open_trades:
t_id = trade['id']
symbol = trade['symbol']
entry_price = float(trade['entry_price'] or 0)
quantity = float(trade['quantity'] or 0)
side = trade['side']
leverage = int(trade['leverage'] or 10)
sl = trade.get('stop_loss_price')
tp = trade.get('take_profit_price')
if entry_price <= 0:
print(f"Skipping trade {t_id} ({symbol}): Invalid entry price {entry_price}")
continue
needs_update = False
# Calculate defaults
# Config defaults: SL 3%, TP 10% (or 30%?)
# Logic from position_manager:
stop_loss_pct = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.03)
if stop_loss_pct > 1: stop_loss_pct /= 100.0
take_profit_pct = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.10)
if take_profit_pct > 1: take_profit_pct /= 100.0
# Calculate SL
if sl is None or float(sl) <= 0:
print(f"Trade {t_id} ({symbol}) missing SL. Calculating...")
# Use RiskManager logic manually or call it
# We'll use the basic margin-based logic
position_value = entry_price * quantity
margin = position_value / leverage if leverage > 0 else position_value
stop_loss_amount = margin * stop_loss_pct
if side == 'BUY':
new_sl = entry_price - (stop_loss_amount / quantity)
else:
new_sl = entry_price + (stop_loss_amount / quantity)
sl = new_sl
needs_update = True
print(f" -> Calculated SL: {sl:.4f} (Margin: {margin:.2f}, SL Amount: {stop_loss_amount:.2f})")
# Calculate TP
if tp is None or float(tp) <= 0:
print(f"Trade {t_id} ({symbol}) missing TP. Calculating...")
# Use basic logic
# Default TP is usually SL distance * 3 or fixed pct
# Position manager uses config TAKE_PROFIT_PERCENT
if take_profit_pct is None or take_profit_pct == 0:
take_profit_pct = stop_loss_pct * 3.0
tp_price = rm.get_take_profit_price(
entry_price, side, quantity, leverage,
take_profit_pct=take_profit_pct,
atr=None,
stop_distance=abs(entry_price - float(sl))
)
tp = tp_price
needs_update = True
print(f" -> Calculated TP: {tp:.4f}")
if needs_update:
try:
db.execute_update(
"UPDATE trades SET stop_loss_price = %s, take_profit_price = %s WHERE id = %s",
(sl, tp, t_id)
)
print(f"✓ Updated trade {t_id} ({symbol})")
updates += 1
except Exception as e:
print(f"❌ Failed to update trade {t_id}: {e}")
else:
print(f"Trade {t_id} ({symbol}) OK.")
print(f"Fixed {updates} trades.")
if __name__ == "__main__":
asyncio.run(fix_missing_sltp())

View File

@ -57,16 +57,29 @@ if get_beijing_time is None:
class PositionManager:
"""仓位管理类"""
def __init__(self, client: BinanceClient, risk_manager: RiskManager):
def __init__(self, client: BinanceClient, risk_manager: RiskManager, account_id: int = None):
"""
初始化仓位管理器
Args:
client: 币安客户端
risk_manager: 风险管理器
account_id: 账户ID默认从环境变量或配置获取
"""
self.client = client
self.risk_manager = risk_manager
# 确定 account_id
if account_id is not None:
self.account_id = int(account_id)
else:
# 尝试从环境变量获取
import os
try:
self.account_id = int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1)
except:
self.account_id = 1
self.active_positions: Dict[str, Dict] = {}
self._monitor_tasks: Dict[str, asyncio.Task] = {} # WebSocket监控任务字典
self._monitoring_enabled = True # 是否启用实时监控
@ -632,6 +645,7 @@ class PositionManager:
notional_usdt=notional_usdt,
margin_usdt=margin_usdt,
entry_context=entry_context, # 入场思路与过程(便于事后分析策略执行效果)
account_id=self.account_id
)
logger.info(f"{symbol} 交易记录已保存到数据库 (ID: {trade_id}, 订单号: {entry_order_id}, 成交价: {entry_price:.4f}, 成交数量: {quantity:.4f})")
except Exception as e:
@ -668,7 +682,8 @@ class PositionManager:
'strategyType': 'trend_following', # 策略类型(简化后只有趋势跟踪)
'atr': atr,
'maxProfit': 0.0, # 记录最大盈利(用于移动止损)
'trailingStopActivated': False # 移动止损是否已激活
'trailingStopActivated': False, # 移动止损是否已激活
'account_id': self.account_id
}
self.active_positions[symbol] = position_info
@ -2018,8 +2033,8 @@ class PositionManager:
binance_symbols = {p['symbol'] for p in binance_positions}
logger.debug(f"币安实际持仓: {len(binance_symbols)} 个 ({', '.join(binance_symbols) if binance_symbols else ''})")
# 2. 获取数据库中状态为open的交易记录
db_open_trades = Trade.get_all(status='open')
# 2. 获取数据库中状态为open的交易记录(仅当前账号)
db_open_trades = Trade.get_all(status='open', account_id=self.account_id)
db_open_symbols = {t['symbol'] for t in db_open_trades}
logger.debug(f"数据库open状态: {len(db_open_symbols)} 个 ({', '.join(db_open_symbols) if db_open_symbols else ''})")
@ -2035,7 +2050,7 @@ class PositionManager:
# 4. 更新这些持仓的状态
for symbol in missing_in_binance:
try:
trades = Trade.get_by_symbol(symbol, status='open')
trades = Trade.get_by_symbol(symbol, status='open', account_id=self.account_id)
if not trades:
logger.warning(f"{symbol} [状态同步] ⚠️ 数据库中没有找到open状态的交易记录跳过")
continue
@ -2370,6 +2385,15 @@ class PositionManager:
exit_reason = "stop_loss"
logger.info(f"{trade.get('symbol')} [同步] 特征判断:持仓{duration_minutes:.1f}分钟,亏损{pnl_percent_for_judge:.2f}% of margin价格在止损方向标记为止损")
# ⚠️ 2026-02-05 修复:防止亏损单被错误标记为止盈
if pnl_percent_for_judge < 0 and exit_reason == "take_profit":
logger.warning(f"{symbol} [状态同步] ⚠️ 亏损单({pnl_percent_for_judge:.2f}%)被标记为止盈强制修正为manual/stop_loss")
# 如果亏损较大,归为止损
if pnl_percent_for_judge < -5.0:
exit_reason = "stop_loss"
else:
exit_reason = "manual"
# 4. 如果之前标记为 sync 且是 reduceOnly 订单,但价格不匹配止损/止盈,可能是其他自动平仓(如移动止损)
if exit_reason == "sync" and is_reduce_only:
# 检查是否是移动止损:如果价格接近入场价,可能是移动止损触发的