mirror of
https://github.com/theodore-song/polymarket-analyst.git
synced 2026-07-27 16:07:46 +00:00
Add personal capital tracker and market browser
This commit is contained in:
+138
@@ -145,6 +145,41 @@ export async function ensureSchema() {
|
||||
)
|
||||
`;
|
||||
|
||||
await db`
|
||||
create table if not exists personal_capital_accounts (
|
||||
user_id text primary key,
|
||||
cash numeric not null default 0,
|
||||
updated_at timestamptz not null default now()
|
||||
)
|
||||
`;
|
||||
|
||||
await db`
|
||||
create table if not exists personal_agent_allocations (
|
||||
user_id text not null,
|
||||
agent_id text not null,
|
||||
amount numeric not null default 0,
|
||||
updated_at timestamptz not null default now(),
|
||||
primary key (user_id, agent_id)
|
||||
)
|
||||
`;
|
||||
|
||||
await db`
|
||||
create table if not exists personal_capital_events (
|
||||
id bigserial primary key,
|
||||
user_id text not null,
|
||||
action text not null,
|
||||
agent_id text,
|
||||
amount numeric not null,
|
||||
note text,
|
||||
created_at timestamptz not null default now()
|
||||
)
|
||||
`;
|
||||
|
||||
await db`
|
||||
create index if not exists personal_capital_events_user_idx
|
||||
on personal_capital_events (user_id, created_at desc)
|
||||
`;
|
||||
|
||||
schemaReady = true;
|
||||
}
|
||||
|
||||
@@ -472,6 +507,109 @@ export async function listRealPortfolio(userId) {
|
||||
return { positions, fills };
|
||||
}
|
||||
|
||||
export async function listPersonalCapital(userId) {
|
||||
await ensureSchema();
|
||||
const db = sql();
|
||||
await db`
|
||||
insert into personal_capital_accounts (user_id, cash)
|
||||
values (${userId}, 0)
|
||||
on conflict (user_id) do nothing
|
||||
`;
|
||||
const accounts = await db`
|
||||
select *
|
||||
from personal_capital_accounts
|
||||
where user_id = ${userId}
|
||||
limit 1
|
||||
`;
|
||||
const allocations = await db`
|
||||
select *
|
||||
from personal_agent_allocations
|
||||
where user_id = ${userId} and amount > 0
|
||||
order by updated_at desc
|
||||
`;
|
||||
const events = await db`
|
||||
select *
|
||||
from personal_capital_events
|
||||
where user_id = ${userId}
|
||||
order by created_at desc
|
||||
limit 100
|
||||
`;
|
||||
return { account: accounts[0] || { user_id: userId, cash: 0 }, allocations, events };
|
||||
}
|
||||
|
||||
export async function recordPersonalCapitalAction(action) {
|
||||
await ensureSchema();
|
||||
const db = sql();
|
||||
const userId = action.userId;
|
||||
const type = String(action.action || "").toUpperCase();
|
||||
const agentId = action.agentId || null;
|
||||
const amount = Number(action.amount || 0);
|
||||
|
||||
await db`
|
||||
insert into personal_capital_accounts (user_id, cash)
|
||||
values (${userId}, 0)
|
||||
on conflict (user_id) do nothing
|
||||
`;
|
||||
|
||||
const accounts = await db`
|
||||
select *
|
||||
from personal_capital_accounts
|
||||
where user_id = ${userId}
|
||||
limit 1
|
||||
`;
|
||||
const account = accounts[0] || { cash: 0 };
|
||||
let nextCash = Number(account.cash || 0);
|
||||
|
||||
if (type === "DEPOSIT") {
|
||||
nextCash += amount;
|
||||
} else if (type === "WITHDRAW") {
|
||||
nextCash -= amount;
|
||||
} else if (type === "BUY_AGENT") {
|
||||
nextCash -= amount;
|
||||
await db`
|
||||
insert into personal_agent_allocations (user_id, agent_id, amount, updated_at)
|
||||
values (${userId}, ${agentId}, ${amount}, now())
|
||||
on conflict (user_id, agent_id) do update set
|
||||
amount = personal_agent_allocations.amount + excluded.amount,
|
||||
updated_at = now()
|
||||
`;
|
||||
} else if (type === "SELL_AGENT") {
|
||||
const rows = await db`
|
||||
select *
|
||||
from personal_agent_allocations
|
||||
where user_id = ${userId} and agent_id = ${agentId}
|
||||
limit 1
|
||||
`;
|
||||
const current = rows[0] ? Number(rows[0].amount || 0) : 0;
|
||||
const reduction = Math.min(amount, current);
|
||||
nextCash += reduction;
|
||||
await db`
|
||||
insert into personal_agent_allocations (user_id, agent_id, amount, updated_at)
|
||||
values (${userId}, ${agentId}, ${Math.max(0, current - reduction)}, now())
|
||||
on conflict (user_id, agent_id) do update set
|
||||
amount = excluded.amount,
|
||||
updated_at = now()
|
||||
`;
|
||||
}
|
||||
|
||||
if (nextCash < -0.000001) {
|
||||
throw new Error("Tracked personal cash is too low for that action");
|
||||
}
|
||||
|
||||
await db`
|
||||
update personal_capital_accounts
|
||||
set cash = ${nextCash}, updated_at = now()
|
||||
where user_id = ${userId}
|
||||
`;
|
||||
|
||||
await db`
|
||||
insert into personal_capital_events (user_id, action, agent_id, amount, note)
|
||||
values (${userId}, ${type}, ${agentId}, ${amount}, ${action.note || null})
|
||||
`;
|
||||
|
||||
return listPersonalCapital(userId);
|
||||
}
|
||||
|
||||
export async function providerEventSummary() {
|
||||
await ensureSchema();
|
||||
const db = sql();
|
||||
|
||||
+54
@@ -1,8 +1,10 @@
|
||||
import {
|
||||
createTradeTicket,
|
||||
listPersonalCapital,
|
||||
listRealPortfolio,
|
||||
listTradeTickets,
|
||||
recordAuditEvent,
|
||||
recordPersonalCapitalAction,
|
||||
recordRealFill,
|
||||
updateRealPositionMark,
|
||||
updateTradeTicketStatus,
|
||||
@@ -242,6 +244,7 @@ function validateIntent(intent) {
|
||||
|
||||
const VALID_TICKET_STATUSES = new Set(["staged", "reviewed", "placed_manually", "skipped", "cancelled"]);
|
||||
const VALID_FILL_ACTIONS = new Set(["BUY", "SELL"]);
|
||||
const VALID_CAPITAL_ACTIONS = new Set(["DEPOSIT", "WITHDRAW", "BUY_AGENT", "SELL_AGENT"]);
|
||||
|
||||
function marketSearchUrl(question) {
|
||||
return `https://polymarket.com/search?query=${encodeURIComponent(String(question || ""))}`;
|
||||
@@ -319,6 +322,25 @@ function validateFill(fill) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
function normalizeCapitalAction(body) {
|
||||
return {
|
||||
userId: String(body.user_id || "local-readiness-user").trim(),
|
||||
action: String(body.capital_action || body.capitalAction || "").trim().toUpperCase(),
|
||||
agentId: String(body.agent_id || "").trim(),
|
||||
amount: Number(body.amount || 0),
|
||||
note: String(body.note || "").trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function validateCapitalAction(action) {
|
||||
const errors = [];
|
||||
if (!action.userId) errors.push("user_id");
|
||||
if (!VALID_CAPITAL_ACTIONS.has(action.action)) errors.push("capital_action must be DEPOSIT, WITHDRAW, BUY_AGENT, or SELL_AGENT");
|
||||
if ((action.action === "BUY_AGENT" || action.action === "SELL_AGENT") && !action.agentId) errors.push("agent_id");
|
||||
if (!Number.isFinite(action.amount) || action.amount <= 0) errors.push("amount must be positive");
|
||||
return errors;
|
||||
}
|
||||
|
||||
export default async function handler(req, res) {
|
||||
res.setHeader("Cache-Control", "no-store");
|
||||
|
||||
@@ -333,6 +355,11 @@ export default async function handler(req, res) {
|
||||
const portfolio = await listRealPortfolio(userId);
|
||||
return res.status(200).json({ ok: true, ...portfolio });
|
||||
}
|
||||
if (req.query?.action === "personal_capital") {
|
||||
const userId = String(req.query?.user_id || "local-readiness-user").trim();
|
||||
const capital = await listPersonalCapital(userId);
|
||||
return res.status(200).json({ ok: true, ...capital });
|
||||
}
|
||||
return res.status(200).json(baseStatus());
|
||||
}
|
||||
|
||||
@@ -410,6 +437,33 @@ export default async function handler(req, res) {
|
||||
return res.status(200).json({ ok: true, position });
|
||||
}
|
||||
|
||||
if (body.action === "capital_action") {
|
||||
const capitalAction = normalizeCapitalAction(body);
|
||||
const capitalErrors = validateCapitalAction(capitalAction);
|
||||
if (capitalErrors.length) return res.status(400).json({ ok: false, error: "Invalid personal capital action", details: capitalErrors });
|
||||
let capital;
|
||||
try {
|
||||
capital = await recordPersonalCapitalAction(capitalAction);
|
||||
} catch (err) {
|
||||
return res.status(400).json({ ok: false, error: err?.message || "Personal capital tracker update failed" });
|
||||
}
|
||||
await recordAuditEvent("PERSONAL_CAPITAL_ACTION", {
|
||||
user_id: capitalAction.userId,
|
||||
action: capitalAction.action,
|
||||
agent_id: capitalAction.agentId,
|
||||
amount: capitalAction.amount,
|
||||
custody_by_site: false,
|
||||
real_order_placed_by_site: false,
|
||||
});
|
||||
return res.status(200).json({
|
||||
ok: true,
|
||||
...capital,
|
||||
custody_by_site: false,
|
||||
real_order_placed_by_site: false,
|
||||
note: "Personal capital tracker updated for bookkeeping only. Poly Arena did not receive funds or place an order.",
|
||||
});
|
||||
}
|
||||
|
||||
const intent = body.intent || body;
|
||||
const errors = validateIntent(intent);
|
||||
if (errors.length) {
|
||||
|
||||
Reference in New Issue
Block a user