← back to Bertha
chore(alshi-dash): update 1 file (.js) [+75/-39]
fa964a3a1d2f65337be56a658bd306b0896c9751 · 2026-02-27 15:34:00 +0000 · DW Commit Agent
Files touched
Diff
commit fa964a3a1d2f65337be56a658bd306b0896c9751
Author: DW Commit Agent <commit-agent@dw-agents.com>
Date: Fri Feb 27 15:34:00 2026 +0000
chore(alshi-dash): update 1 file (.js) [+75/-39]
---
kalshi-dash/server.js | 114 +++++++++++++++++++++++++++++++++-----------------
1 file changed, 75 insertions(+), 39 deletions(-)
diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index 953d056..6cb3c67 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -1416,37 +1416,50 @@ const routes = {
json(res, { success: true, order: paperOrder, message: 'Paper order filled' });
} else if (body.action === 'get_positions') {
- // Aggregate paper trades into positions
- const positionMap = {};
- for (const trade of paperTrades) {
- const key = `${trade.ticker}_${trade.side}`;
- if (!positionMap[key]) {
- positionMap[key] = {
- ticker: trade.ticker,
- side: trade.side,
- total_contracts: 0,
- avg_price: 0,
- total_cost: 0,
- trades: 0,
- };
- }
- const pos = positionMap[key];
- if (trade.action === 'buy') {
- pos.total_cost += trade.price * trade.count;
- pos.total_contracts += trade.count;
- } else {
- pos.total_cost -= trade.price * trade.count;
- pos.total_contracts -= trade.count;
+ // Aggregate from ken_portfolio_trades (live hyper-trading data)
+ try {
+ const { rows } = await kenQ(`
+ SELECT pt.market_id as ticker, pt.market_title, pt.direction,
+ SUM(pt.contracts) as total_contracts,
+ SUM(pt.cost_cents) as position_cost,
+ ROUND(AVG(pt.entry_price_cents)) as avg_price,
+ COUNT(*) as trades,
+ SUM(CASE WHEN pt.status != 'open' THEN pt.pnl_cents ELSE 0 END) as realized_pnl
+ FROM ken_portfolio_trades pt
+ WHERE pt.created_at > NOW() - INTERVAL '24 hours'
+ GROUP BY pt.market_id, pt.market_title, pt.direction
+ ORDER BY SUM(pt.cost_cents) DESC
+ `);
+ const positions = rows.map(r => ({
+ ticker: r.ticker,
+ market_ticker: r.ticker,
+ title: r.market_title,
+ side: r.direction === 'BUY_YES' ? 'yes' : 'no',
+ market_exposure: r.direction === 'BUY_YES' ? parseInt(r.total_contracts) : -parseInt(r.total_contracts),
+ position: r.direction === 'BUY_YES' ? parseInt(r.total_contracts) : -parseInt(r.total_contracts),
+ position_cost: parseInt(r.position_cost) * 100,
+ avg_price_cents: parseInt(r.avg_price),
+ realized_pnl: parseInt(r.realized_pnl || 0) * 100,
+ fees_paid: 0,
+ trades: parseInt(r.trades),
+ }));
+ json(res, { success: true, positions, is_paper: true });
+ } catch (e) {
+ // Fallback to old paper trades
+ const positionMap = {};
+ for (const trade of paperTrades) {
+ const key = `${trade.ticker}_${trade.side}`;
+ if (!positionMap[key]) positionMap[key] = { ticker: trade.ticker, side: trade.side, total_contracts: 0, avg_price: 0, total_cost: 0, trades: 0 };
+ const pos = positionMap[key];
+ if (trade.action === 'buy') { pos.total_cost += trade.price * trade.count; pos.total_contracts += trade.count; }
+ else { pos.total_cost -= trade.price * trade.count; pos.total_contracts -= trade.count; }
+ pos.trades++;
+ pos.avg_price = pos.total_contracts > 0 ? Math.round(pos.total_cost / pos.total_contracts) : 0;
}
- pos.trades++;
- pos.avg_price = pos.total_contracts > 0
- ? Math.round(pos.total_cost / pos.total_contracts)
- : 0;
+ const positions = Object.values(positionMap).filter(p => p.total_contracts !== 0);
+ json(res, { success: true, positions, is_paper: true });
}
- const positions = Object.values(positionMap).filter(p => p.total_contracts !== 0);
- json(res, { success: true, positions, is_paper: true });
-
} else if (body.action === 'get_fills') {
// Return paper trade history
const limit = body.limit || 50;
@@ -7993,12 +8006,12 @@ async function autoPortfolioTrade(signal) {
if (!strategy(sigData)) continue;
- // Skip dupes: max 3 trades per market per portfolio per day
+ // Hyper-trade: max 10 OPEN positions per market per portfolio (no daily cap)
const { rows: dupes } = await kenQ(`
SELECT id FROM ken_portfolio_trades
- WHERE portfolio_id = $1 AND market_id = $2 AND created_at::date = CURRENT_DATE
+ WHERE portfolio_id = $1 AND market_id = $2 AND status = 'open'
`, [pf.id, signal.marketId || signal.market_id]);
- if (dupes.length >= 3) continue;
+ if (dupes.length >= 10) continue;
// Per-strategy position sizing
const absEdge = Math.abs(parseFloat(sigData.edge));
@@ -8065,15 +8078,17 @@ async function capturePortfolioSnapshots() {
}
}
-// Resolve portfolio trades — check if markets settled
+// Resolve portfolio trades — HYPER MODE: quick take-profit/stop-loss + aging out
async function resolvePortfolioTrades() {
try {
+ // Check ALL open trades (no minimum age for hyper-trading)
const { rows: openTrades } = await kenQ(`
- SELECT pt.*, p.name as portfolio_name
+ SELECT pt.*, p.name as portfolio_name, p.strategy as portfolio_strategy
FROM ken_portfolio_trades pt
JOIN ken_portfolios p ON p.id = pt.portfolio_id
- WHERE pt.status = 'open' AND pt.created_at < NOW() - INTERVAL '4 hours'
- LIMIT 100
+ WHERE pt.status = 'open'
+ ORDER BY pt.created_at ASC
+ LIMIT 500
`);
for (const trade of openTrades) {
@@ -8085,17 +8100,33 @@ async function resolvePortfolioTrades() {
if (snaps.length === 0) continue;
const currentPrice = parseInt(snaps[0].last_price);
+ const ageMinutes = (Date.now() - new Date(trade.created_at).getTime()) / 60000;
let pnl = 0;
- let status = 'expired';
if (trade.direction === 'BUY_YES') {
pnl = (currentPrice - trade.entry_price_cents) * trade.contracts;
- status = pnl > 0 ? 'won' : pnl < 0 ? 'lost' : 'expired';
} else {
pnl = (trade.entry_price_cents - currentPrice) * trade.contracts;
+ }
+
+ // Hyper-trade exit rules:
+ // 1. Take profit: +3¢ per contract (minimum 5 min hold)
+ // 2. Stop loss: -5¢ per contract (minimum 5 min hold)
+ // 3. Age out: close after 2 hours regardless
+ let status = null;
+ const profitPerContract = pnl / Math.max(1, trade.contracts);
+
+ if (ageMinutes >= 5 && profitPerContract >= 3) {
+ status = 'won'; // Take profit
+ } else if (ageMinutes >= 5 && profitPerContract <= -5) {
+ status = 'lost'; // Stop loss
+ } else if (ageMinutes >= 120) {
+ // Age out after 2 hours — mark based on current P&L
status = pnl > 0 ? 'won' : pnl < 0 ? 'lost' : 'expired';
}
+ if (!status) continue; // Hold position
+
await kenQ(`
UPDATE ken_portfolio_trades SET status = $1, exit_price_cents = $2, pnl_cents = $3, resolved_at = NOW()
WHERE id = $4
@@ -8516,12 +8547,17 @@ server.listen(PORT, '0.0.0.0', () => {
}
}, SIGNAL_PIPELINE_MS);
- // Resolve old predictions + portfolio trades every hour
+ // Resolve old predictions every hour
setInterval(() => {
resolveOldPredictions().catch(e => console.error('[Ken] Resolution error:', e.message));
- resolvePortfolioTrades().catch(e => console.error('[Ken] Portfolio resolution error:', e.message));
}, 60 * 60 * 1000);
+ // HYPER-TRADE: Resolve portfolio trades every 5 minutes (take-profit/stop-loss)
+ setInterval(() => {
+ resolvePortfolioTrades().catch(e => console.error('[Ken] Portfolio resolution error:', e.message));
+ }, 5 * 60 * 1000);
+ setTimeout(() => resolvePortfolioTrades().catch(() => {}), 30000); // Initial run after startup
+
// Portfolio NAV snapshots every 30 minutes for charting
setInterval(() => capturePortfolioSnapshots().catch(e => console.error('[Ken/Charts] Error:', e.message)), 30 * 60 * 1000);
setTimeout(() => capturePortfolioSnapshots().catch(() => {}), 25000); // Initial after startup
← d107bf6 chore(alshi-dash): update 1 file (.js) [+249/-6]
·
back to Bertha
·
chore(alshi-dash,kalshi-dash): update 8 files (.js) [+583/-1 2acd899 →