|
@@ -832,16 +832,205 @@ class HyperliquidAccountAnalyzer:
|
|
|
|
|
|
return valid_results
|
|
|
|
|
|
- def print_analysis_results(self, stats_list: List[AccountStats]):
|
|
|
+ async def print_single_account_deep_dive(self, stats: AccountStats):
|
|
|
+ """Print detailed deep dive analysis for a single account"""
|
|
|
+ print(f"\n🎯 ACCOUNT: {stats.address}")
|
|
|
+ print("="*100)
|
|
|
+
|
|
|
+ # Fetch current live data
|
|
|
+ print("🔄 Fetching current account state...")
|
|
|
+ try:
|
|
|
+ current_state = await self.get_account_state(stats.address)
|
|
|
+ current_positions = self.parse_positions(current_state) if current_state else []
|
|
|
+
|
|
|
+ # Get recent fills for more context
|
|
|
+ recent_fills = await self.get_user_fills(stats.address, limit=20)
|
|
|
+ recent_trades = self.parse_trades(recent_fills) if recent_fills else []
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ print(f"⚠️ Could not fetch live data: {e}")
|
|
|
+ current_positions = []
|
|
|
+ recent_trades = []
|
|
|
+
|
|
|
+ # Basic Account Information
|
|
|
+ print(f"\n📋 ACCOUNT OVERVIEW")
|
|
|
+ print(f" 📍 Address: {stats.address}")
|
|
|
+ print(f" 📅 Analysis Period: {stats.analysis_period_days} days")
|
|
|
+ if stats.last_trade_timestamp > 0:
|
|
|
+ from datetime import datetime
|
|
|
+ last_trade_date = datetime.fromtimestamp(stats.last_trade_timestamp / 1000)
|
|
|
+ print(f" 🕒 Last Trade: {last_trade_date.strftime('%Y-%m-%d %H:%M:%S')} UTC")
|
|
|
+
|
|
|
+ # Current Positions (LIVE DATA)
|
|
|
+ print(f"\n📊 CURRENT POSITIONS ({len(current_positions)} active)")
|
|
|
+ if current_positions:
|
|
|
+ total_position_value = 0
|
|
|
+ total_unrealized_pnl = 0
|
|
|
+
|
|
|
+ for pos in current_positions:
|
|
|
+ side_emoji = "📈" if pos.side == "long" else "📉"
|
|
|
+ pnl_emoji = "✅" if pos.unrealized_pnl >= 0 else "❌"
|
|
|
+
|
|
|
+ print(f" {side_emoji} {pos.coin} - {pos.side.upper()}")
|
|
|
+ print(f" 💰 Size: {pos.size:.6f} {pos.coin} @ ${pos.entry_price:.4f}")
|
|
|
+ print(f" 🏷️ Mark Price: ${pos.mark_price:.4f}")
|
|
|
+ print(f" {pnl_emoji} Unrealized PnL: ${pos.unrealized_pnl:.2f}")
|
|
|
+ print(f" ⚖️ Leverage: {pos.leverage:.1f}x")
|
|
|
+ print(f" 💳 Margin Used: ${pos.margin_used:.2f}")
|
|
|
+
|
|
|
+ total_position_value += abs(pos.size * pos.mark_price)
|
|
|
+ total_unrealized_pnl += pos.unrealized_pnl
|
|
|
+ print()
|
|
|
+
|
|
|
+ print(f" 📊 POSITION SUMMARY:")
|
|
|
+ print(f" 💼 Total Position Value: ${total_position_value:.2f}")
|
|
|
+ print(f" 📈 Total Unrealized PnL: ${total_unrealized_pnl:.2f}")
|
|
|
+
|
|
|
+ else:
|
|
|
+ print(" 💤 No active positions")
|
|
|
+
|
|
|
+ # Performance Metrics
|
|
|
+ print(f"\n📈 PERFORMANCE ANALYSIS")
|
|
|
+ print(f" 💰 Total Realized PnL: ${stats.total_pnl:.2f}")
|
|
|
+ print(f" 📊 Win Rate: {stats.win_rate:.1%}")
|
|
|
+ print(f" 🎯 Total Trades: {stats.total_trades}")
|
|
|
+ print(f" 💵 Average Position Size: ${stats.avg_position_size:.2f}")
|
|
|
+ print(f" ⏱️ Average Trade Duration: {stats.avg_trade_duration_hours:.1f} hours")
|
|
|
+ print(f" 📉 Maximum Drawdown: {stats.max_drawdown:.1%}")
|
|
|
+ print(f" 📉 Current Drawdown: {stats.current_drawdown:.1%}")
|
|
|
+ print(f" 🏆 Largest Win: ${stats.largest_win:.2f}")
|
|
|
+ print(f" 💔 Largest Loss: ${stats.largest_loss:.2f}")
|
|
|
+ print(f" 📊 Profit Factor: {stats.profit_factor:.2f}")
|
|
|
+ print(f" 🔗 Max Consecutive Losses: {stats.consecutive_losses_max}")
|
|
|
+
|
|
|
+ # Trading Patterns
|
|
|
+ print(f"\n🎲 TRADING PATTERNS")
|
|
|
+ print(f" 🔄 Trading Frequency: {stats.trading_frequency_per_day:.1f} trades/day")
|
|
|
+ print(f" ⚖️ Average Leverage: {stats.avg_leverage_used:.1f}x")
|
|
|
+ print(f" 📊 Max Leverage Used: {stats.max_leverage_used:.1f}x")
|
|
|
+ print(f" 🪙 Unique Tokens Traded: {stats.unique_tokens_traded}")
|
|
|
+
|
|
|
+ # Trading style analysis
|
|
|
+ trading_type_display = {
|
|
|
+ "perps": "🔄 Perpetuals",
|
|
|
+ "spot": "💱 Spot Trading",
|
|
|
+ "mixed": "🔀 Mixed (Spot + Perps)",
|
|
|
+ "unknown": "❓ Unknown"
|
|
|
+ }.get(stats.trading_type, f"❓ {stats.trading_type}")
|
|
|
+ print(f" 📈 Trading Type: {trading_type_display}")
|
|
|
+ print(f" 📊 Trading Style: {stats.trading_style}")
|
|
|
+ print(f" 📉 Short Percentage: {stats.short_percentage:.1f}%")
|
|
|
+
|
|
|
+ # Buy/Sell ratio
|
|
|
+ if stats.buy_sell_ratio == float('inf'):
|
|
|
+ ratio_display = "∞ (only buys)"
|
|
|
+ elif stats.buy_sell_ratio == 0:
|
|
|
+ ratio_display = "0 (only sells)"
|
|
|
+ else:
|
|
|
+ ratio_display = f"{stats.buy_sell_ratio:.2f}"
|
|
|
+ print(f" ⚖️ Buy/Sell Ratio: {ratio_display}")
|
|
|
+
|
|
|
+ # Top tokens
|
|
|
+ if stats.top_tokens:
|
|
|
+ print(f" 🏆 Top Traded Tokens: {', '.join(stats.top_tokens[:5])}")
|
|
|
+
|
|
|
+ # Recent Trading Activity
|
|
|
+ if recent_trades:
|
|
|
+ print(f"\n🕒 RECENT TRADING ACTIVITY (last {len(recent_trades)} trades)")
|
|
|
+ for i, trade in enumerate(recent_trades[:10], 1): # Show last 10 trades
|
|
|
+ from datetime import datetime
|
|
|
+ trade_time = datetime.fromtimestamp(trade.timestamp / 1000)
|
|
|
+ side_emoji = "🟢" if trade.side == "buy" else "🔴"
|
|
|
+
|
|
|
+ print(f" {i}. {side_emoji} {trade.side.upper()} {trade.size:.6f} {trade.coin}")
|
|
|
+ print(f" 💰 Price: ${trade.price:.4f} | Value: ${trade.size * trade.price:.2f}")
|
|
|
+ print(f" 🕒 Time: {trade_time.strftime('%m-%d %H:%M:%S')}")
|
|
|
+ if i < len(recent_trades[:10]):
|
|
|
+ print()
|
|
|
+
|
|
|
+ # Copy Trading Evaluation
|
|
|
+ print(f"\n🎯 COPY TRADING EVALUATION")
|
|
|
+ print(f" ✅ Copyable: {'YES' if stats.is_copyable else 'NO'}")
|
|
|
+ print(f" 📝 Reason: {stats.copyability_reason}")
|
|
|
+
|
|
|
+ # Detailed evaluation
|
|
|
+ evaluation = []
|
|
|
+ is_hft_pattern = stats.trading_frequency_per_day > 50
|
|
|
+ is_copyable = 1 <= stats.trading_frequency_per_day <= 20
|
|
|
+
|
|
|
+ if is_hft_pattern:
|
|
|
+ evaluation.append("❌ HFT/Bot pattern detected")
|
|
|
+ elif stats.trading_frequency_per_day < 1:
|
|
|
+ evaluation.append("❌ Too inactive for copy trading")
|
|
|
+ elif is_copyable:
|
|
|
+ evaluation.append("✅ Human-like trading pattern")
|
|
|
+
|
|
|
+ if stats.total_pnl > 0:
|
|
|
+ evaluation.append("✅ Profitable track record")
|
|
|
+ else:
|
|
|
+ evaluation.append("❌ Not profitable")
|
|
|
+
|
|
|
+ if stats.max_drawdown < 0.15:
|
|
|
+ evaluation.append("✅ Good risk management")
|
|
|
+ elif stats.max_drawdown < 0.25:
|
|
|
+ evaluation.append("⚠️ Moderate risk")
|
|
|
+ else:
|
|
|
+ evaluation.append("❌ High risk (excessive drawdown)")
|
|
|
+
|
|
|
+ if 2 <= stats.avg_trade_duration_hours <= 48:
|
|
|
+ evaluation.append("✅ Suitable trade duration")
|
|
|
+ elif stats.avg_trade_duration_hours < 2:
|
|
|
+ evaluation.append("⚠️ Very short trades (scalping)")
|
|
|
+ else:
|
|
|
+ evaluation.append("⚠️ Long hold times")
|
|
|
+
|
|
|
+ print(f"\n 📊 Detailed Evaluation:")
|
|
|
+ for eval_point in evaluation:
|
|
|
+ print(f" {eval_point}")
|
|
|
+
|
|
|
+ # Copy Trading Recommendations
|
|
|
+ if stats.is_copyable and stats.total_pnl > 0:
|
|
|
+ print(f"\n💡 COPY TRADING RECOMMENDATIONS")
|
|
|
+
|
|
|
+ if stats.max_drawdown < 0.1 and stats.win_rate > 0.6:
|
|
|
+ print(f" 📊 Suggested Portfolio Allocation: 15-25% (high confidence)")
|
|
|
+ print(f" ⚖️ Max Leverage Limit: 8-10x")
|
|
|
+ elif stats.max_drawdown < 0.2 and stats.win_rate > 0.5:
|
|
|
+ print(f" 📊 Suggested Portfolio Allocation: 8-15% (moderate confidence)")
|
|
|
+ print(f" ⚖️ Max Leverage Limit: 5-8x")
|
|
|
+ else:
|
|
|
+ print(f" 📊 Suggested Portfolio Allocation: 3-8% (low confidence)")
|
|
|
+ print(f" ⚖️ Max Leverage Limit: 2-5x")
|
|
|
+
|
|
|
+ print(f" 💰 Min Position Size: $25-50")
|
|
|
+ print(f" 🔄 Expected Activity: ~{stats.trading_frequency_per_day:.1f} trades/day")
|
|
|
+ print(f" ⏱️ Avg Trade Duration: ~{stats.avg_trade_duration_hours:.1f} hours")
|
|
|
+
|
|
|
+ else:
|
|
|
+ print(f"\n❌ NOT RECOMMENDED FOR COPY TRADING")
|
|
|
+ print(f" Reason: {stats.copyability_reason}")
|
|
|
+
|
|
|
+ async def print_analysis_results(self, stats_list: List[AccountStats]):
|
|
|
"""Print comprehensive analysis results with relative scoring"""
|
|
|
if not stats_list:
|
|
|
print("❌ No valid analysis results to display")
|
|
|
return
|
|
|
|
|
|
+ # Check if this is a single account deep dive
|
|
|
+ is_single_account = len(stats_list) == 1
|
|
|
+
|
|
|
print("\n" + "="*100)
|
|
|
- print("📊 HYPERLIQUID ACCOUNT ANALYSIS RESULTS")
|
|
|
+ if is_single_account:
|
|
|
+ print("🔍 DEEP DIVE ACCOUNT ANALYSIS")
|
|
|
+ else:
|
|
|
+ print("📊 HYPERLIQUID ACCOUNT ANALYSIS RESULTS")
|
|
|
print("="*100)
|
|
|
|
|
|
+ # Special handling for single account analysis
|
|
|
+ if is_single_account:
|
|
|
+ await self.print_single_account_deep_dive(stats_list[0])
|
|
|
+ return
|
|
|
+
|
|
|
# Calculate data ranges for relative scoring
|
|
|
def get_data_ranges(stats_list):
|
|
|
"""Calculate min/max values for relative scoring"""
|
|
@@ -1458,7 +1647,7 @@ async def main():
|
|
|
return
|
|
|
|
|
|
results = await analyzer.analyze_multiple_accounts(addresses)
|
|
|
- analyzer.print_analysis_results(results)
|
|
|
+ await analyzer.print_analysis_results(results)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
asyncio.run(main())
|