Compare commits

...
28 Commits
Author SHA1 Message Date
Theodore Song ab333dd045 Replace forced exposure with confirmed signals 2026-08-09 15:47:06 -04:00
Theodore Song 0ef05ea734 Update exposure engine labels 2026-08-02 15:22:59 -04:00
Theodore Song cfecd2e78b Add durable portfolio exposure controller 2026-08-02 15:21:26 -04:00
Theodore Song 6233efc098 Move shared cycle state to Neon fallback 2026-08-02 14:45:06 -04:00
Theodore Song ecbdbf4cbf Use explicit Blob credentials for state sync 2026-08-02 14:41:49 -04:00
Theodore Song a4402134ff Restore resilient cloud state sync 2026-08-02 14:40:02 -04:00
Theodore Song e57d84af1d Let core agents join qualified probes 2026-08-01 17:54:42 -04:00
Theodore Song c7c8359f09 Fix audit churn and idle core portfolios 2026-08-01 17:49:15 -04:00
Theodore Song 8dc7c9506c Let aggressive agents press qualified signals 2026-08-01 12:18:14 -04:00
Theodore Song 313c4f9326 Add five aggressive strategy agents 2026-08-01 12:15:24 -04:00
Theodore Song 1effdd8373 Reject stale strategy data from old clients 2026-08-01 09:50:22 -04:00
Theodore Song 4630bd482b Add structured strategy audit records 2026-08-01 09:47:38 -04:00
Theodore Song 91a6c5678c Replace forced deployment with quality-first trading 2026-08-01 09:44:56 -04:00
Theodore Song 8a897b9a6b Let core agents share strong probe trades 2026-07-30 15:10:50 -04:00
Theodore Song 2a2f8af765 Increase core agent capital deployment 2026-07-30 15:06:51 -04:00
Theodore Song a352e86a2b Restore shared portfolio momentum metrics 2026-07-30 15:01:35 -04:00
Theodore Song 21f89a9fea Focus arena on five active core agents 2026-07-30 14:52:43 -04:00
Theodore Song f8e3799f21 Show rolling 24 hour portfolio changes 2026-07-29 16:11:47 -04:00
Theodore Song 5f57608ee7 Expand tiered trade-ready pool 2026-07-29 15:00:12 -04:00
Theodore Song 697830a782 Restore risk-capped agent deployment 2026-07-29 14:54:21 -04:00
Theodore Song cb4a539256 Add durable agent risk controls 2026-07-28 14:26:57 -04:00
Theodore Song 09b4eac736 Limit agent analysis to top 500 active markets 2026-07-27 15:55:08 -04:00
Theodore Song c7ce683db9 Read latest versioned cloud state 2026-07-27 15:45:51 -04:00
Theodore Song d04ed59088 Preserve suggestion quality fields 2026-07-27 15:40:26 -04:00
Theodore Song 82a1e0c831 Enable selective buys for agents 2026-07-27 15:37:55 -04:00
Theodore Song 67beab0144 Make top 5000 analysis visible 2026-07-27 15:27:48 -04:00
Theodore Song 3c39cfe9f3 Force visible top 5000 build 2026-07-26 21:13:02 -04:00
Theodore Song 744387199e Analyze top 5000 active markets 2026-07-26 20:10:39 -04:00
3 changed files with 755 additions and 479 deletions
+40
View File
@@ -2,6 +2,7 @@ import { neon } from "@neondatabase/serverless";
let sqlClient; let sqlClient;
let schemaReady; let schemaReady;
let sharedStateSchemaReady;
export function databaseUrl() { export function databaseUrl() {
return process.env.DATABASE_URL || process.env.NEON_DATABASE_URL || ""; return process.env.DATABASE_URL || process.env.NEON_DATABASE_URL || "";
@@ -20,6 +21,45 @@ export function sql() {
return sqlClient; return sqlClient;
} }
async function ensureSharedStateSchema() {
if (sharedStateSchemaReady) return;
const db = sql();
await db`
create table if not exists shared_app_state (
state_key text primary key,
payload jsonb not null,
updated_at timestamptz not null default now()
)
`;
sharedStateSchemaReady = true;
}
export async function readSharedAppState(stateKey) {
await ensureSharedStateSchema();
const db = sql();
const rows = await db`
select payload, updated_at
from shared_app_state
where state_key = ${stateKey}
limit 1
`;
return rows[0] || null;
}
export async function writeSharedAppState(stateKey, payload) {
await ensureSharedStateSchema();
const db = sql();
const rows = await db`
insert into shared_app_state (state_key, payload, updated_at)
values (${stateKey}, ${JSON.stringify(payload)}::jsonb, now())
on conflict (state_key) do update set
payload = excluded.payload,
updated_at = now()
returning updated_at
`;
return rows[0] || null;
}
export async function ensureSchema() { export async function ensureSchema() {
if (schemaReady) return; if (schemaReady) return;
const db = sql(); const db = sql();
+160 -17
View File
@@ -1,20 +1,130 @@
import { get, put } from "@vercel/blob"; import { get, list, put } from "@vercel/blob";
import { hasDatabase, readSharedAppState, writeSharedAppState } from "./_db.js";
const STATE_PATH = process.env.PMA_STATE_PATH || "shared/state.json"; const STATE_PATH = process.env.PMA_STATE_PATH || "shared/state.json";
const STATE_VERSION_PREFIX = process.env.PMA_STATE_VERSION_PREFIX || "shared/state-versions/";
const DATABASE_STATE_KEY = process.env.PMA_DATABASE_STATE_KEY || "polymarket-arena";
const AGENTS_KEY = "pma_agents_v2"; const AGENTS_KEY = "pma_agents_v2";
const SUG_KEY = "pma_suggestions_v5"; const SUG_KEY = "pma_suggestions_v5";
const PAPER_KEY = "pma_paper_accounts_v1"; const PAPER_KEY = "pma_paper_accounts_v1";
const LIVE_KEY = "pma_live_readiness_v1"; const LIVE_KEY = "pma_live_readiness_v1";
const AGENT_IDS = ["value", "momentum", "favorite", "longshot", "diversifier", "copycat", "whale1", "whale2", "whale3", "whale4"]; const AGENT_IDS = ["value", "momentum", "favorite", "longshot", "diversifier", "catalyst", "reversal", "breakout", "tailalpha", "conviction"];
const LIMITS = { closed: 80, history: 160, snapshots: 240, suggestions: 900, paperHistory: 120, paperSnapshots: 120, audit: 120 }; const LIMITS = { closed: 80, history: 160, snapshots: 240, suggestions: 900, paperHistory: 120, paperSnapshots: 120, audit: 120 };
function withBlobAuth(options = {}) {
const token = process.env.BLOB_READ_WRITE_TOKEN;
return token ? { ...options, token } : options;
}
async function readJsonBlob() { async function readJsonBlob() {
const blob = await get(STATE_PATH, { access: "private" }); let databaseAvailable = false;
if (hasDatabase()) {
try {
const row = await readSharedAppState(DATABASE_STATE_KEY);
databaseAvailable = true;
if (row && row.payload) return row.payload;
} catch {
databaseAvailable = false;
}
}
let primaryError = null;
try {
const primary = await readBlobJson(STATE_PATH);
if (primary) return primary;
} catch (err) {
primaryError = err;
}
try {
const latest = await latestVersionedStateBlob();
if (latest) return latest;
} catch (err) {
if (!primaryError) primaryError = err;
}
if (primaryError && !databaseAvailable) throw primaryError;
return null;
}
async function persistState(state) {
let databaseSaved = false;
let databaseError = null;
if (hasDatabase()) {
try {
await writeSharedAppState(DATABASE_STATE_KEY, state);
databaseSaved = true;
} catch (err) {
databaseError = err;
}
}
let blobSaved = false;
let blobError = null;
try {
await put(STATE_PATH, JSON.stringify(state), withBlobAuth({
access: "private",
allowOverwrite: true,
contentType: "application/json",
cacheControlMaxAge: 0,
}));
blobSaved = true;
} catch (err) {
blobError = err;
}
if (!databaseSaved && !blobSaved) throw databaseError || blobError || new Error("No state provider is available");
return { databaseSaved, blobSaved };
}
async function readBlobJson(pathname) {
const blob = await get(pathname, withBlobAuth({
access: "private",
headers: { "cache-control": "no-cache" },
}));
if (!blob || blob.statusCode !== 200 || !blob.stream) return null; if (!blob || blob.statusCode !== 200 || !blob.stream) return null;
const text = await new Response(blob.stream).text(); const text = await new Response(blob.stream).text();
return text ? JSON.parse(text) : null; return text ? JSON.parse(text) : null;
} }
async function latestVersionedStateBlob() {
let cursor;
let newest = null;
do {
const page = await list(withBlobAuth({ prefix: STATE_VERSION_PREFIX, limit: 1000, cursor }));
for (const blob of page.blobs || []) {
if (!newest || new Date(blob.uploadedAt).getTime() > new Date(newest.uploadedAt).getTime()) newest = blob;
}
cursor = page.cursor;
} while (cursor);
if (!newest) return null;
return readBlobJson(newest.pathname);
}
function cycleVersion(cycle = "") {
const m = String(cycle).match(/\|v(\d+)$/);
return m ? Number(m[1]) : 0;
}
function openPositionCount(st) {
if (!st || !st.agents) return 0;
return AGENT_IDS.reduce((sum, id) => sum + (Array.isArray(st.agents[id]?.positions) ? st.agents[id].positions.length : 0), 0);
}
function shouldRejectStaleAgentWrite(currentAgents, incomingAgents) {
if (!currentAgents || !incomingAgents) return false;
const currentCycle = currentAgents.last_cycle_hour || "";
const incomingCycle = incomingAgents.last_cycle_hour || "";
if (!currentCycle || !incomingCycle) return false;
const currentVersion = cycleVersion(currentCycle);
const incomingVersion = cycleVersion(incomingCycle);
if (incomingVersion < currentVersion) return true;
if (incomingVersion === currentVersion && incomingCycle < currentCycle) return true;
const currentOpen = openPositionCount(currentAgents);
const incomingOpen = openPositionCount(incomingAgents);
return incomingVersion === currentVersion && incomingCycle === currentCycle && currentOpen > 0 && incomingOpen === 0;
}
function agentStateFromItems(items) { function agentStateFromItems(items) {
if (!items || !items[AGENTS_KEY]) return null; if (!items || !items[AGENTS_KEY]) return null;
try { try {
@@ -24,6 +134,15 @@ function agentStateFromItems(items) {
} }
} }
function suggestionStateFromItems(items) {
if (!items || !items[SUG_KEY]) return null;
try {
return JSON.parse(items[SUG_KEY]);
} catch {
return null;
}
}
function compactPortfolio(p) { function compactPortfolio(p) {
if (!p || typeof p !== "object") return p; if (!p || typeof p !== "object") return p;
const out = { ...p }; const out = { ...p };
@@ -41,7 +160,8 @@ function compactAgentState(st) {
for (const id of AGENT_IDS) { for (const id of AGENT_IDS) {
out.agents[id] = compactPortfolio(st.agents && st.agents[id]); out.agents[id] = compactPortfolio(st.agents && st.agents[id]);
} }
out.whales = st.whales || {}; delete out.whales;
delete out.copycatLeader;
return out; return out;
} }
@@ -51,8 +171,11 @@ function compactSuggestion(s) {
market_id: s.market_id, question: s.question, event: s.event, url: s.url, category: s.category, market_id: s.market_id, question: s.question, event: s.event, url: s.url, category: s.category,
clob_yes: s.clob_yes, clob_no: s.clob_no, yes_price: s.yes_price, no_price: s.no_price, clob_yes: s.clob_yes, clob_no: s.clob_no, yes_price: s.yes_price, no_price: s.no_price,
fair_value: s.fair_value, edge: s.edge, side: s.side, entry_price: s.entry_price, fair_value: s.fair_value, edge: s.edge, side: s.side, entry_price: s.entry_price,
net_edge: s.net_edge, friction: s.friction, chase_penalty: s.chase_penalty,
evidence_score: s.evidence_score, evidence_source_count: s.evidence_source_count, quality: s.quality,
conviction: s.conviction, volume: s.volume, volume_24hr: s.volume_24hr, liquidity: s.liquidity, conviction: s.conviction, volume: s.volume, volume_24hr: s.volume_24hr, liquidity: s.liquidity,
trade_ready: s.trade_ready, watch_only: s.watch_only, spread: s.spread, price_change_1h: s.price_change_1h, price_change_1d: s.price_change_1d, price_change_1w: s.price_change_1w,
momentum_strength: s.momentum_strength, trade_ready: s.trade_ready, watch_only: s.watch_only, jump_risk: s.jump_risk,
days_to_resolution: s.days_to_resolution, drivers: s.drivers, rationale: s.rationale, days_to_resolution: s.days_to_resolution, drivers: s.drivers, rationale: s.rationale,
}; };
} }
@@ -97,7 +220,7 @@ function conflictResponse(res, error, current) {
export default async function handler(req, res) { export default async function handler(req, res) {
res.setHeader("Cache-Control", "no-store"); res.setHeader("Cache-Control", "no-store");
try { try {
if (!process.env.BLOB_READ_WRITE_TOKEN && !process.env.VERCEL_OIDC_TOKEN) { if (!hasDatabase() && !process.env.BLOB_READ_WRITE_TOKEN && !process.env.VERCEL_OIDC_TOKEN) {
return res.status(503).json({ ok: false, error: "Cloud state is not configured" }); return res.status(503).json({ ok: false, error: "Cloud state is not configured" });
} }
@@ -113,30 +236,50 @@ export default async function handler(req, res) {
return res.status(400).json({ ok: false, error: "Invalid state payload" }); return res.status(400).json({ ok: false, error: "Invalid state payload" });
} }
const current = await readJsonBlob(); const current = await readJsonBlob();
const incomingItems = { ...body.items };
const currentAgents = agentStateFromItems(current && current.items); const currentAgents = agentStateFromItems(current && current.items);
const incomingAgents = agentStateFromItems(body.items); let incomingAgents = agentStateFromItems(incomingItems);
const staleAgentWrite = shouldRejectStaleAgentWrite(currentAgents, incomingAgents);
if (body.force && staleAgentWrite && current?.items?.[AGENTS_KEY]) {
incomingItems[AGENTS_KEY] = current.items[AGENTS_KEY];
incomingAgents = currentAgents;
}
const currentSuggestions = suggestionStateFromItems(current && current.items);
const incomingSuggestions = suggestionStateFromItems(incomingItems);
if (currentSuggestions && incomingSuggestions
&& Number(incomingSuggestions.engine_version || 0) < Number(currentSuggestions.engine_version || 0)) {
incomingItems[SUG_KEY] = current.items[SUG_KEY];
}
if (!body.force && currentAgents) { if (!body.force && currentAgents) {
const currentCycle = currentAgents.last_cycle_hour || ""; const currentCycle = currentAgents.last_cycle_hour || "";
const incomingCycle = incomingAgents && incomingAgents.last_cycle_hour ? incomingAgents.last_cycle_hour : ""; const incomingCycle = incomingAgents && incomingAgents.last_cycle_hour ? incomingAgents.last_cycle_hour : "";
if (currentCycle && (!incomingAgents || !incomingCycle)) { if (currentCycle && (!incomingAgents || !incomingCycle)) {
return conflictResponse(res, "Cloud already has a cycle result; refusing unscheduled local state", current); return conflictResponse(res, "Cloud already has a cycle result; refusing unscheduled local state", current);
} }
if (currentCycle && incomingCycle && incomingCycle < currentCycle) {
return conflictResponse(res, "Incoming state is older than the shared cloud result", current);
}
const sameCycle = currentCycle && currentCycle === incomingCycle; const sameCycle = currentCycle && currentCycle === incomingCycle;
const differentRun = currentAgents.last_run && incomingAgents.last_run && currentAgents.last_run !== incomingAgents.last_run; const differentRun = currentAgents.last_run && incomingAgents.last_run && currentAgents.last_run !== incomingAgents.last_run;
if (sameCycle && differentRun) { if (sameCycle && differentRun) {
return conflictResponse(res, "This cycle already has a cloud result", current); return conflictResponse(res, "This cycle already has a cloud result", current);
} }
} }
const state = { version: 1, updated_at: new Date().toISOString(), items: compactItems(body.items) }; if (!body.force && staleAgentWrite) {
await put(STATE_PATH, JSON.stringify(state), { return conflictResponse(res, "Incoming state would replace newer active positions with stale cash-only data", current);
access: "private", }
allowOverwrite: true, const state = { version: 1, updated_at: new Date().toISOString(), items: compactItems(incomingItems) };
contentType: "application/json", const saved = await persistState(state);
cacheControlMaxAge: 60, if (saved.blobSaved && process.env.PMA_ENABLE_STATE_HISTORY === "true") {
}); const versionedPath = `${STATE_VERSION_PREFIX}${Date.now()}-${Math.random().toString(36).slice(2)}.json`;
try {
await put(versionedPath, JSON.stringify(state), withBlobAuth({
access: "private",
allowOverwrite: false,
contentType: "application/json",
cacheControlMaxAge: 0,
}));
} catch {
// The shared state is authoritative; backup retention must not block a cycle.
}
}
return res.status(200).json({ ok: true, state }); return res.status(200).json({ ok: true, state });
} }
+555 -462
View File
File diff suppressed because it is too large Load Diff