institutional-trader/README/CORE_LOGIC_AND_UI_IMPROVEME...

18 KiB

Core Logic & UI Improvements for Trading Insights

MVP Focus: Making Your Signals More Actionable


🎯 What You're Building

A system that identifies institutional options flow and converts it into actionable trade signals with:

  • Entry/Stop/Target levels
  • Confidence scores
  • Flow trend detection (SURGING/FADING/STABLE)
  • Badge-based pattern recognition

PART 1: CORE LOGIC IMPROVEMENTS

1.1 Enhance Signal Accuracy

Current Issue: Some signals might be too generic or miss edge cases.

Improvement: Add More Context to Signal Generation

// backend/src/services/tradeSignalGenerator.js

// ADD: Moneyness context
function getMoneynessContext(row) {
  const strike = row.strike_num || 0;
  const spot = row.spot_num || 0;
  const cp = row.cp_norm || '';
  
  if (!strike || !spot) return 'UNKNOWN';
  
  if (cp === 'CALL') {
    const distance = ((strike - spot) / spot) * 100;
    if (distance < -2) return 'DEEP_ITM';
    if (distance < 0) return 'ITM';
    if (distance < 5) return 'NEAR_ATM';
    if (distance < 10) return 'OTM';
    return 'DEEP_OTM';
  } else {
    const distance = ((spot - strike) / spot) * 100;
    if (distance < -2) return 'DEEP_ITM';
    if (distance < 0) return 'ITM';
    if (distance < 5) return 'NEAR_ATM';
    if (distance < 10) return 'OTM';
    return 'DEEP_OTM';
  }
}

// ENHANCE: Pattern 1 with moneyness check
if (round === '🟢' && hasDiamond && hasStar && hasMoney && hasFlash) {
  const moneyness = getMoneynessContext(row);
  
  // DEEP_OTM calls with full badges = gamma squeeze setup
  if (moneyness === 'DEEP_OTM' && row.cp_norm === 'CALL') {
    return {
      signal: 'BUY',
      type: 'GAMMA_SQUEEZE_SETUP',
      confidence: Math.min(calculateConfidence(row, 'BUY') + 10, 95), // Boost confidence
      entry: generateEntryStrategy(row, 'BUY'),
      stop: generateStopLoss(row, 'BUY'),
      target: generateTarget(row, 'BUY', 'GAMMA'), // Special gamma target
      reasoning: 'Gamma squeeze setup: Deep OTM calls + full institutional flow',
      timeHorizon: 'INTRADAY',
      invalidation: generateInvalidation(row, 'BUY')
    };
  }
  
  // Regular institutional FOMO
  return {
    signal: 'BUY',
    type: 'INSTITUTIONAL_FOMO',
    confidence: calculateConfidence(row, 'BUY'),
    // ... rest
  };
}

Why: Deep OTM calls with full badges are often gamma squeeze setups - this deserves special handling.


1.2 Improve Flow Trend Detection

Current Issue: Flow trend might not account for volume spikes.

Improvement: Add Volume Context

// backend/src/services/flowTrendDetector.js

export function detectFlowTrend(flows) {
  // ... existing code ...
  
  // ADD: Volume spike detection
  const recentVolumes = flows.slice(0, 5).map(f => f.vol_num || 0);
  const avgVolume = recentVolumes.reduce((a, b) => a + b, 0) / recentVolumes.length;
  const currentVolume = recentVolumes[0] || 0;
  const volumeSpike = currentVolume > avgVolume * 2; // 2x average = spike
  
  // ENHANCE: SURGING with volume spike = stronger signal
  if (trend === 'SURGING' && volumeSpike) {
    status = '🔥🔥 SURGING (VOLUME SPIKE)';
    message = 'Net premium increasing + volume spike = institutions aggressively buying';
    // Boost confidence in signal generation
  }
  
  return {
    trend,
    status,
    volumeSpike, // NEW
    volumeRatio: avgVolume > 0 ? currentVolume / avgVolume : 1, // NEW
    // ... rest
  };
}

Why: Volume spikes during surging flow = stronger institutional conviction.


1.3 Add Strike Clustering Detection

New Insight: When multiple strikes show flow, it's more significant.

// backend/src/services/strikeClusterDetector.js

