Files
workspace/projects/feed-hunter/monitor-positions.py
Case 5ce3e812a1 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
2026-02-08 00:06:24 -06:00

329 lines
12 KiB
Python
Executable File

#!/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()