diff --git a/.env.example b/.env.example index 1543aa6..1c65823 100644 --- a/.env.example +++ b/.env.example @@ -34,7 +34,7 @@ HTTP_PROXY=http://127.0.0.1:7890 # TAVILY_API_KEY= # 网页搜索 (https://tavily.com) # TWITTER_API_KEY= # Twitter 社交情绪 # SERPER_API_KEY= # 网页搜索备用 (https://serper.dev) -# POLYGON_API_KEY= # 股票、外汇数据 (https://polygon.io) +# POLYGON_API_KEY= # 股票、外汇数据 (https://polygon.io, 已更名为 massive.com) # FRED_API_KEY= # 美国经济指标 (https://fred.stlouisfed.org) # ETHERSCAN_API_KEY= # 链上数据 (https://etherscan.io) # CONGRESS_API_KEY= # 美国立法动态 (https://api.congress.gov) diff --git a/.kilo/kilo.jsonc b/.kilo/kilo.jsonc new file mode 100644 index 0000000..571a15b --- /dev/null +++ b/.kilo/kilo.jsonc @@ -0,0 +1,3 @@ +{ + "$schema": "https://app.kilo.ai/config.json" +} diff --git a/data/analyze_more.py b/data/analyze_more.py new file mode 100644 index 0000000..3399b8d --- /dev/null +++ b/data/analyze_more.py @@ -0,0 +1,34 @@ +import sqlite3 +conn=sqlite3.connect('data/signals.db') +c=conn.cursor() + +# Trade size vs IAS relationship +print("=== Trade Size vs IAS (by size bucket) ===") +for lo,hi in [(0,2000),(2000,5000),(5000,10000),(10000,20000),(20000,50000),(50000,999999)]: + row=c.execute('SELECT COUNT(*), AVG(information_asymmetry_score), AVG(trade_size_usd) FROM signals WHERE trade_size_usd>? AND trade_size_usd<=?',(lo,hi)).fetchone() + n=row[0] + if n>0: + avg_i = round(row[1],3) + avg_s = round(row[2],0) + print(f" ${lo:,.0f}-${hi:,.0f}: {n:3d} sigs, avg IAS={avg_i:.3f}, avg size=${avg_s:,.0f}") + +print() + +# Market question frequency +print("=== Top 10 Most Frequently Signaled Markets ===") +for r in c.execute('SELECT market_question, COUNT(*) as cnt, AVG(information_asymmetry_score) as avg_i FROM signals GROUP BY market_question ORDER BY cnt DESC LIMIT 10').fetchall(): + print(f" {r[0][:65]:65s} | {r[1]:3d} sigs | avg IAS={r[2]:.3f}") + +print() + +# Resolution status +print("=== Unresolved Signals (PENDING) ===") +pending=c.execute('SELECT COUNT(*) FROM signals WHERE market_resolved IS NULL OR market_resolved=?',(0,)).fetchone()[0] +print(f" Pending: {pending}") + +print() + +# Trader wallet frequency +print("=== Top 10 Most Frequent Trader Wallets ===") +for r in c.execute('SELECT trader_wallet, COUNT(*) as cnt, AVG(information_asymmetry_score) as avg_i FROM signals GROUP BY trader_wallet ORDER BY cnt DESC LIMIT 10').fetchall(): + print(f" {str(r[0])[:25]:25s} | {r[1]:3d} sigs | avg IAS={r[2]:.3f}") diff --git a/data/analyze_signals.py b/data/analyze_signals.py new file mode 100644 index 0000000..b16349a --- /dev/null +++ b/data/analyze_signals.py @@ -0,0 +1,58 @@ +import sqlite3, json +conn=sqlite3.connect('data/signals.db') +c=conn.cursor() + +print("="*60) +print("SIGNALS DATABASE ANALYSIS") +print("="*60) + +total=c.execute('SELECT COUNT(*) FROM signals').fetchone()[0] +resolved=c.execute('SELECT COUNT(*) FROM signals WHERE market_resolved=?',(1,)).fetchone()[0] +correct=c.execute('SELECT COUNT(*) FROM signals WHERE signal_correct=?',(1,)).fetchone()[0] +incorrect=c.execute('SELECT COUNT(*) FROM signals WHERE signal_correct=?',(0,)).fetchone()[0] + +print(f"\nTotal signals: {total}") +print(f"Resolved: {resolved} | Correct: {correct} | Incorrect: {incorrect}") +print(f"Win rate: {correct/resolved:.2%}") +print(f"Avg ROI: {c.execute('SELECT AVG(theoretical_roi) FROM signals WHERE market_resolved=?',(1,)).fetchone()[0]:.2%}") + +print("\n=== IAS Distribution ===") +for lo,hi in [(0.0,0.3),(0.3,0.4),(0.4,0.5),(0.5,0.6),(0.6,0.7),(0.7,0.8),(0.8,1.0)]: + row=c.execute('SELECT COUNT(*), AVG(information_asymmetry_score) FROM signals WHERE information_asymmetry_score>? AND information_asymmetry_score<=?',(lo,hi)).fetchone() + n=row[0]; avg=round(row[1],3) if row[1] else 0 + bar='#'*int(n*0.4) + print(f" {lo}-{hi}: {n:3d} signals, avg={avg:.3f} {bar}") + +print("\n=== Win Rate by IAS Tier ===") +for lo,hi in [(0.3,0.4),(0.4,0.5),(0.5,0.6),(0.6,0.7),(0.7,0.8),(0.8,1.0)]: + n=c.execute('SELECT COUNT(*) FROM signals WHERE information_asymmetry_score>? AND information_asymmetry_score<=?',(lo,hi)).fetchone()[0] + if n>0: + cr=c.execute('SELECT COUNT(*) FROM signals WHERE information_asymmetry_score>? AND information_asymmetry_score<=? AND signal_correct=?',(lo,hi,1)).fetchone()[0] + print(f" IAS {lo}-{hi}: {cr}/{n} correct ({cr/n:.1%})") + +print("\n=== Trade Size Distribution ===") +bins=[(0,1000),(1000,2000),(2000,3000),(3000,5000),(5000,10000),(10000,20000),(20000,50000),(50000,999999)] +for lo,hi in bins: + row=c.execute('SELECT COUNT(*), AVG(trade_size_usd) FROM signals WHERE trade_size_usd>? AND trade_size_usd<=?',(lo,hi)).fetchone() + n=row[0]; avg=round(row[1],0) if row[1] else 0 + if n>0: + print(f" ${lo:,.0f}-${hi:,.0f}: {n:3d} signals, avg ${avg:,.0f}") + +print("\n=== Signal Time Range ===") +earliest=c.execute('SELECT MIN(detected_at) FROM signals').fetchone()[0] +latest=c.execute('SELECT MAX(detected_at) FROM signals').fetchone()[0] +print(f" First: {earliest}") +print(f" Last: {latest}") + +print("\n=== Top 10 Signals by IAS ===") +for r in c.execute('SELECT trade_side, trade_outcome, trade_size_usd, trade_price, market_question, information_asymmetry_score, signal_correct, theoretical_roi FROM signals ORDER BY information_asymmetry_score DESC LIMIT 10').fetchall(): + c_str='CORRECT' if r[6] else ('WRONG' if r[6] is not None else 'PENDING') + roi=f'{r[7]:.2%}' if r[7] else 'N/A' + print(f" IAS={r[5]:.2f} | {r[0]} {r[1]} @ {r[3]:.4f} ${r[2]:,.0f} | {r[4][:55]}... [{c_str}] ROI={roi}") + +print("\n=== Sample LLM Analysis Reasoning (highest IAS) ===") +top=c.execute('SELECT reasoning, insider_evidence FROM signals ORDER BY information_asymmetry_score DESC LIMIT 3').fetchall() +for i, (reasoning, evidence) in enumerate(top): + print(f"\n--- Signal #{i+1} ---") + print(f"Reasoning (first 500 chars): {str(reasoning)[:500]}") + print(f"Insider evidence: {str(evidence)[:500] if evidence else 'None'}") diff --git a/data/analyze_txns.py b/data/analyze_txns.py new file mode 100644 index 0000000..2b713fa --- /dev/null +++ b/data/analyze_txns.py @@ -0,0 +1,78 @@ +import json, collections, math +from datetime import datetime + +with open('data/processed_transactions.json') as f: + txns = json.load(f) + +print(f"Total transactions in file: {len(txns)}") +print() + +# Sample +print("=== Sample (first 3) ===") +for t in txns[:3]: + print(json.dumps(t, indent=2)) + print() + +# Fields available +print("=== Available Fields ===") +sample = txns[0] +print(list(sample.keys())) +print() + +# Direction distribution +print("=== Direction ===") +dirs = collections.Counter() +for t in txns: + dirs[t.get('side','?')]+=1 +print(dirs) +print() + +# Side breakdown +print("=== BUY vs SELL ===") +for s in ['BUY','SELL']: + n=sum(1 for t in txns if t.get('side')==s) + print(f" {s}: {n}") +print() + +# Size distribution +print("=== Trade Size (USD) Distribution ===") +bins=[(0,100),(100,500),(500,1000),(1000,2000),(2000,5000),(5000,10000),(10000,20000),(20000,50000),(50000,100000),(100000,999999)] +for lo,hi in bins: + n=sum(1 for t in txns if lo0: + avg=sum(t.get('usdc_size',0) for t in txns if lo0: + print(f" {lo}-{hi}: {n}") +print() + +# Largest trades +print("=== Top 20 Largest Trades (BUY) ===") +largest=sorted(buy_txns, key=lambda t: t.get('usdc_size',0), reverse=True)[:20] +for t in largest: + print(f" ${t['usdc_size']:,.0f} | {t['side']} {t.get('outcome','?')} @ {t['price']:.4f} | {t.get('title','?')[:60]}") +print() + +# Check what happens at each filter stage +print("=== Filter Stage Analysis (BUY only, 0.0-0.95 price) ===") +print(f"BUY txns with price in 0.0-0.95: {sum(1 for t in buy_txns if 0<=t.get('price',0)<=0.95)}") +print(f"BUY txns size >= 1000: {sum(1 for t in buy_txns if t.get('usdc_size',0)>=1000)}") +print(f"BUY txns size >= 3000: {sum(1 for t in buy_txns if t.get('usdc_size',0)>=3000)}") +print(f"BUY txns size >= 5000: {sum(1 for t in buy_txns if t.get('usdc_size',0)>=5000)}") +print() + +# Check if there are BUY txns >= 5000 with price in range +big_buy=[t for t in buy_txns if t.get('usdc_size',0)>=5000 and 0<=t.get('price',0)<=0.95] +print(f"BUY txns >= $5K and price in range: {len(big_buy)}") +if big_buy: + print(" Sample of qualifying transactions:") + for t in big_buy[:5]: + print(f" ${t['usdc_size']:,.0f} @ {t['price']:.4f} | vol={t.get('volume',0):,.0f}") diff --git a/data/signals.db b/data/signals.db index debd783..0b9388c 100644 Binary files a/data/signals.db and b/data/signals.db differ diff --git a/data/signals.db-shm b/data/signals.db-shm deleted file mode 100644 index 8eebf51..0000000 Binary files a/data/signals.db-shm and /dev/null differ diff --git a/data/signals.db-wal b/data/signals.db-wal deleted file mode 100644 index 77c137a..0000000 Binary files a/data/signals.db-wal and /dev/null differ diff --git a/src/config/settings.py b/src/config/settings.py index 5ef74f9..8ecaf1d 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -56,6 +56,7 @@ class Settings(BaseSettings): min_trade_size_usd: float = Field(default=1000.0, alias="MIN_TRADE_SIZE_USD") min_price: float = Field(default=0.10, alias="MIN_PRICE") max_price: float = Field(default=0.90, alias="MAX_PRICE") + premium_threshold: float = Field(default=0.01, alias="PREMIUM_THRESHOLD") # Monitoring Settings trending_markets_limit: int = Field(default=50, alias="TRENDING_MARKETS_LIMIT") diff --git a/src/services/trade_monitor.py b/src/services/trade_monitor.py index 2d6077d..972ff1b 100644 --- a/src/services/trade_monitor.py +++ b/src/services/trade_monitor.py @@ -254,16 +254,16 @@ class TradeMonitor: pass # --- 4. Size --- - if activity.usdc_size < 3_000: + if activity.usdc_size < self.settings.min_trade_size_usd: return False # --- 5. Dynamic size --- - base_size = 5_000.0 + base_size = max(self.settings.min_trade_size_usd, 5_000.0) baseline_volume = 1_000_000.0 - if market and market.volume > 0: - threshold = base_size * math.sqrt(market.volume / baseline_volume) - threshold = max(3_000.0, min(threshold, 50_000.0)) + if market and market.volume_24hr > 0: + threshold = base_size * math.sqrt(market.volume_24hr / baseline_volume) + threshold = max(self.settings.min_trade_size_usd, min(threshold, 50_000.0)) else: threshold = base_size @@ -271,12 +271,9 @@ class TradeMonitor: return False # --- 5.5 Normalized size (like normalized_premium) --- - # usdc_size / √(volume) makes signals comparable across market sizes. - # A $5K trade in a $50K market is far more significant than $20K in a $10M market. - if market and market.volume > 0: - normalized = activity.usdc_size / math.sqrt(market.volume) - # Minimum normalized threshold: filters out trades that are trivial - # relative to market size (calibrated: $5K in a $1M market → 5.0) + # Use 24h volume so old markets don't suppress small trades. + if market and market.volume_24hr > 0: + normalized = activity.usdc_size / math.sqrt(market.volume_24hr) if normalized < 1.5: return False @@ -289,7 +286,7 @@ class TradeMonitor: else: market_mid = 1.0 - market.outcome_prices[0] - if activity.price < market_mid + 0.01: + if activity.price < market_mid + self.settings.premium_threshold: return False return True