Initial PlayBull release with Trilogy Hub integration.
Includes homelab deploy tooling, image position/zoom manifest persistence, and full trading/casino stack. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
98
server/trading.js
Normal file
98
server/trading.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Users, Holdings, Trades } from './db.js';
|
||||
import { getAsset } from './market.js';
|
||||
|
||||
// Position-Netting: kombiniert bestehende Position mit neuem Trade.
|
||||
function applyPosition(q0, a0, delta, price) {
|
||||
if (q0 === 0 || Math.sign(q0) === Math.sign(delta)) {
|
||||
const newQ = q0 + delta;
|
||||
const newA = (Math.abs(q0) * a0 + Math.abs(delta) * price) / (Math.abs(q0) + Math.abs(delta));
|
||||
return { q: newQ, a: newA };
|
||||
}
|
||||
// Gegenrichtung
|
||||
if (Math.abs(delta) < Math.abs(q0)) return { q: q0 + delta, a: a0 };
|
||||
if (Math.abs(delta) === Math.abs(q0)) return { q: 0, a: 0 };
|
||||
return { q: q0 + delta, a: price }; // Flip
|
||||
}
|
||||
|
||||
/**
|
||||
* side: 'buy' | 'sell'. qty > 0. 'sell' kann Short eroeffnen.
|
||||
*/
|
||||
export function executeTrade(userId, symbol, side, qty) {
|
||||
const asset = getAsset(symbol);
|
||||
if (!asset) return { ok: false, error: 'Unbekanntes Symbol' };
|
||||
if (asset.halted) return { ok: false, error: `${symbol} ist pausiert (halted)` };
|
||||
qty = Number(qty);
|
||||
if (!(qty > 0)) return { ok: false, error: 'Menge muss > 0 sein' };
|
||||
|
||||
const user = Users.byId(userId);
|
||||
if (!user) return { ok: false, error: 'User nicht gefunden' };
|
||||
|
||||
const price = asset.price;
|
||||
const notional = qty * price;
|
||||
const delta = side === 'buy' ? qty : -qty;
|
||||
|
||||
const h = Holdings.get(userId, symbol) || { quantity: 0, avg_price: 0 };
|
||||
|
||||
let newBalance;
|
||||
if (side === 'buy') {
|
||||
if (user.balance < notional) return { ok: false, error: 'Nicht genug Guthaben' };
|
||||
newBalance = user.balance - notional;
|
||||
} else {
|
||||
// Verkauf bringt Cash. Short-Eroeffnung erlaubt, aber Sicherheit pruefen.
|
||||
const result = applyPosition(h.quantity, h.avg_price, delta, price);
|
||||
if (result.q < 0 && user.balance < Math.abs(result.q) * price * 0.5) {
|
||||
return { ok: false, error: 'Nicht genug Sicherheit fuer Short (50% Margin)' };
|
||||
}
|
||||
newBalance = user.balance + notional;
|
||||
}
|
||||
|
||||
const pos = applyPosition(h.quantity, h.avg_price, delta, price);
|
||||
Users.setBalance(userId, newBalance);
|
||||
if (Math.abs(pos.q) < 1e-9) Holdings.remove(userId, symbol);
|
||||
else Holdings.upsert(userId, symbol, pos.q, pos.a);
|
||||
Trades.add(userId, symbol, side, qty, price);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
symbol, side, qty, price: round(price),
|
||||
balance: round(newBalance),
|
||||
position: { quantity: round(pos.q, 6), avgPrice: round(pos.a) },
|
||||
};
|
||||
}
|
||||
|
||||
export function getPortfolio(userId) {
|
||||
const user = Users.byId(userId);
|
||||
if (!user) return null;
|
||||
const holdings = Holdings.forUser(userId).map(h => {
|
||||
const a = getAsset(h.symbol);
|
||||
const price = a ? a.price : h.avg_price;
|
||||
const value = h.quantity * price;
|
||||
const cost = h.quantity * h.avg_price;
|
||||
const pnl = value - cost; // funktioniert fuer Long & Short
|
||||
const pnlPct = cost !== 0 ? (pnl / Math.abs(cost)) * 100 : 0;
|
||||
return {
|
||||
symbol: h.symbol,
|
||||
name: a?.name || h.symbol,
|
||||
emoji: a?.emoji || '',
|
||||
quantity: round(h.quantity, 6),
|
||||
avgPrice: round(h.avg_price),
|
||||
price: round(price),
|
||||
value: round(value),
|
||||
pnl: round(pnl),
|
||||
pnlPct: round(pnlPct, 2),
|
||||
side: h.quantity >= 0 ? 'long' : 'short',
|
||||
};
|
||||
});
|
||||
const holdingsValue = holdings.reduce((s, h) => s + h.value, 0);
|
||||
return {
|
||||
balance: round(user.balance),
|
||||
holdingsValue: round(holdingsValue),
|
||||
netWorth: round(user.balance + holdingsValue),
|
||||
holdings,
|
||||
};
|
||||
}
|
||||
|
||||
function round(n, d = 2) {
|
||||
const f = Math.pow(10, d);
|
||||
return Math.round(n * f) / f;
|
||||
}
|
||||
Reference in New Issue
Block a user