Feed Hunter portal: dark theme dashboard on localhost:8888
- Web portal with 5 views: dashboard, feed, investigations, sims, status - Enhanced triage with 40+ claim patterns - Position monitor script - Pipeline report generator - Systemd service for portal
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
329
projects/feed-hunter/monitor-positions.py
Executable file
329
projects/feed-hunter/monitor-positions.py
Executable file
@ -0,0 +1,329 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Feed Hunter Position Monitor
|
||||||
|
Checks current prices for active simulation positions and updates P&L
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 monitor-positions.py [--update] [--alert-threshold 0.1]
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
|
# Price sources for different asset types
|
||||||
|
PRICE_SOURCES = {
|
||||||
|
'polymarket': {
|
||||||
|
'url_pattern': r'polymarket\.com.*?(@[\w]+|markets?[\w/-]+)',
|
||||||
|
'api_base': 'https://gamma-api.polymarket.com/markets',
|
||||||
|
'method': 'polymarket_api'
|
||||||
|
},
|
||||||
|
'crypto': {
|
||||||
|
'url_pattern': r'(bitcoin|btc|ethereum|eth|solana|sol|cardano|ada)',
|
||||||
|
'api_base': 'https://api.coingecko.com/api/v3/simple/price',
|
||||||
|
'method': 'coingecko_api'
|
||||||
|
},
|
||||||
|
'stock': {
|
||||||
|
'url_pattern': r'(nasdaq|nyse|stock)',
|
||||||
|
'api_base': 'https://api.example.com/stock', # Placeholder
|
||||||
|
'method': 'stock_api'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def detect_asset_type(position):
|
||||||
|
"""Detect what type of asset this position represents"""
|
||||||
|
asset = position.get('asset', '').lower()
|
||||||
|
source_url = position.get('source_post', '')
|
||||||
|
strategy = position.get('strategy', '').lower()
|
||||||
|
|
||||||
|
# Check if it's a prediction market
|
||||||
|
if 'polymarket' in source_url or 'polymarket' in asset or 'polymarket' in strategy:
|
||||||
|
return 'polymarket'
|
||||||
|
elif 'super bowl' in asset or 'win' in asset or 'seahawks' in asset:
|
||||||
|
return 'polymarket' # Sports bets are usually prediction markets
|
||||||
|
elif any(crypto in asset for crypto in ['btc', 'eth', 'sol', 'bitcoin', 'ethereum', 'solana']):
|
||||||
|
return 'crypto'
|
||||||
|
elif any(stock in asset for stock in ['stock', 'nasdaq', 'nyse', 'spy', 'tsla']):
|
||||||
|
return 'stock'
|
||||||
|
else:
|
||||||
|
return 'unknown'
|
||||||
|
|
||||||
|
def get_polymarket_price(position):
|
||||||
|
"""Get current price from Polymarket API"""
|
||||||
|
try:
|
||||||
|
# Extract market info from source URL or asset name
|
||||||
|
source_url = position.get('source_post', '')
|
||||||
|
asset = position.get('asset', '')
|
||||||
|
|
||||||
|
# For now, return mock price update
|
||||||
|
# In real implementation, would parse URL and call Polymarket API
|
||||||
|
if 'seahawks' in asset.lower() and 'super bowl' in asset.lower():
|
||||||
|
# Mock price movement for Seahawks Super Bowl bet
|
||||||
|
import random
|
||||||
|
# Simulate some price movement around entry price
|
||||||
|
entry_price = position.get('entry_price', 0.68)
|
||||||
|
price_change = random.uniform(-0.05, 0.05)
|
||||||
|
new_price = max(0.01, min(0.99, entry_price + price_change))
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'price': round(new_price, 2),
|
||||||
|
'timestamp': datetime.now(timezone.utc).isoformat(),
|
||||||
|
'source': 'polymarket_mock'
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Polymarket price fetch error: {e}")
|
||||||
|
|
||||||
|
return {'success': False, 'error': 'Price fetch failed'}
|
||||||
|
|
||||||
|
def get_crypto_price(position):
|
||||||
|
"""Get crypto price from CoinGecko"""
|
||||||
|
try:
|
||||||
|
asset = position.get('asset', '').lower()
|
||||||
|
|
||||||
|
# Map asset names to CoinGecko IDs
|
||||||
|
coin_map = {
|
||||||
|
'bitcoin': 'bitcoin',
|
||||||
|
'btc': 'bitcoin',
|
||||||
|
'ethereum': 'ethereum',
|
||||||
|
'eth': 'ethereum',
|
||||||
|
'solana': 'solana',
|
||||||
|
'sol': 'solana'
|
||||||
|
}
|
||||||
|
|
||||||
|
coin_id = None
|
||||||
|
for name, id in coin_map.items():
|
||||||
|
if name in asset:
|
||||||
|
coin_id = id
|
||||||
|
break
|
||||||
|
|
||||||
|
if not coin_id:
|
||||||
|
return {'success': False, 'error': 'Unknown crypto asset'}
|
||||||
|
|
||||||
|
url = f"https://api.coingecko.com/api/v3/simple/price?ids={coin_id}&vs_currencies=usd"
|
||||||
|
with urllib.request.urlopen(url, timeout=10) as response:
|
||||||
|
data = json.loads(response.read().decode())
|
||||||
|
|
||||||
|
price = data[coin_id]['usd']
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'price': price,
|
||||||
|
'timestamp': datetime.now(timezone.utc).isoformat(),
|
||||||
|
'source': 'coingecko'
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Crypto price fetch error: {e}")
|
||||||
|
return {'success': False, 'error': str(e)}
|
||||||
|
|
||||||
|
def get_stock_price(position):
|
||||||
|
"""Get stock price (placeholder - would need real API)"""
|
||||||
|
# Placeholder for stock price API
|
||||||
|
return {'success': False, 'error': 'Stock price API not implemented'}
|
||||||
|
|
||||||
|
def get_current_price(position):
|
||||||
|
"""Get current price for a position based on asset type"""
|
||||||
|
asset_type = detect_asset_type(position)
|
||||||
|
|
||||||
|
if asset_type == 'polymarket':
|
||||||
|
return get_polymarket_price(position)
|
||||||
|
elif asset_type == 'crypto':
|
||||||
|
return get_crypto_price(position)
|
||||||
|
elif asset_type == 'stock':
|
||||||
|
return get_stock_price(position)
|
||||||
|
else:
|
||||||
|
return {'success': False, 'error': f'Unknown asset type: {asset_type}'}
|
||||||
|
|
||||||
|
def calculate_pnl(position, current_price):
|
||||||
|
"""Calculate P&L for a position"""
|
||||||
|
entry_price = position.get('entry_price', 0)
|
||||||
|
quantity = position.get('quantity', 0)
|
||||||
|
size = position.get('size', 0)
|
||||||
|
position_type = position.get('type', 'long')
|
||||||
|
|
||||||
|
if position_type == 'bet':
|
||||||
|
# For prediction market bets (binary outcome)
|
||||||
|
# P&L = quantity * (current_price - entry_price)
|
||||||
|
unrealized_pnl = quantity * (current_price - entry_price)
|
||||||
|
unrealized_pnl_pct = ((current_price / entry_price) - 1) * 100 if entry_price > 0 else 0
|
||||||
|
else:
|
||||||
|
# For regular long positions
|
||||||
|
# P&L = size * (current_price / entry_price - 1)
|
||||||
|
unrealized_pnl = size * (current_price / entry_price - 1) if entry_price > 0 else 0
|
||||||
|
unrealized_pnl_pct = ((current_price / entry_price) - 1) * 100 if entry_price > 0 else 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
'unrealized_pnl': round(unrealized_pnl, 2),
|
||||||
|
'unrealized_pnl_pct': round(unrealized_pnl_pct, 2),
|
||||||
|
'current_price': current_price
|
||||||
|
}
|
||||||
|
|
||||||
|
def check_stop_loss_take_profit(position, current_price):
|
||||||
|
"""Check if position hits stop loss or take profit"""
|
||||||
|
stop_loss = position.get('stop_loss')
|
||||||
|
take_profit = position.get('take_profit')
|
||||||
|
|
||||||
|
triggers = []
|
||||||
|
|
||||||
|
if stop_loss and current_price <= stop_loss:
|
||||||
|
triggers.append({
|
||||||
|
'type': 'stop_loss',
|
||||||
|
'trigger_price': stop_loss,
|
||||||
|
'current_price': current_price,
|
||||||
|
'message': f"STOP LOSS triggered at {current_price} (target: {stop_loss})"
|
||||||
|
})
|
||||||
|
|
||||||
|
if take_profit and current_price >= take_profit:
|
||||||
|
triggers.append({
|
||||||
|
'type': 'take_profit',
|
||||||
|
'trigger_price': take_profit,
|
||||||
|
'current_price': current_price,
|
||||||
|
'message': f"TAKE PROFIT triggered at {current_price} (target: {take_profit})"
|
||||||
|
})
|
||||||
|
|
||||||
|
return triggers
|
||||||
|
|
||||||
|
def load_active_positions():
|
||||||
|
"""Load active positions from JSON file"""
|
||||||
|
positions_file = 'data/simulations/active.json'
|
||||||
|
|
||||||
|
if not os.path.exists(positions_file):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(positions_file, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading positions: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def save_active_positions(data):
|
||||||
|
"""Save updated positions to JSON file"""
|
||||||
|
positions_file = 'data/simulations/active.json'
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(positions_file, 'w') as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error saving positions: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send_alert(position, trigger_info, pnl_info):
|
||||||
|
"""Send alert about position trigger (placeholder)"""
|
||||||
|
print(f"🚨 ALERT: {trigger_info['message']}")
|
||||||
|
print(f" Position: {position['asset'][:50]}...")
|
||||||
|
print(f" P&L: ${pnl_info['unrealized_pnl']:.2f} ({pnl_info['unrealized_pnl_pct']:+.1f}%)")
|
||||||
|
print(f" Strategy: {position.get('strategy', 'Unknown')}")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
def monitor_positions(update=False, alert_threshold=0.1):
|
||||||
|
"""Monitor all active positions"""
|
||||||
|
print("🖤 Feed Hunter - Position Monitor")
|
||||||
|
print(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
# Load active positions
|
||||||
|
data = load_active_positions()
|
||||||
|
if not data:
|
||||||
|
print("❌ No active positions file found")
|
||||||
|
return
|
||||||
|
|
||||||
|
positions = data.get('positions', [])
|
||||||
|
if not positions:
|
||||||
|
print("📭 No active positions to monitor")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"📊 Monitoring {len(positions)} active positions...")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
updated = False
|
||||||
|
alerts_sent = 0
|
||||||
|
|
||||||
|
for i, position in enumerate(positions):
|
||||||
|
pos_id = position.get('id', f'pos_{i}')
|
||||||
|
asset = position.get('asset', 'Unknown')
|
||||||
|
|
||||||
|
print(f"[{i+1}] {asset[:50]}...")
|
||||||
|
print(f" ID: {pos_id}")
|
||||||
|
print(f" Entry: ${position.get('entry_price', 0):.2f}")
|
||||||
|
print(f" Strategy: {position.get('strategy', 'Unknown')}")
|
||||||
|
|
||||||
|
# Get current price
|
||||||
|
price_result = get_current_price(position)
|
||||||
|
|
||||||
|
if price_result['success']:
|
||||||
|
current_price = price_result['price']
|
||||||
|
print(f" Current: ${current_price:.2f} ({price_result['source']})")
|
||||||
|
|
||||||
|
# Calculate P&L
|
||||||
|
pnl_info = calculate_pnl(position, current_price)
|
||||||
|
pnl_color = "💚" if pnl_info['unrealized_pnl'] >= 0 else "❤️"
|
||||||
|
print(f" P&L: {pnl_color} ${pnl_info['unrealized_pnl']:.2f} ({pnl_info['unrealized_pnl_pct']:+.1f}%)")
|
||||||
|
|
||||||
|
# Check stop loss / take profit
|
||||||
|
triggers = check_stop_loss_take_profit(position, current_price)
|
||||||
|
for trigger in triggers:
|
||||||
|
print(f" 🚨 {trigger['message']}")
|
||||||
|
send_alert(position, trigger, pnl_info)
|
||||||
|
alerts_sent += 1
|
||||||
|
|
||||||
|
# Update position if requested
|
||||||
|
if update:
|
||||||
|
position.update(pnl_info)
|
||||||
|
position['last_updated'] = datetime.now(timezone.utc).isoformat()
|
||||||
|
updated = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f" ❌ Price fetch failed: {price_result.get('error', 'Unknown error')}")
|
||||||
|
|
||||||
|
print("")
|
||||||
|
|
||||||
|
# Save updates if any
|
||||||
|
if updated and update:
|
||||||
|
if save_active_positions(data):
|
||||||
|
print("✅ Positions updated and saved")
|
||||||
|
else:
|
||||||
|
print("❌ Failed to save position updates")
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
print("📈 Monitor Summary:")
|
||||||
|
print(f" Positions checked: {len(positions)}")
|
||||||
|
print(f" Alerts sent: {alerts_sent}")
|
||||||
|
if updated:
|
||||||
|
print(f" Updates saved: ✅")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Monitor active simulation positions")
|
||||||
|
parser.add_argument("--update", action="store_true", help="Update positions with current prices")
|
||||||
|
parser.add_argument("--alert-threshold", type=float, default=0.1, help="P&L threshold for alerts (10% default)")
|
||||||
|
parser.add_argument("--interval", type=int, help="Run continuously every N seconds")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if args.interval:
|
||||||
|
print(f"🔄 Starting continuous monitoring every {args.interval} seconds")
|
||||||
|
print("Press Ctrl+C to stop")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
monitor_positions(update=args.update, alert_threshold=args.alert_threshold)
|
||||||
|
print(f"⏱️ Sleeping for {args.interval} seconds...")
|
||||||
|
time.sleep(args.interval)
|
||||||
|
else:
|
||||||
|
monitor_positions(update=args.update, alert_threshold=args.alert_threshold)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n⏹️ Monitor stopped")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Monitor error: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
63
projects/feed-hunter/portal/README.md
Normal file
63
projects/feed-hunter/portal/README.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# Feed Hunter Portal
|
||||||
|
|
||||||
|
Self-contained web dashboard for monitoring the X/Twitter feed intelligence pipeline.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
The portal is already running at **http://localhost:8888/**
|
||||||
|
|
||||||
|
## Views
|
||||||
|
|
||||||
|
- **Dashboard** (`/`) - Overview of active simulations, recent scrapes, signal counts
|
||||||
|
- **Feed** (`/feed`) - Latest scraped posts with triage status (color-coded)
|
||||||
|
- **Investigations** (`/investigations`) - Detailed investigation reports
|
||||||
|
- **Simulations** (`/simulations`) - Active paper positions, P&L, trade history
|
||||||
|
- **Status** (`/status`) - Pipeline health, last run times, Chrome status
|
||||||
|
|
||||||
|
## Management
|
||||||
|
|
||||||
|
**Manual Control:**
|
||||||
|
```bash
|
||||||
|
# Start portal
|
||||||
|
cd portal && python3 server.py
|
||||||
|
|
||||||
|
# Stop portal
|
||||||
|
pkill -f "python3 server.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Systemd Service:**
|
||||||
|
```bash
|
||||||
|
# Check status
|
||||||
|
systemctl --user status feed-hunter-portal
|
||||||
|
|
||||||
|
# Start/stop/restart
|
||||||
|
systemctl --user start feed-hunter-portal
|
||||||
|
systemctl --user stop feed-hunter-portal
|
||||||
|
systemctl --user restart feed-hunter-portal
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
journalctl --user -u feed-hunter-portal -f
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Dark theme** optimized for monitoring
|
||||||
|
- **Mobile-friendly** responsive design
|
||||||
|
- **Real-time updates** every 30 seconds on dashboard
|
||||||
|
- **Color-coded** triage status (High Value = green, Investigate = orange, etc.)
|
||||||
|
- **P&L tracking** with profit/loss indicators
|
||||||
|
- **No external dependencies** - uses Python stdlib only
|
||||||
|
|
||||||
|
## Data Sources
|
||||||
|
|
||||||
|
The portal reads from:
|
||||||
|
- `../data/x-feed/*/posts.json` - Scraped Twitter posts
|
||||||
|
- `../data/x-feed/*/triage.json` - Triage results
|
||||||
|
- `../data/investigations/*.json` - Investigation reports
|
||||||
|
- `../data/simulations/active.json` - Active paper positions
|
||||||
|
- `../data/simulations/history.json` - Closed trades
|
||||||
|
- `../config.json` - Pipeline configuration
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Single file architecture in `server.py` with embedded CSS/JS. Easy to modify and extend.
|
||||||
BIN
projects/feed-hunter/portal/__pycache__/server.cpython-312.pyc
Normal file
BIN
projects/feed-hunter/portal/__pycache__/server.cpython-312.pyc
Normal file
Binary file not shown.
0
projects/feed-hunter/portal/portal.log
Normal file
0
projects/feed-hunter/portal/portal.log
Normal file
1263
projects/feed-hunter/portal/server.py
Normal file
1263
projects/feed-hunter/portal/server.py
Normal file
File diff suppressed because it is too large
Load Diff
333
projects/feed-hunter/run-and-report.sh
Executable file
333
projects/feed-hunter/run-and-report.sh
Executable file
@ -0,0 +1,333 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Feed Hunter — Full pipeline with HTML reporting
|
||||||
|
# Usage: ./run-and-report.sh [scroll_pages] [output_dir]
|
||||||
|
#
|
||||||
|
# Runs: scrape → triage → investigate → simulate → generate HTML report
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PAGES=${1:-8}
|
||||||
|
OUTPUT_DIR=${2:-"reports/$(date +%Y%m%d-%H%M%S)"}
|
||||||
|
BASE="/home/wdjones/.openclaw/workspace"
|
||||||
|
SKILL="$BASE/skills/deep-scraper/scripts"
|
||||||
|
PROJECT="$BASE/projects/feed-hunter"
|
||||||
|
DATA="$BASE/data/x-feed"
|
||||||
|
|
||||||
|
echo "=== Feed Hunter Pipeline + Report ==="
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S %Z')"
|
||||||
|
echo "Output: $OUTPUT_DIR"
|
||||||
|
|
||||||
|
# Create output directory
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
# Ensure Chrome is running with debug port
|
||||||
|
if ! curl -s http://127.0.0.1:9222/json >/dev/null 2>&1; then
|
||||||
|
echo "Starting Chrome..."
|
||||||
|
bash "$SKILL/launch-chrome-debug.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stage 1: Scrape
|
||||||
|
echo ""
|
||||||
|
echo "--- Stage 1: Scrape ($PAGES pages) ---"
|
||||||
|
python3 -u "$SKILL/scrape-x-feed.py" --port 9222 --scroll-pages "$PAGES" 2>&1 | tee "$OUTPUT_DIR/scrape.log"
|
||||||
|
|
||||||
|
# Find latest scrape
|
||||||
|
LATEST=$(ls -dt "$DATA"/20* | head -1)
|
||||||
|
echo "Latest scrape: $LATEST"
|
||||||
|
|
||||||
|
# Stage 2: Triage
|
||||||
|
echo ""
|
||||||
|
echo "--- Stage 2: Triage ---"
|
||||||
|
python3 "$SKILL/triage-posts.py" "$LATEST/posts.json" 2>&1 | tee "$OUTPUT_DIR/triage.log"
|
||||||
|
|
||||||
|
# Stage 3: Generate investigation tasks
|
||||||
|
TRIAGE="$LATEST/triage.json"
|
||||||
|
INVESTIGATION_COUNT=0
|
||||||
|
if [ -f "$TRIAGE" ]; then
|
||||||
|
INVESTIGATION_COUNT=$(python3 -c "import json; d=json.load(open('$TRIAGE')); print(len(d.get('investigation_queue',[])))")
|
||||||
|
if [ "$INVESTIGATION_COUNT" -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "--- Stage 3: Investigation Tasks ---"
|
||||||
|
python3 "$PROJECT/investigate.py" "$TRIAGE" --output "$LATEST/investigations" 2>&1 | tee "$OUTPUT_DIR/investigate.log"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo ">>> No posts worth investigating this run."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo ">>> No triage output found."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stage 4: Update simulations if new investigations found
|
||||||
|
SIMULATION_UPDATES=0
|
||||||
|
if [ -f "$PROJECT/data/investigations" ] && [ "$INVESTIGATION_COUNT" -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "--- Stage 4: Simulation Updates ---"
|
||||||
|
python3 "$PROJECT/simulator.py" --check-investigations 2>&1 | tee "$OUTPUT_DIR/simulation.log" || true
|
||||||
|
SIMULATION_UPDATES=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stage 5: Generate HTML Report
|
||||||
|
echo ""
|
||||||
|
echo "--- Stage 5: Generate Report ---"
|
||||||
|
|
||||||
|
# Get summary stats
|
||||||
|
POSTS_COUNT=$(python3 -c "import json; d=json.load(open('$LATEST/posts.json')); print(len(d.get('posts',[])))")
|
||||||
|
HIGH_VALUE=0
|
||||||
|
WORTH_INVESTIGATING=0
|
||||||
|
DISMISSED=0
|
||||||
|
|
||||||
|
if [ -f "$TRIAGE" ]; then
|
||||||
|
HIGH_VALUE=$(python3 -c "import json; d=json.load(open('$TRIAGE')); print(len(d.get('high_value',[])))")
|
||||||
|
WORTH_INVESTIGATING=$(python3 -c "import json; d=json.load(open('$TRIAGE')); print(len(d.get('worth_investigating',[])))")
|
||||||
|
DISMISSED=$(python3 -c "import json; d=json.load(open('$TRIAGE')); print(len(d.get('dismissed',[])))")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get investigation results
|
||||||
|
VERIFIED_CLAIMS=0
|
||||||
|
ACTIONABLE_STRATEGIES=0
|
||||||
|
if [ -d "$LATEST/investigations" ]; then
|
||||||
|
VERIFIED_CLAIMS=$(find "$LATEST/investigations" -name "*.json" -exec grep -l '"verdict".*"VERIFIED"' {} \; | wc -l)
|
||||||
|
ACTIONABLE_STRATEGIES=$(find "$LATEST/investigations" -name "*.json" -exec grep -l '"actionable".*true' {} \; | wc -l)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get simulation status
|
||||||
|
ACTIVE_POSITIONS=0
|
||||||
|
TOTAL_PNL=0
|
||||||
|
if [ -f "$PROJECT/data/simulations/active.json" ]; then
|
||||||
|
ACTIVE_POSITIONS=$(python3 -c "import json; d=json.load(open('$PROJECT/data/simulations/active.json')); print(len(d.get('positions',[])))" 2>/dev/null || echo "0")
|
||||||
|
TOTAL_PNL=$(python3 -c "import json; d=json.load(open('$PROJECT/data/simulations/active.json')); print(sum(p.get('unrealized_pnl',0) for p in d.get('positions',[])))" 2>/dev/null || echo "0")
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > "$OUTPUT_DIR/report.html" << EOF
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Feed Hunter Report - $(date '+%Y-%m-%d %H:%M')</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: #0d1117;
|
||||||
|
color: #f0f6fc;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 2px solid #30363d;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
color: #58a6ff;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.header .subtitle {
|
||||||
|
color: #8b949e;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
.metrics {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
.metric-card {
|
||||||
|
background: #161b22;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.metric-value {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.metric-label {
|
||||||
|
color: #8b949e;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
.positive { color: #3fb950; }
|
||||||
|
.negative { color: #f85149; }
|
||||||
|
.neutral { color: #58a6ff; }
|
||||||
|
.section {
|
||||||
|
background: #161b22;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.section h2 {
|
||||||
|
color: #f0f6fc;
|
||||||
|
border-bottom: 1px solid #30363d;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.status-good {
|
||||||
|
color: #3fb950;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.status-warn {
|
||||||
|
color: #ffab40;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.status-error {
|
||||||
|
color: #f85149;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.log-snippet {
|
||||||
|
background: #0d1117;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1rem;
|
||||||
|
font-family: 'Monaco', 'Menlo', monospace;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #8b949e;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 2rem;
|
||||||
|
border-top: 1px solid #30363d;
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
.link-button {
|
||||||
|
display: inline-block;
|
||||||
|
background: #58a6ff;
|
||||||
|
color: #fff;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0.5rem 0.5rem 0.5rem 0;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.link-button:hover {
|
||||||
|
background: #4493e1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🖤 Feed Hunter Report</h1>
|
||||||
|
<div class="subtitle">Pipeline execution completed at $(date '+%Y-%m-%d %H:%M:%S %Z')</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value neutral">$POSTS_COUNT</div>
|
||||||
|
<div class="metric-label">Posts Scraped</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value $([ $HIGH_VALUE -gt 0 ] && echo 'positive' || echo 'neutral')">$HIGH_VALUE</div>
|
||||||
|
<div class="metric-label">High Value</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value $([ $INVESTIGATION_COUNT -gt 0 ] && echo 'positive' || echo 'neutral')">$INVESTIGATION_COUNT</div>
|
||||||
|
<div class="metric-label">Investigated</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value $([ $VERIFIED_CLAIMS -gt 0 ] && echo 'positive' || echo 'neutral')">$VERIFIED_CLAIMS</div>
|
||||||
|
<div class="metric-label">Verified Claims</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value $([ $ACTIVE_POSITIONS -gt 0 ] && echo 'positive' || echo 'neutral')">$ACTIVE_POSITIONS</div>
|
||||||
|
<div class="metric-label">Active Positions</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-value $(python3 -c "print('positive' if $TOTAL_PNL >= 0 else 'negative')")">$${TOTAL_PNL}</div>
|
||||||
|
<div class="metric-label">Total P&L</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>📊 Pipeline Summary</h2>
|
||||||
|
<p><strong>Scraping:</strong> Collected $POSTS_COUNT posts from $PAGES pages</p>
|
||||||
|
<p><strong>Triaging:</strong> $HIGH_VALUE high-value, $WORTH_INVESTIGATING worth investigating, $DISMISSED dismissed</p>
|
||||||
|
<p><strong>Investigation:</strong> $INVESTIGATION_COUNT posts analyzed, $VERIFIED_CLAIMS claims verified</p>
|
||||||
|
<p><strong>Simulations:</strong> $ACTIVE_POSITIONS active positions, \$$TOTAL_PNL unrealized P&L</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>🔍 Investigation Highlights</h2>
|
||||||
|
$(if [ $VERIFIED_CLAIMS -gt 0 ]; then
|
||||||
|
echo "<div class='status-good'>✓ Found $VERIFIED_CLAIMS verified claims worth tracking</div>"
|
||||||
|
else
|
||||||
|
echo "<div class='status-warn'>⚠ No verified claims found in this run</div>"
|
||||||
|
fi)
|
||||||
|
|
||||||
|
$(if [ $ACTIONABLE_STRATEGIES -gt 0 ]; then
|
||||||
|
echo "<div class='status-good'>✓ Identified $ACTIONABLE_STRATEGIES actionable strategies</div>"
|
||||||
|
else
|
||||||
|
echo "<div class='status-warn'>⚠ No actionable strategies identified</div>"
|
||||||
|
fi)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>📈 Simulation Status</h2>
|
||||||
|
$(if [ $ACTIVE_POSITIONS -gt 0 ]; then
|
||||||
|
echo "<div class='status-good'>✓ $ACTIVE_POSITIONS positions actively tracked</div>"
|
||||||
|
echo "<div>Current P&L: <span class='$(python3 -c "print('positive' if $TOTAL_PNL >= 0 else 'negative')")'>\$$TOTAL_PNL</span></div>"
|
||||||
|
else
|
||||||
|
echo "<div class='status-warn'>⚠ No active positions</div>"
|
||||||
|
fi)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>📱 Quick Actions</h2>
|
||||||
|
<a href="http://localhost:8888/" class="link-button">Open Portal Dashboard</a>
|
||||||
|
<a href="http://localhost:8888/feed" class="link-button">View Latest Feed</a>
|
||||||
|
<a href="http://localhost:8888/investigations" class="link-button">Investigation Reports</a>
|
||||||
|
<a href="http://localhost:8888/simulations" class="link-button">Simulation Tracker</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>📋 Execution Logs</h2>
|
||||||
|
|
||||||
|
<h3>Scraping Output</h3>
|
||||||
|
<div class="log-snippet">$(tail -n 10 "$OUTPUT_DIR/scrape.log" 2>/dev/null || echo "No scrape log found")</div>
|
||||||
|
|
||||||
|
<h3>Triage Output</h3>
|
||||||
|
<div class="log-snippet">$(tail -n 10 "$OUTPUT_DIR/triage.log" 2>/dev/null || echo "No triage log found")</div>
|
||||||
|
|
||||||
|
$(if [ $INVESTIGATION_COUNT -gt 0 ]; then
|
||||||
|
echo "<h3>Investigation Output</h3>"
|
||||||
|
echo "<div class='log-snippet'>$(tail -n 10 "$OUTPUT_DIR/investigate.log" 2>/dev/null || echo "No investigation log found")</div>"
|
||||||
|
fi)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="timestamp">
|
||||||
|
Report generated: $(date '+%Y-%m-%d %H:%M:%S %Z')<br>
|
||||||
|
Data directory: $LATEST<br>
|
||||||
|
Report saved: $OUTPUT_DIR/report.html
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Pipeline + Report Complete ==="
|
||||||
|
echo "📊 Dashboard: http://localhost:8888/"
|
||||||
|
echo "📄 Report: $OUTPUT_DIR/report.html"
|
||||||
|
echo "📁 Data: $LATEST"
|
||||||
|
echo ""
|
||||||
|
echo "Summary:"
|
||||||
|
echo " Posts: $POSTS_COUNT | High Value: $HIGH_VALUE | Investigated: $INVESTIGATION_COUNT"
|
||||||
|
echo " Verified: $VERIFIED_CLAIMS | Actionable: $ACTIONABLE_STRATEGIES"
|
||||||
|
echo " Active Positions: $ACTIVE_POSITIONS | P&L: \$$TOTAL_PNL"
|
||||||
@ -16,38 +16,97 @@ from datetime import datetime, timezone
|
|||||||
|
|
||||||
# Patterns that suggest a verifiable claim
|
# Patterns that suggest a verifiable claim
|
||||||
CLAIM_PATTERNS = [
|
CLAIM_PATTERNS = [
|
||||||
|
# Specific dollar amounts and profits
|
||||||
|
(r'\$(\d{1,3}(?:,\d{3})*(?:\.\d{2})?)\s*(profit|gained|made|earned|up|return)', 'dollar_profit'),
|
||||||
|
(r'made\s+\$(\d{1,3}(?:,\d{3})*(?:\.\d{2})?)', 'dollar_profit'),
|
||||||
|
(r'(\d{1,3}(?:,\d{3})*)\s*(k|K)\s*(profit|gained|made|earned|up|return)', 'k_profit'),
|
||||||
|
(r'(\d+(?:\.\d+)?)\s*(million|M)\s+(profit|made|earned|up)', 'million_profit'),
|
||||||
|
|
||||||
# Performance claims
|
# Performance claims
|
||||||
(r'(\d+[\d.]*)\s*%\s*(win|success|profit|return|accuracy|hit rate)', 'performance_claim'),
|
(r'(\d+[\d.]*)\s*%\s*(win|success|profit|return|accuracy|hit rate)', 'performance_claim'),
|
||||||
(r'wins?\s+(\d+[\d.]*)\s*%', 'performance_claim'),
|
(r'wins?\s+(\d+[\d.]*)\s*%', 'performance_claim'),
|
||||||
(r'(\d+[\d.]*)\s*%\s+of the time', 'performance_claim'),
|
(r'(\d+[\d.]*)\s*%\s+of the time', 'performance_claim'),
|
||||||
(r'(\d+[\d.]*)x\s+(return|profit|gain)', 'multiplier_claim'),
|
(r'(\d+[\d.]*)x\s+(return|profit|gain|returns)', 'multiplier_claim'),
|
||||||
|
(r'(\d+[\d.]*)\s*x\s+in\s+(\d+)\s+(day|week|month|year)s?', 'timeframe_multiplier'),
|
||||||
|
|
||||||
|
# Trading track record claims
|
||||||
|
(r'(\d+)\s+(win|profitable)\s+(trade|bet|position)s?\s+in\s+a\s+row', 'streak_claim'),
|
||||||
|
(r'(\d+)\s+out\s+of\s+(\d+)\s+(trade|bet|call)s?\s+(right|correct|profitable)', 'ratio_claim'),
|
||||||
|
(r'(\d+[\d.]*)\s*%\s+accuracy\s+(this|last)\s+(week|month|year)', 'accuracy_claim'),
|
||||||
|
(r'portfolio\s+up\s+(\d+[\d.]*)\s*%', 'portfolio_performance'),
|
||||||
|
|
||||||
# Copy/follow trading
|
# Copy/follow trading
|
||||||
(r'copy(ing|cat)?\s+(trader|user|bet|position|strat)', 'copy_trading'),
|
(r'copy(ing|cat)?\s+(trader|user|bet|position|strat)', 'copy_trading'),
|
||||||
(r'follow\s+(this|my|their)\s+(trade|bet|position|strat)', 'copy_trading'),
|
(r'follow\s+(this|my|their)\s+(trade|bet|position|strat)', 'copy_trading'),
|
||||||
(r'mirror(ing)?\s+(trade|bet|position)', 'copy_trading'),
|
(r'mirror(ing)?\s+(trade|bet|position)', 'copy_trading'),
|
||||||
|
(r'best\s+(trader|performer)\s+(on|in)\s+\w+', 'top_trader'),
|
||||||
|
|
||||||
|
# Crypto/DeFi specific claims
|
||||||
|
(r'(\d+[\d.]*)\s+(eth|btc|sol|bnb|ada|dot)\s+(profit|gained|made|up)', 'crypto_profit'),
|
||||||
|
(r'yield\s+farming\s+.{0,30}\s*(\d+[\d.]*)\s*%', 'yield_claim'),
|
||||||
|
(r'staking\s+.{0,30}\s*(\d+[\d.]*)\s*%\s*(apy|apr)', 'staking_yield'),
|
||||||
|
(r'liquidity\s+provider?\s+.{0,30}\s*(\d+[\d.]*)\s*%', 'lp_yield'),
|
||||||
|
(r'(\d+[\d.]*)x\s+(leverage|margin)', 'leverage_claim'),
|
||||||
|
|
||||||
|
# NFT claims
|
||||||
|
(r'(flip|sold|bought)\s+.{0,30}\s+for\s+(\d+[\d.]*)\s+(eth|sol)', 'nft_flip'),
|
||||||
|
(r'(\d+[\d.]*)\s+(eth|sol)\s+floor', 'nft_floor'),
|
||||||
|
(r'mint\s+(price|cost)\s+(\d+[\d.]*)', 'mint_price'),
|
||||||
|
|
||||||
# Arbitrage/spread
|
# Arbitrage/spread
|
||||||
(r'(arb|arbitrage|spread|mismatch|mispriced)', 'arbitrage_opp'),
|
(r'(arb|arbitrage|spread|mismatch|mispriced)', 'arbitrage_opp'),
|
||||||
(r'risk[\s-]?free', 'arbitrage_opp'),
|
(r'risk[\s-]?free', 'arbitrage_opp'),
|
||||||
(r'guaranteed\s+(profit|return|money)', 'arbitrage_opp'),
|
(r'guaranteed\s+(profit|return|money)', 'arbitrage_opp'),
|
||||||
|
(r'(\d+[\d.]*)\s*%\s+guaranteed', 'guaranteed_return'),
|
||||||
|
|
||||||
# Prediction/betting
|
# Sports betting / Prediction markets
|
||||||
(r'(polymarket|kalshi|manifold|prediction\s+market)', 'prediction_market'),
|
(r'(polymarket|kalshi|manifold|prediction\s+market)', 'prediction_market'),
|
||||||
(r'(odds|probability)\s+.{0,20}\s*(\d+[\d.]*)\s*%', 'odds_claim'),
|
(r'(odds|probability)\s+.{0,20}\s*(\d+[\d.]*)\s*%', 'odds_claim'),
|
||||||
(r'(yes|no)\s+shares?\s+at\s+(\d+)', 'shares_price'),
|
(r'(yes|no)\s+shares?\s+at\s+(\d+)', 'shares_price'),
|
||||||
|
(r'betting\s+(\d+[\d.]*)\s*%\s+on', 'betting_confidence'),
|
||||||
|
(r'(\d+[\d.]*)\s*%\s+chance\s+(of|that)', 'probability_estimate'),
|
||||||
|
|
||||||
# Price/target claims
|
# Price/target claims
|
||||||
(r'(target|pt|price target)\s*[:\s]+\$?\s*(\d[\d,.]*)', 'price_target'),
|
(r'(target|pt|price target)\s*[:\s]+\$?\s*(\d[\d,.]*)', 'price_target'),
|
||||||
(r'(entry|buy)\s*(at|zone|point)\s*[:\s]+\$?\s*(\d[\d,.]*)', 'entry_point'),
|
(r'(entry|buy)\s*(at|zone|point)\s*[:\s]+\$?\s*(\d[\d,.]*)', 'entry_point'),
|
||||||
|
(r'next\s+(resistance|support)\s+at\s+\$?(\d[\d,.]*)', 'technical_level'),
|
||||||
|
(r'going\s+to\s+\$?(\d[\d,.]*)', 'price_prediction'),
|
||||||
|
|
||||||
# Airdrop/free money
|
# Airdrop/free money
|
||||||
(r'(airdrop|free\s+money|free\s+tokens?|claiming)', 'airdrop'),
|
(r'(airdrop|free\s+money|free\s+tokens?|claiming)', 'airdrop'),
|
||||||
(r'(step\s+\d|how\s+to\s+(get|claim|earn))', 'howto'),
|
(r'(step\s+\d|how\s+to\s+(get|claim|earn))', 'howto'),
|
||||||
|
(r'eligible\s+for\s+(\d+[\d.]*)\s+tokens?', 'airdrop_amount'),
|
||||||
|
|
||||||
# User/account references
|
# Signal/alert claims
|
||||||
|
(r'(signal|alert|call)\s+.{0,30}\s*(\d+[\d.]*)\s*%\s+(gain|profit)', 'signal_performance'),
|
||||||
|
(r'last\s+(\d+)\s+(signal|call|alert)s?\s+all\s+(profit|green|won)', 'signal_streak'),
|
||||||
|
(r'(\d+[\d.]*)\s*%\s+win\s+rate\s+on\s+(signal|call)s?', 'signal_winrate'),
|
||||||
|
|
||||||
|
# Time-sensitive claims
|
||||||
|
(r'(today|yesterday|this\s+week)\s+.{0,30}\s*(\d+[\d.]*)\s*%\s+(up|gain|profit)', 'recent_performance'),
|
||||||
|
(r'(\d+[\d.]*)\s*%\s+in\s+(\d+)\s+(hour|day|week)s?', 'timeframe_performance'),
|
||||||
|
(r'last\s+(\d+)\s+(day|week|month)s?\s+.{0,30}\s*(\d+[\d.]*)\s*%', 'period_performance'),
|
||||||
|
|
||||||
|
# User/account references with performance
|
||||||
(r'@\w+.*?(portfolio|track\s*record|history|performance)', 'user_reference'),
|
(r'@\w+.*?(portfolio|track\s*record|history|performance)', 'user_reference'),
|
||||||
(r'(this\s+(guy|trader|user|person|account))\s+.{0,30}(profit|win|return|made)', 'user_reference'),
|
(r'(this\s+(guy|trader|user|person|account))\s+.{0,30}(profit|win|return|made)', 'user_reference'),
|
||||||
|
(r'trader\s+with\s+(\d+[\d.]*)\s*%\s+(accuracy|win\s+rate)', 'trader_stats'),
|
||||||
|
(r'(\d+[\d.]*)\s*(million|M|k|K)\s+follower\s+trader', 'influencer_trader'),
|
||||||
|
|
||||||
|
# Strategy claims
|
||||||
|
(r'(strategy|method|system)\s+that\s+.{0,30}(\d+[\d.]*)\s*%', 'strategy_claim'),
|
||||||
|
(r'using\s+.{0,30}\s+(\d+[\d.]*)\s*%\s+(success|win)', 'method_performance'),
|
||||||
|
(r'secret\s+(strategy|method|formula)', 'secret_method'),
|
||||||
|
|
||||||
|
# Options/derivatives
|
||||||
|
(r'(call|put)\s+option\s+.{0,30}\s*(\d+[\d.]*)\s*%', 'options_claim'),
|
||||||
|
(r'(\d+[\d.]*)\s*%\s+(otm|itm|out\s+of\s+the\s+money|in\s+the\s+money)', 'options_probability'),
|
||||||
|
(r'delta\s+(\d+[\d.]*)', 'options_delta'),
|
||||||
|
|
||||||
|
# Course/education monetization
|
||||||
|
(r'course\s+that\s+.{0,30}(\d+[\d.]*)\s*(k|K|dollar)', 'course_earning'),
|
||||||
|
(r'teaching\s+.{0,30}\s+(\d+[\d.]*)\s*(k|K)\s+student', 'teaching_claim'),
|
||||||
|
(r'(\d+[\d.]*)\s*%\s+of\s+students\s+(profit|succeed)', 'student_success'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Link domains that are investigatable
|
# Link domains that are investigatable
|
||||||
|
|||||||
Reference in New Issue
Block a user