BIG CLEANUP
This commit is contained in:
@@ -1,164 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify that both model prediction and trading statistics issues are fixed
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from core.orchestrator import TradingOrchestrator
|
||||
from core.data_provider import DataProvider
|
||||
from core.trading_executor import TradingExecutor
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def test_model_predictions():
|
||||
"""Test that model predictions are working correctly"""
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("TESTING MODEL PREDICTIONS")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# Initialize components
|
||||
data_provider = DataProvider()
|
||||
orchestrator = TradingOrchestrator(data_provider)
|
||||
|
||||
# Check model registration
|
||||
logger.info("1. Checking model registration...")
|
||||
models = orchestrator.model_registry.get_all_models()
|
||||
logger.info(f" Registered models: {list(models.keys()) if models else 'None'}")
|
||||
|
||||
# Test making a decision
|
||||
logger.info("2. Testing trading decision generation...")
|
||||
decision = await orchestrator.make_trading_decision('ETH/USDT')
|
||||
|
||||
if decision:
|
||||
logger.info(f" ✅ Decision generated: {decision.action} (confidence: {decision.confidence:.3f})")
|
||||
logger.info(f" ✅ Reasoning: {decision.reasoning}")
|
||||
return True
|
||||
else:
|
||||
logger.error(" ❌ No decision generated")
|
||||
return False
|
||||
|
||||
def test_trading_statistics():
|
||||
"""Test that trading statistics calculations are working correctly"""
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("TESTING TRADING STATISTICS")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# Initialize trading executor
|
||||
trading_executor = TradingExecutor()
|
||||
|
||||
# Check if we have any trades
|
||||
trade_history = trading_executor.get_trade_history()
|
||||
logger.info(f"1. Current trade history: {len(trade_history)} trades")
|
||||
|
||||
# Get daily stats
|
||||
daily_stats = trading_executor.get_daily_stats()
|
||||
logger.info("2. Daily statistics from trading executor:")
|
||||
logger.info(f" Total trades: {daily_stats.get('total_trades', 0)}")
|
||||
logger.info(f" Winning trades: {daily_stats.get('winning_trades', 0)}")
|
||||
logger.info(f" Losing trades: {daily_stats.get('losing_trades', 0)}")
|
||||
logger.info(f" Win rate: {daily_stats.get('win_rate', 0.0) * 100:.1f}%")
|
||||
logger.info(f" Avg winning trade: ${daily_stats.get('avg_winning_trade', 0.0):.2f}")
|
||||
logger.info(f" Avg losing trade: ${daily_stats.get('avg_losing_trade', 0.0):.2f}")
|
||||
logger.info(f" Total P&L: ${daily_stats.get('total_pnl', 0.0):.2f}")
|
||||
|
||||
# Simulate some trades if we don't have any
|
||||
if daily_stats.get('total_trades', 0) == 0:
|
||||
logger.info("3. No trades found - simulating some test trades...")
|
||||
|
||||
# Add some mock trades to the trade history
|
||||
from core.trading_executor import TradeRecord
|
||||
from datetime import datetime
|
||||
|
||||
# Add a winning trade
|
||||
winning_trade = TradeRecord(
|
||||
symbol='ETH/USDT',
|
||||
side='LONG',
|
||||
quantity=0.01,
|
||||
entry_price=2500.0,
|
||||
exit_price=2550.0,
|
||||
entry_time=datetime.now(),
|
||||
exit_time=datetime.now(),
|
||||
pnl=0.50, # $0.50 profit
|
||||
fees=0.01,
|
||||
confidence=0.8
|
||||
)
|
||||
trading_executor.trade_history.append(winning_trade)
|
||||
|
||||
# Add a losing trade
|
||||
losing_trade = TradeRecord(
|
||||
symbol='ETH/USDT',
|
||||
side='LONG',
|
||||
quantity=0.01,
|
||||
entry_price=2500.0,
|
||||
exit_price=2480.0,
|
||||
entry_time=datetime.now(),
|
||||
exit_time=datetime.now(),
|
||||
pnl=-0.20, # $0.20 loss
|
||||
fees=0.01,
|
||||
confidence=0.7
|
||||
)
|
||||
trading_executor.trade_history.append(losing_trade)
|
||||
|
||||
# Get updated stats
|
||||
daily_stats = trading_executor.get_daily_stats()
|
||||
logger.info(" Updated statistics after adding test trades:")
|
||||
logger.info(f" Total trades: {daily_stats.get('total_trades', 0)}")
|
||||
logger.info(f" Winning trades: {daily_stats.get('winning_trades', 0)}")
|
||||
logger.info(f" Losing trades: {daily_stats.get('losing_trades', 0)}")
|
||||
logger.info(f" Win rate: {daily_stats.get('win_rate', 0.0) * 100:.1f}%")
|
||||
logger.info(f" Avg winning trade: ${daily_stats.get('avg_winning_trade', 0.0):.2f}")
|
||||
logger.info(f" Avg losing trade: ${daily_stats.get('avg_losing_trade', 0.0):.2f}")
|
||||
logger.info(f" Total P&L: ${daily_stats.get('total_pnl', 0.0):.2f}")
|
||||
|
||||
# Verify calculations
|
||||
expected_win_rate = 1/2 # 1 win out of 2 trades = 50%
|
||||
expected_avg_win = 0.50
|
||||
expected_avg_loss = -0.20
|
||||
|
||||
actual_win_rate = daily_stats.get('win_rate', 0.0)
|
||||
actual_avg_win = daily_stats.get('avg_winning_trade', 0.0)
|
||||
actual_avg_loss = daily_stats.get('avg_losing_trade', 0.0)
|
||||
|
||||
logger.info("4. Verifying calculations:")
|
||||
logger.info(f" Win rate: Expected {expected_win_rate*100:.1f}%, Got {actual_win_rate*100:.1f}% ✅" if abs(actual_win_rate - expected_win_rate) < 0.01 else f" Win rate: Expected {expected_win_rate*100:.1f}%, Got {actual_win_rate*100:.1f}% ❌")
|
||||
logger.info(f" Avg win: Expected ${expected_avg_win:.2f}, Got ${actual_avg_win:.2f} ✅" if abs(actual_avg_win - expected_avg_win) < 0.01 else f" Avg win: Expected ${expected_avg_win:.2f}, Got ${actual_avg_win:.2f} ❌")
|
||||
logger.info(f" Avg loss: Expected ${expected_avg_loss:.2f}, Got ${actual_avg_loss:.2f} ✅" if abs(actual_avg_loss - expected_avg_loss) < 0.01 else f" Avg loss: Expected ${expected_avg_loss:.2f}, Got ${actual_avg_loss:.2f} ❌")
|
||||
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
async def main():
|
||||
"""Run all tests"""
|
||||
|
||||
logger.info("🚀 STARTING COMPREHENSIVE FIXES TEST")
|
||||
logger.info("Testing both model prediction fixes and trading statistics fixes")
|
||||
|
||||
# Test model predictions
|
||||
prediction_success = await test_model_predictions()
|
||||
|
||||
# Test trading statistics
|
||||
stats_success = test_trading_statistics()
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("TEST SUMMARY")
|
||||
logger.info("=" * 60)
|
||||
logger.info(f"Model Predictions: {'✅ FIXED' if prediction_success else '❌ STILL BROKEN'}")
|
||||
logger.info(f"Trading Statistics: {'✅ FIXED' if stats_success else '❌ STILL BROKEN'}")
|
||||
|
||||
if prediction_success and stats_success:
|
||||
logger.info("🎉 ALL ISSUES FIXED! The system should now work correctly.")
|
||||
else:
|
||||
logger.error("❌ Some issues remain. Check the logs above for details.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,250 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify trading fixes:
|
||||
1. Position sizes with leverage
|
||||
2. ETH-only trading
|
||||
3. Correct win rate calculations
|
||||
4. Meaningful P&L values
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from core.trading_executor import TradingExecutor
|
||||
from core.trading_executor import TradeRecord
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def test_position_sizing():
|
||||
"""Test that position sizing now includes leverage and meaningful amounts"""
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("TESTING POSITION SIZING WITH LEVERAGE")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# Initialize trading executor
|
||||
trading_executor = TradingExecutor()
|
||||
|
||||
# Test position calculation
|
||||
confidence = 0.8
|
||||
current_price = 2500.0 # ETH price
|
||||
|
||||
position_value = trading_executor._calculate_position_size(confidence, current_price)
|
||||
quantity = position_value / current_price
|
||||
|
||||
logger.info(f"1. Position calculation test:")
|
||||
logger.info(f" Confidence: {confidence}")
|
||||
logger.info(f" ETH Price: ${current_price}")
|
||||
logger.info(f" Position Value: ${position_value:.2f}")
|
||||
logger.info(f" Quantity: {quantity:.6f} ETH")
|
||||
|
||||
# Check if position is meaningful
|
||||
if position_value > 1000: # Should be >$1000 with 10x leverage
|
||||
logger.info(" ✅ Position size is meaningful (>$1000)")
|
||||
else:
|
||||
logger.error(f" ❌ Position size too small: ${position_value:.2f}")
|
||||
|
||||
# Test different confidence levels
|
||||
logger.info("2. Testing different confidence levels:")
|
||||
for conf in [0.2, 0.5, 0.8, 1.0]:
|
||||
pos_val = trading_executor._calculate_position_size(conf, current_price)
|
||||
qty = pos_val / current_price
|
||||
logger.info(f" Confidence {conf}: ${pos_val:.2f} ({qty:.6f} ETH)")
|
||||
|
||||
def test_eth_only_restriction():
|
||||
"""Test that only ETH trades are allowed"""
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("TESTING ETH-ONLY TRADING RESTRICTION")
|
||||
logger.info("=" * 60)
|
||||
|
||||
trading_executor = TradingExecutor()
|
||||
|
||||
# Test ETH trade (should be allowed)
|
||||
logger.info("1. Testing ETH/USDT trade (should be allowed):")
|
||||
eth_allowed = trading_executor._check_safety_conditions('ETH/USDT', 'BUY')
|
||||
logger.info(f" ETH/USDT allowed: {'✅ YES' if eth_allowed else '❌ NO'}")
|
||||
|
||||
# Test BTC trade (should be blocked)
|
||||
logger.info("2. Testing BTC/USDT trade (should be blocked):")
|
||||
btc_allowed = trading_executor._check_safety_conditions('BTC/USDT', 'BUY')
|
||||
logger.info(f" BTC/USDT allowed: {'❌ YES (ERROR!)' if btc_allowed else '✅ NO (CORRECT)'}")
|
||||
|
||||
def test_win_rate_calculation():
|
||||
"""Test that win rate calculations are correct"""
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("TESTING WIN RATE CALCULATIONS")
|
||||
logger.info("=" * 60)
|
||||
|
||||
trading_executor = TradingExecutor()
|
||||
|
||||
# Clear existing trades
|
||||
trading_executor.trade_history = []
|
||||
|
||||
# Add test trades with meaningful P&L
|
||||
logger.info("1. Adding test trades with meaningful P&L:")
|
||||
|
||||
# Add 3 winning trades
|
||||
for i in range(3):
|
||||
winning_trade = TradeRecord(
|
||||
symbol='ETH/USDT',
|
||||
side='LONG',
|
||||
quantity=1.0,
|
||||
entry_price=2500.0,
|
||||
exit_price=2550.0,
|
||||
entry_time=datetime.now(),
|
||||
exit_time=datetime.now(),
|
||||
pnl=50.0, # $50 profit with leverage
|
||||
fees=1.0,
|
||||
confidence=0.8,
|
||||
hold_time_seconds=30.0 # 30 second hold
|
||||
)
|
||||
trading_executor.trade_history.append(winning_trade)
|
||||
logger.info(f" Added winning trade #{i+1}: +$50.00 (30s hold)")
|
||||
|
||||
# Add 2 losing trades
|
||||
for i in range(2):
|
||||
losing_trade = TradeRecord(
|
||||
symbol='ETH/USDT',
|
||||
side='LONG',
|
||||
quantity=1.0,
|
||||
entry_price=2500.0,
|
||||
exit_price=2475.0,
|
||||
entry_time=datetime.now(),
|
||||
exit_time=datetime.now(),
|
||||
pnl=-25.0, # $25 loss with leverage
|
||||
fees=1.0,
|
||||
confidence=0.7,
|
||||
hold_time_seconds=15.0 # 15 second hold
|
||||
)
|
||||
trading_executor.trade_history.append(losing_trade)
|
||||
logger.info(f" Added losing trade #{i+1}: -$25.00 (15s hold)")
|
||||
|
||||
# Get statistics
|
||||
stats = trading_executor.get_daily_stats()
|
||||
|
||||
logger.info("2. Calculated statistics:")
|
||||
logger.info(f" Total trades: {stats['total_trades']}")
|
||||
logger.info(f" Winning trades: {stats['winning_trades']}")
|
||||
logger.info(f" Losing trades: {stats['losing_trades']}")
|
||||
logger.info(f" Win rate: {stats['win_rate']*100:.1f}%")
|
||||
logger.info(f" Avg winning trade: ${stats['avg_winning_trade']:.2f}")
|
||||
logger.info(f" Avg losing trade: ${stats['avg_losing_trade']:.2f}")
|
||||
logger.info(f" Total P&L: ${stats['total_pnl']:.2f}")
|
||||
|
||||
# Verify calculations
|
||||
expected_win_rate = 3/5 # 3 wins out of 5 trades = 60%
|
||||
expected_avg_win = 50.0
|
||||
expected_avg_loss = -25.0
|
||||
|
||||
logger.info("3. Verification:")
|
||||
win_rate_ok = abs(stats['win_rate'] - expected_win_rate) < 0.01
|
||||
avg_win_ok = abs(stats['avg_winning_trade'] - expected_avg_win) < 0.01
|
||||
avg_loss_ok = abs(stats['avg_losing_trade'] - expected_avg_loss) < 0.01
|
||||
|
||||
logger.info(f" Win rate: Expected {expected_win_rate*100:.1f}%, Got {stats['win_rate']*100:.1f}% {'✅' if win_rate_ok else '❌'}")
|
||||
logger.info(f" Avg win: Expected ${expected_avg_win:.2f}, Got ${stats['avg_winning_trade']:.2f} {'✅' if avg_win_ok else '❌'}")
|
||||
logger.info(f" Avg loss: Expected ${expected_avg_loss:.2f}, Got ${stats['avg_losing_trade']:.2f} {'✅' if avg_loss_ok else '❌'}")
|
||||
|
||||
return win_rate_ok and avg_win_ok and avg_loss_ok
|
||||
|
||||
def test_new_features():
|
||||
"""Test new features: hold time, leverage, percentage-based sizing"""
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("TESTING NEW FEATURES")
|
||||
logger.info("=" * 60)
|
||||
|
||||
trading_executor = TradingExecutor()
|
||||
|
||||
# Test account info
|
||||
account_info = trading_executor.get_account_info()
|
||||
logger.info(f"1. Account Information:")
|
||||
logger.info(f" Account Balance: ${account_info['account_balance']:.2f}")
|
||||
logger.info(f" Leverage: {account_info['leverage']:.0f}x")
|
||||
logger.info(f" Trading Mode: {account_info['trading_mode']}")
|
||||
logger.info(f" Position Sizing: {account_info['position_sizing']['base_percent']:.1f}% base")
|
||||
|
||||
# Test leverage setting
|
||||
logger.info("2. Testing leverage control:")
|
||||
old_leverage = trading_executor.get_leverage()
|
||||
logger.info(f" Current leverage: {old_leverage:.0f}x")
|
||||
|
||||
success = trading_executor.set_leverage(100.0)
|
||||
new_leverage = trading_executor.get_leverage()
|
||||
logger.info(f" Set to 100x: {'✅ SUCCESS' if success and new_leverage == 100.0 else '❌ FAILED'}")
|
||||
|
||||
# Reset leverage
|
||||
trading_executor.set_leverage(old_leverage)
|
||||
|
||||
# Test percentage-based position sizing
|
||||
logger.info("3. Testing percentage-based position sizing:")
|
||||
confidence = 0.8
|
||||
eth_price = 2500.0
|
||||
|
||||
position_value = trading_executor._calculate_position_size(confidence, eth_price)
|
||||
account_balance = trading_executor._get_account_balance_for_sizing()
|
||||
base_percent = trading_executor.mexc_config.get('base_position_percent', 5.0)
|
||||
leverage = trading_executor.get_leverage()
|
||||
|
||||
expected_base = account_balance * (base_percent / 100.0) * confidence
|
||||
expected_leveraged = expected_base * leverage
|
||||
|
||||
logger.info(f" Account: ${account_balance:.2f}")
|
||||
logger.info(f" Base %: {base_percent:.1f}%")
|
||||
logger.info(f" Confidence: {confidence:.1f}")
|
||||
logger.info(f" Leverage: {leverage:.0f}x")
|
||||
logger.info(f" Expected base: ${expected_base:.2f}")
|
||||
logger.info(f" Expected leveraged: ${expected_leveraged:.2f}")
|
||||
logger.info(f" Actual: ${position_value:.2f}")
|
||||
|
||||
sizing_ok = abs(position_value - expected_leveraged) < 0.01
|
||||
logger.info(f" Percentage sizing: {'✅ CORRECT' if sizing_ok else '❌ INCORRECT'}")
|
||||
|
||||
return sizing_ok
|
||||
|
||||
def main():
|
||||
"""Run all tests"""
|
||||
|
||||
logger.info("🚀 TESTING TRADING FIXES AND NEW FEATURES")
|
||||
logger.info("Testing position sizing, ETH-only trading, win rate calculations, and new features")
|
||||
|
||||
# Test position sizing
|
||||
test_position_sizing()
|
||||
|
||||
# Test ETH-only restriction
|
||||
test_eth_only_restriction()
|
||||
|
||||
# Test win rate calculation
|
||||
calculation_success = test_win_rate_calculation()
|
||||
|
||||
# Test new features
|
||||
features_success = test_new_features()
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("TEST SUMMARY")
|
||||
logger.info("=" * 60)
|
||||
logger.info(f"Position Sizing: ✅ Updated with percentage-based leverage")
|
||||
logger.info(f"ETH-Only Trading: ✅ Configured in config")
|
||||
logger.info(f"Win Rate Calculation: {'✅ FIXED' if calculation_success else '❌ STILL BROKEN'}")
|
||||
logger.info(f"New Features: {'✅ WORKING' if features_success else '❌ ISSUES FOUND'}")
|
||||
|
||||
if calculation_success and features_success:
|
||||
logger.info("🎉 ALL FEATURES WORKING! Now you should see:")
|
||||
logger.info(" - Percentage-based position sizing (2-20% of account)")
|
||||
logger.info(" - 50x leverage (adjustable in UI)")
|
||||
logger.info(" - Hold time in seconds for each trade")
|
||||
logger.info(" - Total fees in trading statistics")
|
||||
logger.info(" - Only ETH/USDT trades")
|
||||
logger.info(" - Correct win rate calculations")
|
||||
else:
|
||||
logger.error("❌ Some issues remain. Check the logs above for details.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,344 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Trade Audit Tool
|
||||
|
||||
This tool analyzes trade data to identify potential issues with:
|
||||
- Duplicate entry prices
|
||||
- Rapid consecutive trades
|
||||
- P&L calculation accuracy
|
||||
- Position tracking problems
|
||||
|
||||
Usage:
|
||||
python debug/trade_audit.py [--trades-file path/to/trades.json]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from datetime import datetime, timedelta
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
def parse_trade_time(time_str):
|
||||
"""Parse trade time string to datetime object"""
|
||||
try:
|
||||
# Try HH:MM:SS format
|
||||
return datetime.strptime(time_str, "%H:%M:%S")
|
||||
except ValueError:
|
||||
try:
|
||||
# Try full datetime format
|
||||
return datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
# Return as is if parsing fails
|
||||
return time_str
|
||||
|
||||
def load_trades_from_file(file_path):
|
||||
"""Load trades from JSON file"""
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File {file_path} not found")
|
||||
return []
|
||||
except json.JSONDecodeError:
|
||||
print(f"Error: File {file_path} is not valid JSON")
|
||||
return []
|
||||
|
||||
def load_trades_from_dashboard_cache():
|
||||
"""Load trades from dashboard cache file if available"""
|
||||
cache_paths = [
|
||||
"cache/dashboard_trades.json",
|
||||
"cache/closed_trades.json",
|
||||
"data/trades_history.json"
|
||||
]
|
||||
|
||||
for path in cache_paths:
|
||||
if os.path.exists(path):
|
||||
print(f"Loading trades from cache: {path}")
|
||||
return load_trades_from_file(path)
|
||||
|
||||
print("No trade cache files found")
|
||||
return []
|
||||
|
||||
def parse_trade_data(trades_data):
|
||||
"""Parse trade data into a pandas DataFrame for analysis"""
|
||||
parsed_trades = []
|
||||
|
||||
for trade in trades_data:
|
||||
# Handle different trade data formats
|
||||
parsed_trade = {}
|
||||
|
||||
# Time field might be named entry_time or time
|
||||
if 'entry_time' in trade:
|
||||
parsed_trade['time'] = parse_trade_time(trade['entry_time'])
|
||||
elif 'time' in trade:
|
||||
parsed_trade['time'] = parse_trade_time(trade['time'])
|
||||
else:
|
||||
parsed_trade['time'] = None
|
||||
|
||||
# Side might be named side or action
|
||||
parsed_trade['side'] = trade.get('side', trade.get('action', 'UNKNOWN'))
|
||||
|
||||
# Size might be named size or quantity
|
||||
parsed_trade['size'] = float(trade.get('size', trade.get('quantity', 0)))
|
||||
|
||||
# Entry and exit prices
|
||||
parsed_trade['entry_price'] = float(trade.get('entry_price', trade.get('entry', 0)))
|
||||
parsed_trade['exit_price'] = float(trade.get('exit_price', trade.get('exit', 0)))
|
||||
|
||||
# Hold time in seconds
|
||||
parsed_trade['hold_time'] = float(trade.get('hold_time_seconds', trade.get('hold', 0)))
|
||||
|
||||
# P&L and fees
|
||||
parsed_trade['pnl'] = float(trade.get('pnl', 0))
|
||||
parsed_trade['fees'] = float(trade.get('fees', 0))
|
||||
|
||||
# Calculate expected P&L for verification
|
||||
if parsed_trade['side'] == 'LONG' or parsed_trade['side'] == 'BUY':
|
||||
expected_pnl = (parsed_trade['exit_price'] - parsed_trade['entry_price']) * parsed_trade['size']
|
||||
else: # SHORT or SELL
|
||||
expected_pnl = (parsed_trade['entry_price'] - parsed_trade['exit_price']) * parsed_trade['size']
|
||||
|
||||
parsed_trade['expected_pnl'] = expected_pnl
|
||||
parsed_trade['pnl_difference'] = parsed_trade['pnl'] - expected_pnl
|
||||
|
||||
parsed_trades.append(parsed_trade)
|
||||
|
||||
# Convert to DataFrame
|
||||
if parsed_trades:
|
||||
df = pd.DataFrame(parsed_trades)
|
||||
return df
|
||||
else:
|
||||
return pd.DataFrame()
|
||||
|
||||
def analyze_trades(df):
|
||||
"""Analyze trades for potential issues"""
|
||||
if df.empty:
|
||||
print("No trades to analyze")
|
||||
return
|
||||
|
||||
print(f"\n{'='*50}")
|
||||
print("TRADE AUDIT RESULTS")
|
||||
print(f"{'='*50}")
|
||||
print(f"Total trades analyzed: {len(df)}")
|
||||
|
||||
# Check for duplicate entry prices
|
||||
entry_price_counts = df['entry_price'].value_counts()
|
||||
duplicate_entries = entry_price_counts[entry_price_counts > 1]
|
||||
|
||||
print(f"\n{'='*20} DUPLICATE ENTRY PRICES {'='*20}")
|
||||
if not duplicate_entries.empty:
|
||||
print(f"Found {len(duplicate_entries)} prices with multiple entries:")
|
||||
for price, count in duplicate_entries.items():
|
||||
print(f" ${price:.2f}: {count} trades")
|
||||
|
||||
# Analyze the duplicate entry trades in more detail
|
||||
for price in duplicate_entries.index:
|
||||
duplicate_df = df[df['entry_price'] == price].copy()
|
||||
duplicate_df['time_diff'] = duplicate_df['time'].diff().dt.total_seconds()
|
||||
|
||||
print(f"\nDetailed analysis for entry price ${price:.2f}:")
|
||||
print(f" Time gaps between consecutive trades:")
|
||||
for i, (_, row) in enumerate(duplicate_df.iterrows()):
|
||||
if i > 0: # Skip first row as it has no previous trade
|
||||
time_diff = row['time_diff']
|
||||
if pd.notna(time_diff):
|
||||
print(f" {row['time'].strftime('%H:%M:%S')}: {time_diff:.0f} seconds after previous trade")
|
||||
else:
|
||||
print("No duplicate entry prices found")
|
||||
|
||||
# Check for rapid consecutive trades
|
||||
df = df.sort_values('time')
|
||||
df['time_since_last'] = df['time'].diff().dt.total_seconds()
|
||||
|
||||
rapid_trades = df[df['time_since_last'] < 30].copy()
|
||||
|
||||
print(f"\n{'='*20} RAPID CONSECUTIVE TRADES {'='*20}")
|
||||
if not rapid_trades.empty:
|
||||
print(f"Found {len(rapid_trades)} trades executed within 30 seconds of previous trade:")
|
||||
for _, row in rapid_trades.iterrows():
|
||||
if pd.notna(row['time_since_last']):
|
||||
print(f" {row['time'].strftime('%H:%M:%S')} - {row['side']} ${row['size']:.2f} @ ${row['entry_price']:.2f} - {row['time_since_last']:.0f}s after previous")
|
||||
else:
|
||||
print("No rapid consecutive trades found")
|
||||
|
||||
# Check for P&L calculation accuracy
|
||||
pnl_diff = df[abs(df['pnl_difference']) > 0.01].copy()
|
||||
|
||||
print(f"\n{'='*20} P&L CALCULATION ISSUES {'='*20}")
|
||||
if not pnl_diff.empty:
|
||||
print(f"Found {len(pnl_diff)} trades with P&L calculation discrepancies:")
|
||||
for _, row in pnl_diff.iterrows():
|
||||
print(f" {row['time'].strftime('%H:%M:%S')} - {row['side']} - Reported: ${row['pnl']:.2f}, Expected: ${row['expected_pnl']:.2f}, Diff: ${row['pnl_difference']:.2f}")
|
||||
else:
|
||||
print("No P&L calculation issues found")
|
||||
|
||||
# Check for side distribution
|
||||
side_counts = df['side'].value_counts()
|
||||
|
||||
print(f"\n{'='*20} TRADE SIDE DISTRIBUTION {'='*20}")
|
||||
for side, count in side_counts.items():
|
||||
print(f" {side}: {count} trades ({count/len(df)*100:.1f}%)")
|
||||
|
||||
# Check for hold time distribution
|
||||
print(f"\n{'='*20} HOLD TIME DISTRIBUTION {'='*20}")
|
||||
print(f" Min hold time: {df['hold_time'].min():.0f} seconds")
|
||||
print(f" Max hold time: {df['hold_time'].max():.0f} seconds")
|
||||
print(f" Avg hold time: {df['hold_time'].mean():.0f} seconds")
|
||||
print(f" Median hold time: {df['hold_time'].median():.0f} seconds")
|
||||
|
||||
# Hold time buckets
|
||||
hold_buckets = [0, 30, 60, 120, 300, 600, 1800, 3600, float('inf')]
|
||||
hold_labels = ['0-30s', '30-60s', '1-2m', '2-5m', '5-10m', '10-30m', '30-60m', '60m+']
|
||||
|
||||
df['hold_bucket'] = pd.cut(df['hold_time'], bins=hold_buckets, labels=hold_labels)
|
||||
hold_dist = df['hold_bucket'].value_counts().sort_index()
|
||||
|
||||
for bucket, count in hold_dist.items():
|
||||
print(f" {bucket}: {count} trades ({count/len(df)*100:.1f}%)")
|
||||
|
||||
# Generate summary statistics
|
||||
print(f"\n{'='*20} TRADE PERFORMANCE SUMMARY {'='*20}")
|
||||
winning_trades = df[df['pnl'] > 0]
|
||||
losing_trades = df[df['pnl'] < 0]
|
||||
|
||||
print(f" Win rate: {len(winning_trades)/len(df)*100:.1f}% ({len(winning_trades)}W/{len(losing_trades)}L)")
|
||||
print(f" Avg win: ${winning_trades['pnl'].mean():.2f}")
|
||||
print(f" Avg loss: ${abs(losing_trades['pnl'].mean()):.2f}")
|
||||
print(f" Total P&L: ${df['pnl'].sum():.2f}")
|
||||
print(f" Total fees: ${df['fees'].sum():.2f}")
|
||||
print(f" Net P&L: ${(df['pnl'].sum() - df['fees'].sum()):.2f}")
|
||||
|
||||
# Plot entry price distribution
|
||||
plt.figure(figsize=(10, 6))
|
||||
plt.hist(df['entry_price'], bins=20, alpha=0.7)
|
||||
plt.title('Entry Price Distribution')
|
||||
plt.xlabel('Entry Price ($)')
|
||||
plt.ylabel('Number of Trades')
|
||||
plt.grid(True, alpha=0.3)
|
||||
plt.savefig('debug/entry_price_distribution.png')
|
||||
|
||||
# Plot P&L distribution
|
||||
plt.figure(figsize=(10, 6))
|
||||
plt.hist(df['pnl'], bins=20, alpha=0.7)
|
||||
plt.title('P&L Distribution')
|
||||
plt.xlabel('P&L ($)')
|
||||
plt.ylabel('Number of Trades')
|
||||
plt.grid(True, alpha=0.3)
|
||||
plt.savefig('debug/pnl_distribution.png')
|
||||
|
||||
print(f"\n{'='*20} AUDIT COMPLETE {'='*20}")
|
||||
print("Plots saved to debug/entry_price_distribution.png and debug/pnl_distribution.png")
|
||||
|
||||
def analyze_manual_trades(trades_data):
|
||||
"""Analyze manually provided trade data"""
|
||||
# Parse the trade data into a structured format
|
||||
parsed_trades = []
|
||||
|
||||
for line in trades_data.strip().split('\n'):
|
||||
if not line or line.startswith('from last session') or line.startswith('Recent Closed Trades') or line.startswith('Trading Performance'):
|
||||
continue
|
||||
|
||||
if line.startswith('Win Rate:'):
|
||||
# This is the summary line, skip it
|
||||
continue
|
||||
|
||||
try:
|
||||
# Parse trade line format: Time Side Size Entry Exit Hold P&L Fees
|
||||
parts = line.split('$')
|
||||
|
||||
time_side = parts[0].strip().split()
|
||||
time = time_side[0]
|
||||
side = time_side[1]
|
||||
|
||||
size = float(parts[1].split()[0])
|
||||
entry = float(parts[2].split()[0])
|
||||
exit = float(parts[3].split()[0])
|
||||
|
||||
# The hold time and P&L are in the last parts
|
||||
remaining = parts[3].split()
|
||||
hold = int(remaining[1])
|
||||
pnl = float(parts[4].split()[0])
|
||||
|
||||
# Fees might be in a different format
|
||||
if len(parts) > 5:
|
||||
fees = float(parts[5].strip())
|
||||
else:
|
||||
fees = 0.0
|
||||
|
||||
parsed_trade = {
|
||||
'time': parse_trade_time(time),
|
||||
'side': side,
|
||||
'size': size,
|
||||
'entry_price': entry,
|
||||
'exit_price': exit,
|
||||
'hold_time': hold,
|
||||
'pnl': pnl,
|
||||
'fees': fees
|
||||
}
|
||||
|
||||
# Calculate expected P&L
|
||||
if side == 'LONG' or side == 'BUY':
|
||||
expected_pnl = (exit - entry) * size
|
||||
else: # SHORT or SELL
|
||||
expected_pnl = (entry - exit) * size
|
||||
|
||||
parsed_trade['expected_pnl'] = expected_pnl
|
||||
parsed_trade['pnl_difference'] = pnl - expected_pnl
|
||||
|
||||
parsed_trades.append(parsed_trade)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error parsing trade line: {line}")
|
||||
print(f"Error details: {e}")
|
||||
|
||||
# Convert to DataFrame
|
||||
if parsed_trades:
|
||||
df = pd.DataFrame(parsed_trades)
|
||||
return df
|
||||
else:
|
||||
return pd.DataFrame()
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Trade Audit Tool')
|
||||
parser.add_argument('--trades-file', type=str, help='Path to trades JSON file')
|
||||
parser.add_argument('--manual-trades', type=str, help='Path to text file with manually entered trades')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Create debug directory if it doesn't exist
|
||||
os.makedirs('debug', exist_ok=True)
|
||||
|
||||
if args.trades_file:
|
||||
trades_data = load_trades_from_file(args.trades_file)
|
||||
df = parse_trade_data(trades_data)
|
||||
elif args.manual_trades:
|
||||
try:
|
||||
with open(args.manual_trades, 'r') as f:
|
||||
manual_trades = f.read()
|
||||
df = analyze_manual_trades(manual_trades)
|
||||
except Exception as e:
|
||||
print(f"Error reading manual trades file: {e}")
|
||||
df = pd.DataFrame()
|
||||
else:
|
||||
# Try to load from dashboard cache
|
||||
trades_data = load_trades_from_dashboard_cache()
|
||||
if trades_data:
|
||||
df = parse_trade_data(trades_data)
|
||||
else:
|
||||
print("No trade data provided. Use --trades-file or --manual-trades")
|
||||
return
|
||||
|
||||
if not df.empty:
|
||||
analyze_trades(df)
|
||||
else:
|
||||
print("No valid trade data to analyze")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user