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:
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()
|
||||
Reference in New Issue
Block a user