diff --git a/README.md b/README.md index 594645f..7aed22f 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,17 @@ Personal research mode: https://polymarket-site-eta.vercel.app/personal.html The site fetches live Polymarket markets, generates agent suggestions, lets you -run frequent paper cycles, and syncs the shared arena state through the Vercel -API when `BLOB_READ_WRITE_TOKEN` is configured. +run frequent paper cycles, and syncs the shared arena state through Neon or +Vercel Blob. Engine v35 also installs an offline app shell and caches timestamped +market snapshots. During an outage, cycles continue locally; cached entries are +allowed for 90 minutes, older snapshots become mark-only, and all cached data +expires after 24 hours. + +Each agent learns bounded weights from its own v34+ trade outcomes across signal +type, setup quality, category, side, and entry-price band. The learner shrinks +small samples toward neutral, caps sizing changes to 0.72x-1.28x, and reserves +15% of candidates for deterministic exploration so a stale regime cannot become +permanent. Paper accounts created with a password are also saved through the backend, so a user can log in from another device and see the same paper portfolio, activity, @@ -43,7 +52,8 @@ Pick one — all give you a public URL: Use `.env.example` as the setup template. -- `BLOB_READ_WRITE_TOKEN` enables cross-device shared state. +- `DATABASE_URL` or `NEON_DATABASE_URL` enables Neon-backed shared state; + `BLOB_READ_WRITE_TOKEN` is the fallback provider. - `ACCOUNT_SESSION_SECRET` signs cloud paper-account sessions. If omitted, the app falls back to the existing server secret/token, but production should use a dedicated value. diff --git a/api/state.js b/api/state.js index 0a354db..ab9c53f 100644 --- a/api/state.js +++ b/api/state.js @@ -225,9 +225,19 @@ export default async function handler(req, res) { } if (req.method === "GET") { - const state = await readJsonBlob(); - if (state && state.items) state.items = compactItems(state.items); - return res.status(200).json({ ok: true, state }); + try { + const state = await readJsonBlob(); + if (state && state.items) state.items = compactItems(state.items); + return res.status(200).json({ ok: true, state, degraded: false }); + } catch (err) { + // A storage outage must not prevent the installed app from using its local paper state. + return res.status(200).json({ + ok: true, + state: null, + degraded: true, + error: err && err.message ? err.message : "Cloud state provider unavailable", + }); + } } if (req.method === "POST") { @@ -235,7 +245,17 @@ export default async function handler(req, res) { if (!body || typeof body !== "object" || !body.items || typeof body.items !== "object") { return res.status(400).json({ ok: false, error: "Invalid state payload" }); } - const current = await readJsonBlob(); + let current; + try { + current = await readJsonBlob(); + } catch (err) { + return res.status(503).json({ + ok: false, + degraded: true, + retryable: true, + error: err && err.message ? err.message : "Cloud state provider unavailable", + }); + } const incomingItems = { ...body.items }; const currentAgents = agentStateFromItems(current && current.items); let incomingAgents = agentStateFromItems(incomingItems); diff --git a/index.html b/index.html index d960585..dfc3691 100644 --- a/index.html +++ b/index.html @@ -340,7 +340,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color