1
This commit is contained in:
parent
9be1c5777d
commit
a38f5ff05d
93
check_account_status.py
Normal file
93
check_account_status.py
Normal 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
78
check_trades.py
Normal 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
61
debug_sltp.py
Normal 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
131
fix_db_positions.py
Normal 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())
|
||||
|
|
@ -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:
|
||||
# 检查是否是移动止损:如果价格接近入场价,可能是移动止损触发的
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user