91 lines
3.2 KiB
TypeScript
91 lines
3.2 KiB
TypeScript
'use client';
|
||
|
||
import React, { useState, useMemo } from 'react';
|
||
|
||
interface KellyBetSizingProps {
|
||
trueProb: number; // 系統計算的真實勝率 (0.0 - 1.0)
|
||
decimalOdds: number; // 莊家賠率
|
||
}
|
||
|
||
export default function KellyBetSizing({ trueProb, decimalOdds }: KellyBetSizingProps) {
|
||
const [bankroll, setBankroll] = useState<number>(10000);
|
||
const [kellyFraction, setKellyFraction] = useState<number>(0.25); // 預設 1/4 凱利
|
||
|
||
// 核心計算邏輯 (套用凱利公式)
|
||
const suggestedStake = useMemo(() => {
|
||
const b = decimalOdds - 1.0;
|
||
const p = trueProb;
|
||
const q = 1.0 - p;
|
||
|
||
if (b <= 0) return 0;
|
||
|
||
const fullKellyPct = ((b * p) - q) / b;
|
||
if (fullKellyPct <= 0) return 0;
|
||
|
||
return Math.round(bankroll * fullKellyPct * kellyFraction);
|
||
}, [bankroll, kellyFraction, decimalOdds, trueProb]);
|
||
|
||
// 動態樣式判斷
|
||
const isHighRisk = kellyFraction > 0.5;
|
||
const highlightColor = isHighRisk ? 'text-quant-red' : 'text-quant-orange';
|
||
|
||
return (
|
||
<div className="w-full bg-stone-50 p-6 rounded-lg border border-stone-200 shadow-sm">
|
||
<div className="flex justify-between items-center border-b border-stone-200 pb-4 mb-6">
|
||
<div>
|
||
<h3 className="text-sm text-stone-500 uppercase tracking-wider font-semibold">
|
||
注碼風險控管
|
||
</h3>
|
||
<h2 className="text-xl text-stone-900 font-bold">量化注碼建議</h2>
|
||
</div>
|
||
<div className="text-right">
|
||
<p className="text-sm text-stone-500 mb-1">建議絕對金額</p>
|
||
<p className={`text-4xl font-dotmatrix transition-colors duration-300 ${highlightColor}`}>
|
||
$ {suggestedStake.toLocaleString()}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 控制區:總資金 */}
|
||
<div className="mb-6">
|
||
<label className="block text-sm text-stone-600 font-medium mb-2">
|
||
當前可用資金:<span className="font-dotmatrix ml-2 text-lg">${bankroll.toLocaleString()}</span>
|
||
</label>
|
||
<input
|
||
type="range"
|
||
min="1000"
|
||
max="100000"
|
||
step="1000"
|
||
value={bankroll}
|
||
onChange={(e) => setBankroll(Number(e.target.value))}
|
||
className="w-full h-2 bg-stone-200 rounded-lg appearance-none cursor-pointer accent-quant-orange"
|
||
/>
|
||
</div>
|
||
|
||
{/* 控制區:風險偏好 (分數凱利) */}
|
||
<div className="mb-2">
|
||
<label className="block text-sm text-stone-600 font-medium mb-2">
|
||
風險偏好:
|
||
<span className={`font-dotmatrix ml-2 text-lg ${isHighRisk ? 'text-quant-red' : 'text-stone-900'}`}>
|
||
{kellyFraction}x
|
||
</span>
|
||
</label>
|
||
<input
|
||
type="range"
|
||
min="0.1"
|
||
max="1.0"
|
||
step="0.05"
|
||
value={kellyFraction}
|
||
onChange={(e) => setKellyFraction(Number(e.target.value))}
|
||
className="w-full h-2 bg-stone-200 rounded-lg appearance-none cursor-pointer accent-stone-700"
|
||
/>
|
||
<div className="flex justify-between text-xs text-stone-400 mt-2 font-dotmatrix uppercase">
|
||
<span>保守 0.1x</span>
|
||
<span>一般 0.25x</span>
|
||
<span className="text-quant-red">高風險 1.0x</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|