feat: risikobasiertes Position-Sizing mit Caps

This commit is contained in:
2026-06-09 20:30:16 +00:00
parent d54b11293d
commit 7d576c61c5
2 changed files with 57 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
import { expect, test } from 'bun:test';
import { sizePosition, DEFAULT_RISK } from './sizing';
test('1% Equity-Risiko bestimmt die Größe', () => {
// Equity 1000, Risiko 10 USDT; Entry 100, Stop 94 → 6 USDT Risiko/Einheit → qty 10/6
const r = sizePosition(1000, 1000, 100, 94, DEFAULT_RISK);
expect(r.qty).toBeCloseTo(10 / 6);
expect(r.notional).toBeCloseTo((10 / 6) * 100);
expect(r.blockedBy).toBeNull();
});
test('Cap bei 30% der Equity', () => {
// enger Stop: Entry 100, Stop 99.5 → ungecappt 2000 USDT Notional → Cap 300
const r = sizePosition(1000, 1000, 100, 99.5, DEFAULT_RISK);
expect(r.notional).toBeCloseTo(300);
});
test('Cap durch verfügbares Cash', () => {
const r = sizePosition(1000, 100, 100, 99.5, DEFAULT_RISK);
expect(r.notional).toBeLessThanOrEqual(100);
});
test('blockiert unter Mindestordergröße', () => {
const r = sizePosition(1000, 5, 100, 94, DEFAULT_RISK);
expect(r.qty).toBe(0);
expect(r.blockedBy).toBe('min_notional');
});

View File

@@ -0,0 +1,30 @@
export interface RiskConfig {
riskPerTradePct: number; // 0.01 = 1% der Equity
maxPositionPct: number; // 0.30
minNotionalUsdt: number; // 10
}
export const DEFAULT_RISK: RiskConfig = { riskPerTradePct: 0.01, maxPositionPct: 0.3, minNotionalUsdt: 10 };
export interface SizingResult {
qty: number;
notional: number;
riskAmount: number;
blockedBy: 'min_notional' | null;
}
export function sizePosition(
equity: number,
cash: number,
entryPrice: number,
stopPrice: number,
cfg: RiskConfig,
): SizingResult {
const riskAmount = equity * cfg.riskPerTradePct;
const stopDist = entryPrice - stopPrice;
let notional = (riskAmount / stopDist) * entryPrice;
// 0.997: Puffer für Fee (0.1%) + Slippage (0.05%) auf der Entry-Seite
notional = Math.min(notional, equity * cfg.maxPositionPct, cash * 0.997);
if (!(notional >= cfg.minNotionalUsdt)) return { qty: 0, notional: 0, riskAmount, blockedBy: 'min_notional' };
return { qty: notional / entryPrice, notional, riskAmount, blockedBy: null };
}