191 lines
7.8 KiB
Python
191 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
|
import asyncio
|
|
import logging
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timedelta
|
|
|
|
# Add project root to path
|
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'backend'))
|
|
|
|
from backend.database.connection import db
|
|
from backend.database.models import Trade, Account
|
|
from trading_system.binance_client import BinanceClient
|
|
|
|
# Setup logging
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
logger = logging.getLogger("fix_trades")
|
|
|
|
async def main():
|
|
# Loop through active accounts
|
|
# Based on previous check, active accounts are 2, 3, 4
|
|
active_account_ids = [2, 3, 4]
|
|
|
|
# Check columns once
|
|
existing_columns = set()
|
|
try:
|
|
cols = db.execute_query("DESCRIBE trades")
|
|
existing_columns = {row['Field'] for row in cols}
|
|
except Exception as e:
|
|
logger.error(f"Failed to describe trades table: {e}")
|
|
return
|
|
|
|
if 'realized_pnl' not in existing_columns:
|
|
logger.info("Adding 'realized_pnl' column to trades table...")
|
|
db.execute_update("ALTER TABLE trades ADD COLUMN realized_pnl DECIMAL(20, 8) NULL COMMENT '已实现盈亏'")
|
|
|
|
if 'commission' not in existing_columns:
|
|
logger.info("Adding 'commission' column to trades table...")
|
|
db.execute_update("ALTER TABLE trades ADD COLUMN commission DECIMAL(20, 8) NULL COMMENT '手续费'")
|
|
|
|
total_fixed = 0
|
|
|
|
for account_id in active_account_ids:
|
|
logger.info(f"Processing Account ID: {account_id}")
|
|
|
|
# Get account credentials
|
|
creds = Account.get_credentials(account_id)
|
|
if not creds:
|
|
logger.error(f"No account credentials found for account {account_id}")
|
|
continue
|
|
|
|
api_key, api_secret, use_testnet, status = creds
|
|
|
|
if not api_key or not api_secret:
|
|
logger.warning(f"Skipping account {account_id}: No API key/secret")
|
|
continue
|
|
|
|
if status != 'active':
|
|
logger.warning(f"Skipping account {account_id}: Status is {status}")
|
|
continue
|
|
|
|
client = BinanceClient(api_key, api_secret, testnet=use_testnet)
|
|
try:
|
|
# Check for proxy in environment
|
|
proxy = os.environ.get('HTTP_PROXY') or os.environ.get('HTTPS_PROXY')
|
|
requests_params = {'proxy': proxy} if proxy else None
|
|
|
|
await client.connect(requests_params=requests_params)
|
|
except Exception as e:
|
|
logger.error(f"Failed to connect to Binance for account {account_id}: {e}")
|
|
continue
|
|
|
|
try:
|
|
# Get recent closed trades from DB (last 30 days) for this account
|
|
thirty_days_ago = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
# Check if entry_time is int (unix timestamp) or string/datetime based on schema check
|
|
# Schema says entry_time is 'int unsigned', so it's a timestamp.
|
|
thirty_days_ago_ts = int((datetime.now() - timedelta(days=30)).timestamp())
|
|
|
|
query = """
|
|
SELECT * FROM trades
|
|
WHERE status = 'closed'
|
|
AND account_id = %s
|
|
AND entry_time > %s
|
|
ORDER BY id DESC
|
|
"""
|
|
|
|
trades = db.execute_query(query, (account_id, thirty_days_ago_ts))
|
|
logger.info(f"Found {len(trades)} closed trades for account {account_id} from last 30 days.")
|
|
|
|
updated_count = 0
|
|
|
|
for trade in trades:
|
|
symbol = trade['symbol']
|
|
trade_id = trade['id']
|
|
entry_time = trade['entry_time'] # Should be int
|
|
side = trade['side']
|
|
|
|
entry_ts_ms = entry_time * 1000
|
|
|
|
try:
|
|
# Get recent trades from Binance
|
|
recent_trades = await client.get_recent_trades(symbol, limit=50)
|
|
|
|
# Filter trades after entry time
|
|
closing_trades = [
|
|
t for t in recent_trades
|
|
if t.get('time', 0) > entry_ts_ms and float(t.get('realizedPnl', 0)) != 0
|
|
]
|
|
|
|
if not closing_trades:
|
|
continue
|
|
|
|
# Calculate actual values
|
|
total_pnl = 0.0
|
|
total_comm = 0.0
|
|
total_qty = 0.0
|
|
total_val = 0.0
|
|
|
|
for t in closing_trades:
|
|
pnl_val = float(t.get('realizedPnl', 0))
|
|
comm_val = float(t.get('commission', 0))
|
|
qty_val = float(t.get('qty', 0))
|
|
price_val = float(t.get('price', 0))
|
|
|
|
total_pnl += pnl_val
|
|
total_comm += comm_val
|
|
total_qty += qty_val
|
|
total_val += qty_val * price_val
|
|
|
|
if total_qty == 0:
|
|
continue
|
|
|
|
avg_exit_price = total_val / total_qty
|
|
|
|
# Check if values differ significantly from DB
|
|
db_pnl = float(trade.get('pnl') or 0)
|
|
db_exit_price = float(trade.get('exit_price') or 0)
|
|
|
|
needs_update = False
|
|
if abs(db_pnl - total_pnl) > 0.01:
|
|
needs_update = True
|
|
|
|
if 'realized_pnl' not in trade or trade.get('realized_pnl') is None:
|
|
needs_update = True
|
|
|
|
if needs_update:
|
|
logger.info(f"Fixing trade {trade_id} ({symbol}): PnL {db_pnl:.4f} -> {total_pnl:.4f}, ExitPrice {db_exit_price:.4f} -> {avg_exit_price:.4f}")
|
|
|
|
# Recalculate pnl_percent based on entry price
|
|
entry_price = float(trade.get('entry_price', 1))
|
|
if entry_price == 0:
|
|
entry_price = 1
|
|
|
|
if side == 'BUY':
|
|
pnl_percent = ((avg_exit_price - entry_price) / entry_price) * 100
|
|
else:
|
|
pnl_percent = ((entry_price - avg_exit_price) / entry_price) * 100
|
|
|
|
# Update DB
|
|
update_sql = """
|
|
UPDATE trades
|
|
SET pnl = %s,
|
|
pnl_percent = %s,
|
|
exit_price = %s,
|
|
realized_pnl = %s,
|
|
commission = %s
|
|
WHERE id = %s
|
|
"""
|
|
db.execute_update(update_sql, (total_pnl, pnl_percent, avg_exit_price, total_pnl, total_comm, trade_id))
|
|
updated_count += 1
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing trade {trade_id} ({symbol}): {e}")
|
|
|
|
logger.info(f"Account {account_id}: Fixed {updated_count} trades.")
|
|
total_fixed += updated_count
|
|
|
|
if client.client:
|
|
await client.client.close_connection()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing account {account_id}: {e}")
|
|
|
|
logger.info(f"Total fixed trades: {total_fixed}")
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|