mirror of
https://github.com/theodore-song/polymarket-analyst.git
synced 2026-08-06 20:57:44 +00:00
Reject stale strategy data from old clients
This commit is contained in:
+30
-10
@@ -6,7 +6,7 @@ const AGENTS_KEY = "pma_agents_v2";
|
||||
const SUG_KEY = "pma_suggestions_v5";
|
||||
const PAPER_KEY = "pma_paper_accounts_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"];
|
||||
const LIMITS = { closed: 80, history: 160, snapshots: 240, suggestions: 900, paperHistory: 120, paperSnapshots: 120, audit: 120 };
|
||||
|
||||
async function readJsonBlob() {
|
||||
@@ -71,6 +71,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) {
|
||||
if (!p || typeof p !== "object") return p;
|
||||
const out = { ...p };
|
||||
@@ -88,7 +97,8 @@ function compactAgentState(st) {
|
||||
for (const id of AGENT_IDS) {
|
||||
out.agents[id] = compactPortfolio(st.agents && st.agents[id]);
|
||||
}
|
||||
out.whales = st.whales || {};
|
||||
delete out.whales;
|
||||
delete out.copycatLeader;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -99,9 +109,10 @@ function compactSuggestion(s) {
|
||||
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,
|
||||
net_edge: s.net_edge, friction: s.friction, chase_penalty: s.chase_penalty,
|
||||
evidence_score: s.evidence_score, quality: s.quality,
|
||||
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,
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -162,27 +173,36 @@ export default async function handler(req, res) {
|
||||
return res.status(400).json({ ok: false, error: "Invalid state payload" });
|
||||
}
|
||||
const current = await readJsonBlob();
|
||||
const incomingItems = { ...body.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) {
|
||||
const currentCycle = currentAgents.last_cycle_hour || "";
|
||||
const incomingCycle = incomingAgents && incomingAgents.last_cycle_hour ? incomingAgents.last_cycle_hour : "";
|
||||
if (currentCycle && (!incomingAgents || !incomingCycle)) {
|
||||
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 differentRun = currentAgents.last_run && incomingAgents.last_run && currentAgents.last_run !== incomingAgents.last_run;
|
||||
if (sameCycle && differentRun) {
|
||||
return conflictResponse(res, "This cycle already has a cloud result", current);
|
||||
}
|
||||
}
|
||||
if (!body.force && shouldRejectStaleAgentWrite(currentAgents, incomingAgents)) {
|
||||
if (!body.force && staleAgentWrite) {
|
||||
return conflictResponse(res, "Incoming state would replace newer active positions with stale cash-only data", current);
|
||||
}
|
||||
const state = { version: 1, updated_at: new Date().toISOString(), items: compactItems(body.items) };
|
||||
const state = { version: 1, updated_at: new Date().toISOString(), items: compactItems(incomingItems) };
|
||||
const versionedPath = `${STATE_VERSION_PREFIX}${Date.now()}-${Math.random().toString(36).slice(2)}.json`;
|
||||
await put(versionedPath, JSON.stringify(state), {
|
||||
access: "private",
|
||||
|
||||
+4
-4
@@ -340,7 +340,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
||||
<nav class="topnav">
|
||||
<div class="brand">
|
||||
<div class="logo">🏆</div>
|
||||
<div><div class="brand-name">Polymarket Arena</div><div class="brand-sub">5 core agents · evidence-led strategies</div><div class="build-badge">Quality-first engine · v28</div></div>
|
||||
<div><div class="brand-name">Polymarket Arena</div><div class="brand-sub">5 core agents · evidence-led strategies</div><div class="build-badge">Quality-first engine · v29</div></div>
|
||||
</div>
|
||||
<div class="tabs" id="tabs">
|
||||
<button class="tab" data-tab="overview">Overview</button>
|
||||
@@ -362,7 +362,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
||||
<div class="personal-banner" id="personalBanner">
|
||||
<b>Personal research mode.</b> This copy is for your own analysis, paper tracking, and manual trade research only. It does not pool money, onboard investors, custody funds, bypass eligibility rules, or place orders without your manual approval.
|
||||
</div>
|
||||
<div class="live-build-banner"><b>Build v28 active:</b> agents require measurable post-cost edge, use strategy-specific entry rules, avoid jump-to-settlement events, and record structured exit reasons for continuous review. No return is guaranteed.</div>
|
||||
<div class="live-build-banner"><b>Build v29 active:</b> agents require measurable post-cost edge, use strategy-specific entry rules, and reject stale strategy data from older browser tabs. No return is guaranteed.</div>
|
||||
|
||||
<!-- ============ OVERVIEW ============ -->
|
||||
<section class="tabpanel" data-tab="overview">
|
||||
@@ -743,7 +743,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
Build quality-first-v28 · Paper trading only · Live prices from Polymarket's public Gamma API · Not financial advice ·
|
||||
Build quality-first-v29 · Paper trading only · Live prices from Polymarket's public Gamma API · Not financial advice ·
|
||||
<a class="market-link" href="https://github.com/theodore-song/polymarket-analyst" target="_blank" rel="noopener">Source on GitHub</a>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -770,7 +770,7 @@ const EXIT_STALE_DAYS = 10;
|
||||
const EXIT_RESOLUTION_DAYS = 2;
|
||||
const AGENTS_KEY = "pma_agents_v2";
|
||||
const SUG_KEY = "pma_suggestions_v5";
|
||||
const SUGGESTION_ENGINE_VERSION = 28;
|
||||
const SUGGESTION_ENGINE_VERSION = 29;
|
||||
const FOCUS_KEY = "pma_focus_v1";
|
||||
const VIEW_KEY = "pma_view_v1";
|
||||
const PF_SORT_KEY = "pma_portfolio_sort_v1";
|
||||
|
||||
Reference in New Issue
Block a user