export function detectStrikeClusters(flows) {
  // Group flows by symbol and expiration
  const bySymbolExp = {};
  flows.forEach(flow => {
    const key = `${flow.symbol_norm}_${flow.exp_date}`;
    if (!bySymbolExp[key]) {
      bySymbolExp[key] = [];
    }
    bySymbolExp[key].push(flow);
  });
  
  const clusters = {};
  
  for (const [key, symbolFlows] of Object.entries(bySymbolExp)) {
    // Group by strike (within 2% of each other)
    const strikeGroups = {};
    symbolFlows.forEach(flow => {
      const strike = flow.strike_num || 0;
      const spot = flow.spot_num || 0;
      if (!strike || !spot) return;
      
      // Find existing group within 2%
      let found = false;
      for (const [groupStrike, group] of Object.entries(strikeGroups)) {
        const groupStrikeNum = parseFloat(groupStrike);
        const distance = Math.abs(strike - groupStrikeNum) / spot;
        if (distance < 0.02) {
          group.push(flow);
          found = true;
          break;
        }
      }
      
      if (!found) {
        strikeGroups[strike] = [flow];
      }
    });
    
    // Find clusters (3+ flows at similar strikes)
    for (const [strike, group] of Object.entries(strikeGroups)) {
      if (group.length >= 3) {
        const totalPremium = group.reduce((sum, f) => sum + (f.premium_num || 0), 0);
        clusters[key] = {
          strike: parseFloat(strike),
          flowCount: group.length,
          totalPremium,
          direction: group[0].direction,
          avgPremium: totalPremium / group.length
        };
      }
    }
  }
  
  return clusters;
}

Why: Strike clustering = institutions building positions at specific levels = stronger signal.


1.4 Enhance Confidence Calculation

Current: Confidence is good but could use more factors.

Improvement:

// backend/src/services/tradeSignalGenerator.js

function calculateConfidence(row, direction) {
  let confidence = 50;
  
  // ... existing factors ...
  
  // ADD: Strike clustering bonus
  const strikeCluster = row.strikeCluster;
  if (strikeCluster && strikeCluster.flowCount >= 3) {
    confidence += 5; // Multiple strikes = stronger conviction
  }
  
  // ADD: Expiration proximity (0DTE = higher risk/reward)
  const expDate = new Date(row.exp_date || row.ExpirationDate);
  const now = new Date();
  const daysToExp = Math.floor((expDate - now) / (1000 * 60 * 60 * 24));
  if (daysToExp === 0) {
    confidence += 3; // 0DTE = institutions betting on immediate move
  } else if (daysToExp <= 7) {
    confidence += 2; // Weekly = short-term conviction
  }
  
  // ADD: Session timing (RTH = better than PRE/POST)
  if (row.session_bucket === 'RTH') {
    confidence += 2; // Regular hours = more liquidity
  }
  
  // ADD: Volume vs OI ratio (higher = more aggressive)
  const volOIRatio = row.oi_num > 0 ? (row.vol_num || 0) / row.oi_num : 0;
  if (volOIRatio > 2) {
    confidence += 3; // High volume vs OI = aggressive positioning
  }
  
  return Math.max(5, Math.min(Math.round(confidence), 95));
}

PART 2: UI IMPROVEMENTS FOR ACTIONABLE INSIGHTS

2.1 Make Trade Signals More Prominent

Current: Signals are in a column, but could be more actionable.

Improvement: Signal Card Component

// frontend/src/components/tables/TradeSignalCard.jsx

export function TradeSignalCard({ signal, row }) {
  if (!signal || signal.signal === 'NEUTRAL') return null;
  
  const isBuy = signal.signal === 'BUY';
  const confidenceColor = signal.confidence >= 70 ? 'green' : 
                          signal.confidence >= 50 ? 'yellow' : 'red';
  
  return (
    <div className={`border-2 rounded-lg p-3 ${
      isBuy ? 'border-green-500/50 bg-green-500/10' : 'border-red-500/50 bg-red-500/10'
    }`}>
      {/* Signal Header */}
      <div className="flex items-center justify-between mb-2">
        <div className="flex items-center gap-2">
          <span className="text-2xl">{isBuy ? '🟢' : '🔴'}</span>
          <span className="font-bold text-lg">{signal.signal}</span>
          <Badge className={`bg-${confidenceColor}-500/20 text-${confidenceColor}-400`}>
            {signal.confidence}% confidence
          </Badge>
        </div>
        <Badge variant="outline">{signal.type}</Badge>
      </div>
      
      {/* Entry/Stop/Target Grid */}
      <div className="grid grid-cols-3 gap-2 text-sm">
        <div>
          <div className="text-slate-400 text-xs">Entry</div>
          <div className="text-green-400 font-mono">
            {signal.entry?.primary || '—'}
          </div>
        </div>
        <div>
          <div className="text-slate-400 text-xs">Stop</div>
          <div className="text-red-400 font-mono">
            {signal.stop?.tight || '—'}
          </div>
        </div>
        <div>
          <div className="text-slate-400 text-xs">Target</div>
          <div className="text-blue-400 font-mono">
            {signal.target?.t1 || '—'}
          </div>
        </div>
      </div>
      
      {/* Reasoning */}
      <div className="mt-2 text-xs text-slate-400 italic">
        {signal.reasoning}
      </div>
      
      {/* Invalidation Conditions */}
      {signal.invalidation && (
        <details className="mt-2">
          <summary className="text-xs text-yellow-400 cursor-pointer">
            ⚠️ Invalidation Conditions
          </summary>
          <ul className="text-xs text-slate-400 mt-1 list-disc list-inside">
            {Array.isArray(signal.invalidation) 
              ? signal.invalidation.map((cond, i) => <li key={i}>{cond}</li>)
              : <li>{signal.invalidation}</li>
            }
          </ul>
        </details>
      )}
    </div>
  );
}

