Memory update: Feed Hunter live, email setup, control panel building

This commit is contained in:
2026-02-08 09:59:42 -06:00
parent 5ce3e812a1
commit cac47724b1
15 changed files with 1485 additions and 48 deletions

View File

@ -7,6 +7,8 @@
},
"investigation": {
"profile_url": "https://polymarket.com/@kch123",
"wallet_address": "0x6a72f61820b26b1fe4d956e17b6dc2a1ea3033ee",
"secondary_wallet": "0x8c74b4eef9a894433B8126aA11d1345efb2B0488",
"verified_data": {
"all_time_pnl": "$9,371,829.00",
"positions_value": "$2.3m",

View File

@ -1,24 +1,100 @@
{
"positions": [
{
"id": "6607b9c1",
"strategy": "polymarket-copy-kch123",
"opened_at": "2026-02-08T05:50:14.328434+00:00",
"id": "1403ffd3",
"strategy": "copy-kch123-spread-4.5",
"opened_at": "2026-02-08T15:15:17.482343+00:00",
"type": "bet",
"asset": "Seahawks win Super Bowl 2026",
"entry_price": 0.68,
"size": 200,
"quantity": 1470,
"stop_loss": 0.4,
"take_profit": 1.0,
"current_price": 0.68,
"asset": "Spread: Seahawks (-4.5)",
"entry_price": 0.505,
"size": 200.0,
"quantity": 851,
"stop_loss": null,
"take_profit": null,
"current_price": 0.505,
"unrealized_pnl": 0,
"unrealized_pnl_pct": 0,
"source_post": "https://x.com/linie_oo/status/2020141674828034243",
"thesis": "Mirror kch123 largest active position. Seahawks Super Bowl at 68c. If they win, pays $1. kch123 has $9.3M all-time P&L, 1862 predictions. Sports betting specialist.",
"notes": "Paper trade to track if copying kch123 positions is profitable. Entry simulated at current 68c price.",
"thesis": "Mirror kch123 largest position. Seahawks -4.5 spread vs Patriots. Super Bowl today.",
"notes": "",
"updates": []
},
{
"id": "5451b4d6",
"strategy": "copy-kch123-sb-yes",
"opened_at": "2026-02-08T15:15:17.519032+00:00",
"type": "bet",
"asset": "Seahawks win Super Bowl 2026",
"entry_price": 0.6845,
"size": 200.0,
"quantity": 324,
"stop_loss": null,
"take_profit": null,
"current_price": 0.6845,
"unrealized_pnl": 0,
"unrealized_pnl_pct": 0,
"source_post": "https://x.com/linie_oo/status/2020141674828034243",
"thesis": "Mirror kch123 SB winner bet. Seahawks YES at 68.45c.",
"notes": "",
"updates": []
},
{
"id": "f2ddcf73",
"strategy": "copy-kch123-moneyline",
"opened_at": "2026-02-08T15:15:17.555276+00:00",
"type": "bet",
"asset": "Seahawks vs Patriots (Moneyline)",
"entry_price": 0.685,
"size": 184,
"quantity": 269,
"stop_loss": null,
"take_profit": null,
"current_price": 0.685,
"unrealized_pnl": 0,
"unrealized_pnl_pct": 0,
"source_post": "https://x.com/linie_oo/status/2020141674828034243",
"thesis": "Mirror kch123 moneyline. Seahawks to beat Patriots straight up.",
"notes": "",
"updates": []
},
{
"id": "3fcfddb4",
"strategy": "copy-kch123-spread-5.5",
"opened_at": "2026-02-08T15:15:17.593863+00:00",
"type": "bet",
"asset": "Spread: Seahawks (-5.5)",
"entry_price": 0.475,
"size": 89,
"quantity": 188,
"stop_loss": null,
"take_profit": null,
"current_price": 0.475,
"unrealized_pnl": 0,
"unrealized_pnl_pct": 0,
"source_post": "https://x.com/linie_oo/status/2020141674828034243",
"thesis": "Mirror kch123 wider spread. Seahawks -5.5. Riskier.",
"notes": "",
"updates": []
},
{
"id": "bf1e7b4f",
"strategy": "copy-kch123-pats-no",
"opened_at": "2026-02-08T15:15:17.632987+00:00",
"type": "bet",
"asset": "Patriots win Super Bowl - NO",
"entry_price": 0.6865,
"size": 75,
"quantity": 110,
"stop_loss": null,
"take_profit": null,
"current_price": 0.6865,
"unrealized_pnl": 0,
"unrealized_pnl_pct": 0,
"source_post": "https://x.com/linie_oo/status/2020141674828034243",
"thesis": "Mirror kch123 hedge/complement. Patriots NO to win SB.",
"notes": "",
"updates": []
}
],
"bankroll_used": 200
"bankroll_used": 748.0
}

View File

@ -0,0 +1,27 @@
{
"closed": [
{
"id": "6607b9c1",
"strategy": "polymarket-copy-kch123",
"opened_at": "2026-02-08T05:50:14.328434+00:00",
"type": "bet",
"asset": "Seahawks win Super Bowl 2026",
"entry_price": 0.68,
"size": 200,
"quantity": 1470,
"stop_loss": 0.4,
"take_profit": 1.0,
"current_price": 0.68,
"unrealized_pnl": 0,
"unrealized_pnl_pct": 0,
"source_post": "https://x.com/linie_oo/status/2020141674828034243",
"thesis": "Mirror kch123 largest active position. Seahawks Super Bowl at 68c. If they win, pays $1. kch123 has $9.3M all-time P&L, 1862 predictions. Sports betting specialist.",
"notes": "Paper trade to track if copying kch123 positions is profitable. Entry simulated at current 68c price.",
"updates": [],
"closed_at": "2026-02-08T15:15:17.443369+00:00",
"exit_price": 0.6845,
"realized_pnl": 1.32,
"realized_pnl_pct": 0.66
}
]
}

View File

@ -0,0 +1,50 @@
----------------------------------------
Exception occurred during processing of request from ('127.0.0.1', 45572)
Traceback (most recent call last):
File "/usr/lib/python3.12/socketserver.py", line 318, in _handle_request_noblock
self.process_request(request, client_address)
File "/usr/lib/python3.12/socketserver.py", line 349, in process_request
self.finish_request(request, client_address)
File "/usr/lib/python3.12/socketserver.py", line 362, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/usr/lib/python3.12/socketserver.py", line 761, in __init__
self.handle()
File "/usr/lib/python3.12/http/server.py", line 436, in handle
self.handle_one_request()
File "/usr/lib/python3.12/http/server.py", line 424, in handle_one_request
method()
File "/home/wdjones/.openclaw/workspace/projects/feed-hunter/portal/server.py", line 37, in do_GET
self.serve_simulations()
File "/home/wdjones/.openclaw/workspace/projects/feed-hunter/portal/server.py", line 243, in serve_simulations
{self.render_trade_history(sims.get('history', []))}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/wdjones/.openclaw/workspace/projects/feed-hunter/portal/server.py", line 748, in render_trade_history
for trade in history[-10:]: # Last 10 trades
~~~~~~~^^^^^^
KeyError: slice(-10, None, None)
----------------------------------------
----------------------------------------
Exception occurred during processing of request from ('127.0.0.1', 48354)
Traceback (most recent call last):
File "/usr/lib/python3.12/socketserver.py", line 318, in _handle_request_noblock
self.process_request(request, client_address)
File "/usr/lib/python3.12/socketserver.py", line 349, in process_request
self.finish_request(request, client_address)
File "/usr/lib/python3.12/socketserver.py", line 362, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/usr/lib/python3.12/socketserver.py", line 761, in __init__
self.handle()
File "/usr/lib/python3.12/http/server.py", line 436, in handle
self.handle_one_request()
File "/usr/lib/python3.12/http/server.py", line 424, in handle_one_request
method()
File "/home/wdjones/.openclaw/workspace/projects/feed-hunter/portal/server.py", line 37, in do_GET
self.serve_simulations()
File "/home/wdjones/.openclaw/workspace/projects/feed-hunter/portal/server.py", line 243, in serve_simulations
{self.render_trade_history(sims.get('history', []))}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/wdjones/.openclaw/workspace/projects/feed-hunter/portal/server.py", line 748, in render_trade_history
for trade in history[-10:]: # Last 10 trades
~~~~~~~^^^^^^
KeyError: slice(-10, None, None)
----------------------------------------

View File

@ -9,37 +9,54 @@ import os
import glob
from datetime import datetime, timezone
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
from urllib.parse import urlparse, parse_qs
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
daemon_threads = True
import re
# Configuration
PORT = 8888
DATA_DIR = "../data"
SKILLS_DIR = "../../skills/deep-scraper/scripts"
_PORTAL_DIR = os.path.dirname(os.path.abspath(__file__))
_PROJECT_DIR = os.path.dirname(_PORTAL_DIR)
DATA_DIR = os.path.join(_PROJECT_DIR, "data")
SKILLS_DIR = os.path.join(os.path.dirname(_PROJECT_DIR), "skills", "deep-scraper", "scripts")
X_FEED_DIR = os.path.join(os.path.dirname(_PROJECT_DIR), "..", "data", "x-feed")
class FeedHunterHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed_path = urlparse(self.path)
path = parsed_path.path
query = parse_qs(parsed_path.query)
if path == '/' or path == '/dashboard':
self.serve_dashboard()
elif path == '/feed':
self.serve_feed_view()
elif path == '/investigations':
self.serve_investigations()
elif path == '/simulations':
self.serve_simulations()
elif path == '/status':
self.serve_status()
elif path == '/api/data':
self.serve_api_data(query.get('type', [''])[0])
elif path.startswith('/static/'):
self.serve_static(path)
else:
self.send_error(404)
try:
parsed_path = urlparse(self.path)
path = parsed_path.path
query = parse_qs(parsed_path.query)
if path == '/' or path == '/dashboard':
self.serve_dashboard()
elif path == '/feed':
self.serve_feed_view()
elif path == '/investigations':
self.serve_investigations()
elif path == '/simulations':
self.serve_simulations()
elif path == '/status':
self.serve_status()
elif path == '/api/data':
self.serve_api_data(query.get('type', [''])[0])
elif path.startswith('/static/'):
self.serve_static(path)
else:
self.send_error(404)
except Exception as e:
try:
self.send_response(500)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(f"<h1>500 Error</h1><pre>{e}</pre>".encode())
except:
pass
def serve_dashboard(self):
"""Main dashboard overview"""
@ -237,7 +254,7 @@ class FeedHunterHandler(BaseHTTPRequestHandler):
<div class="card">
<h3>Trade History</h3>
<div class="trade-history">
{self.render_trade_history(sims.get('history', []))}
{self.render_trade_history(sims.get('history', {}).get('closed', []) if isinstance(sims.get('history'), dict) else sims.get('history', []))}
</div>
</div>
</div>
@ -425,7 +442,7 @@ class FeedHunterHandler(BaseHTTPRequestHandler):
posts = []
try:
# Find latest x-feed directory
x_feed_pattern = os.path.join("../../data/x-feed", "20*")
x_feed_pattern = os.path.join(X_FEED_DIR, "20*")
x_feed_dirs = sorted(glob.glob(x_feed_pattern))
if x_feed_dirs:
@ -526,7 +543,7 @@ class FeedHunterHandler(BaseHTTPRequestHandler):
}
# Check for recent pipeline runs
x_feed_pattern = os.path.join("../../data/x-feed", "20*")
x_feed_pattern = os.path.join(X_FEED_DIR, "20*")
x_feed_dirs = sorted(glob.glob(x_feed_pattern))
if x_feed_dirs:
latest = os.path.basename(x_feed_dirs[-1])
@ -592,7 +609,7 @@ class FeedHunterHandler(BaseHTTPRequestHandler):
return html
def render_investigations(self, investigations):
"""Render investigation reports"""
"""Render investigation reports with rich links"""
if not investigations:
return '<div class="empty-state">No investigations found</div>'
@ -601,21 +618,81 @@ class FeedHunterHandler(BaseHTTPRequestHandler):
investigation = inv.get('investigation', {})
verdict = investigation.get('verdict', 'Unknown')
risk_score = investigation.get('risk_assessment', {}).get('score', 0)
risk_notes = investigation.get('risk_assessment', {}).get('notes', [])
source = inv.get('source_post', {})
verified = investigation.get('verified_data', {})
claim_vs = investigation.get('claim_vs_reality', {})
profile_url = investigation.get('profile_url', '')
strategy_notes = investigation.get('strategy_notes', '')
suggested = inv.get('suggested_simulation', {})
verdict_class = 'verified' if 'VERIFIED' in verdict else 'failed'
# Build links section
links_html = '<div class="investigation-links">'
if source.get('url'):
links_html += f'<a href="{source["url"]}" target="_blank" class="inv-link">📝 Original Post</a>'
if source.get('author'):
author = source["author"].replace("@", "")
links_html += f'<a href="https://x.com/{author}" target="_blank" class="inv-link">🐦 {source["author"]} on X</a>'
if profile_url:
links_html += f'<a href="{profile_url}" target="_blank" class="inv-link">👤 Polymarket Profile</a>'
# Extract wallet if present in the investigation data
wallet = inv.get('investigation', {}).get('wallet_address', '')
if not wallet:
# Try to find it in verified data or elsewhere
for key, val in verified.items():
if isinstance(val, str) and val.startswith('0x'):
wallet = val
break
if wallet:
links_html += f'<a href="https://polygonscan.com/address/{wallet}" target="_blank" class="inv-link">🔗 Wallet on Polygonscan</a>'
links_html += '</div>'
# Build verified data section
verified_html = ''
if verified:
verified_html = '<div class="investigation-verified"><h4>Verified Data</h4><div class="verified-grid">'
for key, val in verified.items():
label = key.replace('_', ' ').title()
verified_html += f'<div class="verified-item"><span class="verified-label">{label}</span><span class="verified-value">{val}</span></div>'
verified_html += '</div></div>'
# Build claim vs reality section
claim_html = ''
if claim_vs:
claim_html = '<div class="investigation-claims"><h4>Claim vs Reality</h4>'
for key, val in claim_vs.items():
label = key.replace('_', ' ').title()
claim_html += f'<div class="claim-row"><span class="claim-label">{label}</span><span class="claim-value">{val}</span></div>'
claim_html += '</div>'
# Risk notes
risk_html = ''
if risk_notes:
risk_html = '<div class="investigation-risk"><h4>Risk Assessment</h4><ul>'
for note in risk_notes:
risk_html += f'<li>{note}</li>'
risk_html += '</ul></div>'
# Strategy notes
strategy_html = ''
if strategy_notes:
strategy_html = f'<div class="investigation-strategy"><h4>Strategy Notes</h4><p>{strategy_notes}</p></div>'
html += f"""
<div class="investigation-item">
<div class="investigation-header">
<div class="investigation-author">{source.get('author', 'Unknown')}</div>
<div class="investigation-verdict {verdict_class}">{verdict}</div>
</div>
<div class="investigation-claim">{source.get('claim', 'No claim')}</div>
<div class="investigation-score">Risk Score: {risk_score}/10</div>
<div class="investigation-actions">
<button onclick="showInvestigationDetail('{inv.get('id', '')}')">View Details</button>
</div>
<div class="investigation-claim">"{source.get('claim', 'No claim')}"</div>
{links_html}
{verified_html}
{claim_html}
<div class="investigation-score">Risk Score: <strong>{risk_score}/10</strong></div>
{risk_html}
{strategy_html}
</div>
"""
@ -980,6 +1057,110 @@ body {
margin-bottom: 0.75rem;
}
.investigation-links {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0.75rem 0;
}
.inv-link {
display: inline-block;
padding: 0.35rem 0.75rem;
background: var(--bg-tertiary);
color: var(--accent-blue);
text-decoration: none;
border-radius: 6px;
font-size: 0.85rem;
border: 1px solid var(--border-color);
transition: all 0.2s;
}
.inv-link:hover {
background: var(--border-color);
color: var(--text-primary);
}
.investigation-verified, .investigation-claims, .investigation-risk, .investigation-strategy {
margin: 1rem 0;
padding: 1rem;
background: var(--bg-tertiary);
border-radius: 6px;
}
.investigation-verified h4, .investigation-claims h4, .investigation-risk h4, .investigation-strategy h4 {
color: var(--accent-blue);
margin-bottom: 0.75rem;
font-size: 0.95rem;
}
.verified-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 0.5rem;
}
.verified-item {
display: flex;
flex-direction: column;
padding: 0.5rem;
background: var(--bg-secondary);
border-radius: 4px;
}
.verified-label {
color: var(--text-secondary);
font-size: 0.8rem;
}
.verified-value {
color: var(--text-primary);
font-weight: bold;
font-size: 1rem;
}
.claim-row {
display: flex;
justify-content: space-between;
padding: 0.4rem 0;
border-bottom: 1px solid var(--border-color);
}
.claim-row:last-child { border-bottom: none; }
.claim-label {
color: var(--text-secondary);
font-size: 0.9rem;
}
.claim-value {
color: var(--text-primary);
font-size: 0.9rem;
text-align: right;
max-width: 60%;
}
.investigation-risk ul {
list-style: none;
padding: 0;
}
.investigation-risk li {
padding: 0.3rem 0;
color: var(--text-secondary);
font-size: 0.9rem;
}
.investigation-risk li::before {
content: "⚠️ ";
}
.investigation-strategy p {
color: var(--text-secondary);
font-size: 0.9rem;
line-height: 1.5;
}
/* Positions */
.position-item {
background: var(--bg-tertiary);
@ -1252,7 +1433,7 @@ def main():
print("")
try:
server = HTTPServer(('localhost', PORT), FeedHunterHandler)
server = ThreadedHTTPServer(('0.0.0.0', PORT), FeedHunterHandler)
server.serve_forever()
except KeyboardInterrupt:
print("\n🛑 Portal stopped")