|
@@ -93,6 +93,12 @@ class AccountStats:
|
|
|
analysis_period_days: int
|
|
|
is_copyable: bool # Whether this account is suitable for copy trading
|
|
|
copyability_reason: str # Why it is/isn't copyable
|
|
|
+ unique_tokens_traded: int # Number of unique tokens/coins traded
|
|
|
+ trading_type: str # "spot", "perps", or "mixed"
|
|
|
+ top_tokens: List[str] # Top 5 most traded tokens by volume
|
|
|
+ short_percentage: float # Percentage of trades that are likely shorts
|
|
|
+ trading_style: str # Directional trading style description
|
|
|
+ buy_sell_ratio: float # Ratio of buys to sells
|
|
|
|
|
|
class HyperliquidAccountAnalyzer:
|
|
|
"""Analyzes Hyperliquid trading accounts"""
|
|
@@ -450,6 +456,146 @@ class HyperliquidAccountAnalyzer:
|
|
|
|
|
|
return cumulative_pnl, pnl_series, winning_periods, losing_periods
|
|
|
|
|
|
+ def analyze_token_diversity_and_type(self, trades: List[Trade], positions: List[Position]) -> Tuple[int, str, List[str]]:
|
|
|
+ """
|
|
|
+ Analyze token diversity and determine trading type (spot vs perps)
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ tuple: (unique_tokens_count, trading_type, top_tokens_list)
|
|
|
+ """
|
|
|
+ if not trades:
|
|
|
+ return 0, "unknown", []
|
|
|
+
|
|
|
+ # Count token frequency by volume
|
|
|
+ token_volumes = defaultdict(float)
|
|
|
+ for trade in trades:
|
|
|
+ volume = trade.size * trade.price
|
|
|
+ token_volumes[trade.coin] += volume
|
|
|
+
|
|
|
+ # Get unique token count
|
|
|
+ unique_tokens = len(token_volumes)
|
|
|
+
|
|
|
+ # Get top 5 tokens by volume
|
|
|
+ sorted_tokens = sorted(token_volumes.items(), key=lambda x: x[1], reverse=True)
|
|
|
+ top_tokens = [token for token, _ in sorted_tokens[:5]]
|
|
|
+
|
|
|
+ # Determine trading type based on positions and leverage
|
|
|
+ trading_type = self._determine_trading_type(positions, trades)
|
|
|
+
|
|
|
+ return unique_tokens, trading_type, top_tokens
|
|
|
+
|
|
|
+ def _determine_trading_type(self, positions: List[Position], trades: List[Trade]) -> str:
|
|
|
+ """
|
|
|
+ Determine if account trades spot, perps, or mixed
|
|
|
+
|
|
|
+ Logic:
|
|
|
+ - If positions have leverage > 1.1, it's perps
|
|
|
+ - If no positions with leverage, check for margin/leverage indicators
|
|
|
+ - Hyperliquid primarily offers perps, so default to perps if uncertain
|
|
|
+ """
|
|
|
+ if not positions and not trades:
|
|
|
+ return "unknown"
|
|
|
+
|
|
|
+ # Check current positions for leverage
|
|
|
+ leveraged_positions = 0
|
|
|
+ total_positions = len(positions)
|
|
|
+
|
|
|
+ for position in positions:
|
|
|
+ if position.leverage > 1.1: # Consider leverage > 1.1 as perps
|
|
|
+ leveraged_positions += 1
|
|
|
+
|
|
|
+ # If we have positions, determine based on leverage
|
|
|
+ if total_positions > 0:
|
|
|
+ leverage_ratio = leveraged_positions / total_positions
|
|
|
+
|
|
|
+ if leverage_ratio >= 0.8: # 80%+ leveraged positions = perps
|
|
|
+ return "perps"
|
|
|
+ elif leverage_ratio <= 0.2: # 20%- leveraged positions = spot
|
|
|
+ return "spot"
|
|
|
+ else: # Mixed
|
|
|
+ return "mixed"
|
|
|
+
|
|
|
+ # If no current positions, check historical leverage patterns
|
|
|
+ # For Hyperliquid, most trading is perps, so default to perps
|
|
|
+ # We could also check if trades show signs of leverage (frequent short selling, etc.)
|
|
|
+
|
|
|
+ # Check for short selling patterns (indicator of perps)
|
|
|
+ total_trades = len(trades)
|
|
|
+ if total_trades > 0:
|
|
|
+ sell_trades = sum(1 for trade in trades if trade.side == 'sell')
|
|
|
+ buy_trades = total_trades - sell_trades
|
|
|
+
|
|
|
+ # If significantly more sells than buys, likely includes short selling (perps)
|
|
|
+ if sell_trades > buy_trades * 1.2:
|
|
|
+ return "perps"
|
|
|
+ # If roughly balanced, could be perps with both long/short
|
|
|
+ elif abs(sell_trades - buy_trades) / total_trades < 0.3:
|
|
|
+ return "perps"
|
|
|
+
|
|
|
+ # Default to perps for Hyperliquid (they primarily offer perps)
|
|
|
+ return "perps"
|
|
|
+
|
|
|
+ def analyze_short_long_patterns(self, trades: List[Trade]) -> Dict[str, Any]:
|
|
|
+ """
|
|
|
+ Analyze short/long trading patterns for perpetual traders
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ dict: Analysis of directional trading patterns
|
|
|
+ """
|
|
|
+ if not trades:
|
|
|
+ return {
|
|
|
+ 'total_buys': 0,
|
|
|
+ 'total_sells': 0,
|
|
|
+ 'buy_sell_ratio': 0,
|
|
|
+ 'likely_short_trades': 0,
|
|
|
+ 'directional_balance': 'unknown',
|
|
|
+ 'trading_style': 'unknown'
|
|
|
+ }
|
|
|
+
|
|
|
+ total_buys = sum(1 for trade in trades if trade.side == 'buy')
|
|
|
+ total_sells = sum(1 for trade in trades if trade.side == 'sell')
|
|
|
+ total_trades = len(trades)
|
|
|
+
|
|
|
+ buy_sell_ratio = total_buys / max(1, total_sells)
|
|
|
+
|
|
|
+ # Analyze trading patterns
|
|
|
+ if abs(total_buys - total_sells) / total_trades < 0.1: # Within 10%
|
|
|
+ directional_balance = "balanced"
|
|
|
+ trading_style = "Long/Short Balanced (can profit both ways)"
|
|
|
+ elif total_sells > total_buys * 1.3: # 30% more sells
|
|
|
+ directional_balance = "sell_heavy"
|
|
|
+ trading_style = "Short-Heavy (profits from price drops)"
|
|
|
+ elif total_buys > total_sells * 1.3: # 30% more buys
|
|
|
+ directional_balance = "buy_heavy"
|
|
|
+ trading_style = "Long-Heavy (profits from price rises)"
|
|
|
+ else:
|
|
|
+ directional_balance = "moderately_balanced"
|
|
|
+ trading_style = "Moderately Balanced (flexible direction)"
|
|
|
+
|
|
|
+ # Estimate likely short positions (sells without preceding buys)
|
|
|
+ likely_shorts = 0
|
|
|
+ position_tracker = defaultdict(lambda: {'net_position': 0})
|
|
|
+
|
|
|
+ for trade in sorted(trades, key=lambda x: x.timestamp):
|
|
|
+ coin_pos = position_tracker[trade.coin]
|
|
|
+
|
|
|
+ if trade.side == 'sell':
|
|
|
+ if coin_pos['net_position'] <= 0: # Selling without long position = likely short
|
|
|
+ likely_shorts += 1
|
|
|
+ coin_pos['net_position'] -= trade.size
|
|
|
+ else: # buy
|
|
|
+ coin_pos['net_position'] += trade.size
|
|
|
+
|
|
|
+ return {
|
|
|
+ 'total_buys': total_buys,
|
|
|
+ 'total_sells': total_sells,
|
|
|
+ 'buy_sell_ratio': buy_sell_ratio,
|
|
|
+ 'likely_short_trades': likely_shorts,
|
|
|
+ 'short_percentage': (likely_shorts / total_trades * 100) if total_trades > 0 else 0,
|
|
|
+ 'directional_balance': directional_balance,
|
|
|
+ 'trading_style': trading_style
|
|
|
+ }
|
|
|
+
|
|
|
async def analyze_account(self, address: str) -> Optional[AccountStats]:
|
|
|
"""Analyze a single account and return comprehensive statistics"""
|
|
|
print(f"\n🔍 Analyzing account: {address}")
|
|
@@ -617,6 +763,12 @@ class HyperliquidAccountAnalyzer:
|
|
|
# Fallback if variables don't exist
|
|
|
risk_reward_ratio = win_rate / (1 - win_rate) if win_rate < 1 else 1.0
|
|
|
|
|
|
+ # Analyze token diversity and trading type
|
|
|
+ unique_tokens, trading_type, top_tokens = self.analyze_token_diversity_and_type(trades, positions)
|
|
|
+
|
|
|
+ # Analyze short/long patterns
|
|
|
+ short_long_analysis = self.analyze_short_long_patterns(trades)
|
|
|
+
|
|
|
return AccountStats(
|
|
|
address=address,
|
|
|
total_pnl=total_pnl,
|
|
@@ -639,7 +791,13 @@ class HyperliquidAccountAnalyzer:
|
|
|
last_trade_timestamp=newest_trade,
|
|
|
analysis_period_days=int(analysis_period_days),
|
|
|
is_copyable=is_copyable,
|
|
|
- copyability_reason=copyability_reason
|
|
|
+ copyability_reason=copyability_reason,
|
|
|
+ unique_tokens_traded=unique_tokens,
|
|
|
+ trading_type=trading_type,
|
|
|
+ top_tokens=top_tokens,
|
|
|
+ short_percentage=short_long_analysis['short_percentage'],
|
|
|
+ trading_style=short_long_analysis['trading_style'],
|
|
|
+ buy_sell_ratio=short_long_analysis['buy_sell_ratio']
|
|
|
)
|
|
|
|
|
|
async def analyze_multiple_accounts(self, addresses: List[str]) -> List[AccountStats]:
|
|
@@ -836,6 +994,30 @@ class HyperliquidAccountAnalyzer:
|
|
|
print(f" 📍 Active Positions: {stats.active_positions}")
|
|
|
print(f" 📅 Analysis Period: {stats.analysis_period_days} days")
|
|
|
|
|
|
+ # New token and trading type information
|
|
|
+ print(f" 🪙 Unique Tokens: {stats.unique_tokens_traded}")
|
|
|
+
|
|
|
+ # Trading type with emoji
|
|
|
+ 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}")
|
|
|
+
|
|
|
+ # Short/Long patterns - KEY ADVANTAGE
|
|
|
+ print(f" 📊 Trading Style: {stats.trading_style}")
|
|
|
+ print(f" 📉 Short Trades: {stats.short_percentage:.1f}% (can profit from price drops)")
|
|
|
+ print(f" ⚖️ Buy/Sell Ratio: {stats.buy_sell_ratio:.2f}")
|
|
|
+
|
|
|
+ # Top tokens
|
|
|
+ if stats.top_tokens:
|
|
|
+ top_tokens_str = ", ".join(stats.top_tokens[:3]) # Show top 3
|
|
|
+ if len(stats.top_tokens) > 3:
|
|
|
+ top_tokens_str += f" +{len(stats.top_tokens)-3} more"
|
|
|
+ print(f" 🏆 Top Tokens: {top_tokens_str}")
|
|
|
+
|
|
|
# Copy Trading Suitability Evaluation
|
|
|
evaluation = []
|
|
|
is_hft_pattern = stats.trading_frequency_per_day > 50
|
|
@@ -947,6 +1129,18 @@ class HyperliquidAccountAnalyzer:
|
|
|
print(f" • Start with small allocation (5%) and increase gradually")
|
|
|
print(f" • Monitor performance and adjust leverage accordingly")
|
|
|
print(f" • Higher relative scores indicate better performance within this cohort")
|
|
|
+ print(f" • 🔄 ADVANTAGE: Perpetual traders can profit in BOTH bull & bear markets!")
|
|
|
+ print(f" • 📈📉 They go long (profit when price rises) AND short (profit when price falls)")
|
|
|
+ print(f" • 💡 This means potential profits in any market condition")
|
|
|
+
|
|
|
+ # Show directional trading summary
|
|
|
+ if copyable_accounts:
|
|
|
+ print(f"\n🎯 DIRECTIONAL TRADING ANALYSIS OF COPYABLE ACCOUNTS:")
|
|
|
+ for i, (stats, score, breakdown) in enumerate(copyable_accounts, 1):
|
|
|
+ short_capability = "✅ Excellent" if stats.short_percentage > 30 else "⚠️ Limited" if stats.short_percentage > 10 else "❌ Minimal"
|
|
|
+ print(f" {i}. {stats.address[:10]}... - {stats.short_percentage:.1f}% shorts ({short_capability} short capability)")
|
|
|
+ print(f" Style: {stats.trading_style}")
|
|
|
+ print(f" Advantage: Can profit when {', '.join(stats.top_tokens[:2])} prices move in EITHER direction")
|
|
|
|
|
|
async def get_leaderboard(self, window: str = "7d", limit: int = 20) -> Optional[List[str]]:
|
|
|
"""
|