Usage in table:

{
  accessorKey: 'TradeSignal',
  header: 'Trade Signal',
  cell: ({ row }) => <TradeSignalCard signal={row.original.tradeSignal} row={row.original} />
}

2.2 Add Flow Trend Visualization

Current: Text only. Add visual indicator.

// frontend/src/components/tables/FlowTrendIndicator.jsx

export function FlowTrendIndicator({ trend }) {
  if (!trend) return null;
  
  const trendData = trend.full || trend;
  const trendType = trendData.trend || 'STABLE';
  
  // Progress bar showing trend strength
  const getTrendStrength = () => {
    if (trendType === 'SURGING') {
      const change = trendData.netPremiumChange || 0;
      return Math.min(Math.abs(change) / 1000000 * 100, 100); // Scale to 1M
    }
    if (trendType === 'FADING') {
      const change = Math.abs(trendData.netPremiumChange || 0);
      return Math.min(change / 1000000 * 100, 100);
    }
    return 50; // STABLE = middle
  };
  
  const strength = getTrendStrength();
  const color = trendType === 'SURGING' ? 'green' : 
                trendType === 'FADING' ? 'red' : 'yellow';
  
  return (
    <div className="space-y-1">
      <div className="flex items-center gap-2">
        <span className="text-lg">{trendData.status || '🟡 STABLE'}</span>
        {trendData.volumeSpike && (
          <Badge className="bg-orange-500/20 text-orange-400 text-xs">
            VOL SPIKE
          </Badge>
        )}
      </div>
      
      {/* Progress bar */}
      <div className="w-full bg-slate-700 rounded-full h-2">
        <div 
          className={`bg-${color}-500 h-2 rounded-full transition-all`}
          style={{ width: `${strength}%` }}
        />
      </div>
      
      {/* Net premium change */}
      {trendData.netPremiumChange !== undefined && (
        <div className={`text-xs ${trendData.netPremiumChange > 0 ? 'text-green-400' : 'text-red-400'}`}>
          {trendData.netPremiumChange > 0 ? '+' : ''}
          ${(trendData.netPremiumChange / 1000).toFixed(0)}K change
        </div>
      )}
    </div>
  );
}

2.3 Add Quick Action Buttons

Make signals actionable with one click:

// frontend/src/components/tables/ActionButtons.jsx

export function ActionButtons({ row }) {
  const signal = row.tradeSignal;
  const symbol = row.Symbol || row.symbol_norm;
  
  if (!signal || signal.signal === 'NEUTRAL') return null;
  
  const handleQuickEntry = () => {
    // Copy entry price to clipboard or open broker
    const entryPrice = signal.entry?.primary?.match(/\$?([\d.]+)/)?.[1];
    if (entryPrice) {
      navigator.clipboard.writeText(`${symbol} @ $${entryPrice}`);
      // Or open broker link
      window.open(`https://your-broker.com/order?symbol=${symbol}&price=${entryPrice}`);
    }
  };
  
  return (
    <div className="flex gap-1">
      <Button 
        size="sm" 
        variant={signal.signal === 'BUY' ? 'success' : 'destructive'}
        onClick={handleQuickEntry}
        className="text-xs"
      >
        Quick Entry
      </Button>
      <Button 
        size="sm" 
        variant="outline"
        onClick={() => {
          // Show full trade plan
          // Could open modal or navigate to detail page
        }}
        className="text-xs"
      >
        Full Plan
      </Button>
    </div>
  );
}

2.4 Add Filter by Signal Type

Make it easy to find specific patterns:

// frontend/src/components/dashboard/OptionsFlowPanel.jsx

// Add to quick filters
const signalTypeFilters = [
  { key: 'INSTITUTIONAL_FOMO', label: '🔥 Institutional FOMO', icon: '🔥' },
  { key: 'GAMMA_SQUEEZE_SETUP', label: '⚡ Gamma Squeeze', icon: '⚡' },
  { key: 'INSTITUTIONAL_DISTRIBUTION', label: '🔴 Distribution', icon: '🔴' },
  { key: 'SLOW_ACCUMULATION', label: '📈 Slow Accumulation', icon: '📈' },
  { key: 'TRAP_WARNING', label: '⚠️ Trap Warning', icon: '⚠️' }
];

// In filteredData useMemo
if (quickFilters.has('INSTITUTIONAL_FOMO')) {
  filtered = filtered.filter(row => 
    row.tradeSignal?.type === 'INSTITUTIONAL_FOMO'
  );
}
// ... etc for other signal types

PART 3: MISSING INSIGHTS TO ADD

3.1 Add "Flow Momentum Score"

New metric: How strong is the flow momentum right now?

// backend/src/services/momentumScore.js

export function calculateFlowMomentum(row, trendData) {
  let score = 0;
  
  // Base: Rocket score (0-30 points)
  score += (row.rocketScore || 0) * 3;
  
  // Trend contribution (0-25 points)
  if (trendData?.trend === 'SURGING') {
    score += 25;
  } else if (trendData?.trend === 'STABLE') {
    score += 10;
  } else if (trendData?.trend === 'FADING') {
    score -= 15;
  }
  
  // Volume spike (0-15 points)
  if (trendData?.volumeSpike) {
    score += 15;
  }
  
  // Tape alignment (0-10 points)
  if (row.tapeAligned) {
    score += 10;
  }
  
  // Catalyst (0-10 points)
  if (row.near_alert_type) {
    score += 10;
  }
  
  // Recency (0-10 points) - fresher = better
  const minutesAgo = trendData?.lastFlowMinutesAgo || 999;
  if (minutesAgo < 5) score += 10;
  else if (minutesAgo < 15) score += 7;
  else if (minutesAgo < 30) score += 4;
  
  return Math.max(0, Math.min(100, Math.round(score)));
}

Display in UI:

{
  accessorKey: 'Momentum',
  header: 'Momentum',
  cell: ({ row }) => {
    const momentum = row.original.momentumScore || 0;
    const color = momentum >= 70 ? 'green' : momentum >= 50 ? 'yellow' : 'red';
    return (
      <div className="flex items-center gap-2">
        <div className="w-16 bg-slate-700 rounded-full h-2">
          <div 
            className={`bg-${color}-500 h-2 rounded-full`}
            style={{ width: `${momentum}%` }}
          />
        </div>
        <span className={`text-${color}-400 font-bold text-sm`}>{momentum}</span>
      </div>
    );
  }
}

3.2 Add "Risk/Reward Ratio"

Show R:R for each signal:

// backend/src/services/tradeSignalGenerator.js

function calculateRiskReward(row, signal) {
  if (!signal.entry || !signal.stop || !signal.target) return null;
  
  const entryPrice = extractPrice(signal.entry.primary);
  const stopPrice = extractPrice(signal.stop.tight);
  const targetPrice = extractPrice(signal.target.t1);
  
  if (!entryPrice || !stopPrice || !targetPrice) return null;
  
  const entry = parseFloat(entryPrice.replace('$', ''));
  const stop = parseFloat(stopPrice.replace('$', ''));
  const target = parseFloat(targetPrice.replace('$', ''));
  
  const risk = Math.abs(entry - stop);
  const reward = Math.abs(target - entry);
  
  if (risk === 0) return null;
  
  return {
    ratio: (reward / risk).toFixed(2),
    risk,
    reward,
    riskPct: ((risk / entry) * 100).toFixed(2),
    rewardPct: ((reward / entry) * 100).toFixed(2)
  };
}

// Add to signal object
signal.riskReward = calculateRiskReward(row, signal);

Display:

{signal.riskReward && (
  <div className="text-xs">
    <span className="text-slate-400">R:R</span>
    <span className="text-green-400 font-bold ml-1">
      {signal.riskReward.ratio}:1
    </span>
  </div>
)}

PART 4: QUICK WINS (Implement First)

1. Add Moneyness Context to Signals (15 min)

  • Helps identify gamma squeeze setups
  • Makes signals more accurate

2. Make Trade Signal Card More Prominent (20 min)

  • Better visual hierarchy
  • Easier to scan for opportunities

3. Add Flow Momentum Score (30 min)

  • Single number to gauge strength
  • Easy to sort/filter by

4. Add Risk/Reward Display (15 min)

  • Helps prioritize trades
  • Essential for risk management

5. Add Signal Type Filters (10 min)

  • Quick access to specific patterns
  • Better workflow

Implementation Order

  1. Moneyness Context → Better signal accuracy
  2. Momentum Score → Quick strength gauge
  3. Signal Card UI → Better visual presentation
  4. Risk/Reward → Better trade selection
  5. Signal Filters → Better workflow

Expected Impact

After these changes:

  • Signals are more accurate (moneyness + clustering)
  • Easier to spot opportunities (better UI)
  • Better trade selection (R:R + momentum)
  • Faster workflow (filters + quick actions)

Total time: ~2 hours for all improvements


Ready to implement? Start with #1 (Moneyness Context) - it's the quickest win with biggest impact!