Initial commit with 2026 World Cup Quant Platform core modules and CI/CD

This commit is contained in:
QuantBot
2026-06-13 23:18:18 +08:00
commit 073abf98c1
155 changed files with 19539 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
'use client';
import { FormEvent, useEffect, useState } from 'react';
import {
analyzePortfolioLeaks,
type PortfolioLeaksRequestPayload,
type PortfolioLeaksResponse,
} from '@/lib/analytics-api';
import { BettingLeaksDashboard } from '@/components/BettingLeaksDashboard';
const DEFAULT_BETS = [
{
market_type: '1x2',
parlay_type: 'single',
odds: 2.7,
stake: 120,
recommended_odds: 2.7,
closing_odds: 2.2,
is_settled: true,
is_win: true,
stage: '小組賽',
},
{
market_type: '1x2',
parlay_type: 'single',
odds: 2.9,
stake: 120,
recommended_odds: 2.9,
closing_odds: 3.4,
is_settled: true,
is_win: false,
stage: '小組賽',
},
{
market_type: '大小球',
parlay_type: 'single',
odds: 1.82,
stake: 80,
recommended_odds: 1.82,
closing_odds: 1.88,
is_settled: true,
is_win: false,
stage: '小組賽',
},
{
market_type: '讓球',
parlay_type: 'parlay',
odds: 2.05,
stake: 60,
recommended_odds: 2.05,
closing_odds: 2.3,
is_settled: true,
is_win: false,
stage: '淘汰賽',
},
{
market_type: '1x2',
parlay_type: 'single',
odds: 3.2,
stake: 100,
recommended_odds: 3.2,
closing_odds: 2.8,
is_settled: true,
is_win: true,
stage: '淘汰賽',
},
];
const EMPTY_REPORT: PortfolioLeaksResponse = {
total_bet_count: 0,
settled_bet_count: 0,
total_stake: 0,
total_pnl: 0,
overall_roi_percent: 0,
overall_hit_rate_percent: 0,
clusters: [],
hard_truths: [],
};
export default function PortfolioPage() {
const [rawBetsText, setRawBetsText] = useState(JSON.stringify(DEFAULT_BETS, null, 2));
const [report, setReport] = useState<PortfolioLeaksResponse>(EMPTY_REPORT);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState('');
async function loadBets(payload: PortfolioLeaksRequestPayload) {
setLoading(true);
setMessage('');
try {
const result = await analyzePortfolioLeaks(payload);
setReport(result);
} catch (error) {
setMessage(error instanceof Error ? error.message : '注單分析暫時中斷');
setReport(EMPTY_REPORT);
} finally {
setLoading(false);
}
}
useEffect(() => {
loadBets({ user_bets: DEFAULT_BETS }).catch(() => {
// noop
});
}, []);
async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
let parsed: PortfolioLeaksRequestPayload;
try {
const parsedBets = JSON.parse(rawBetsText);
if (!Array.isArray(parsedBets)) {
throw new Error('請用陣列格式貼入投注明細');
}
parsed = { user_bets: parsedBets as PortfolioLeaksRequestPayload['user_bets'] };
} catch (error) {
setMessage(error instanceof Error ? error.message : 'JSON 格式無法解析');
return;
}
await loadBets(parsed);
}
return (
<div className="space-y-4">
<h2 className="dot-matrix text-2xl text-[#7d2a15]">Betting Leaks Analyzer</h2>
<section className="panel-glow rounded-2xl p-4">
<h3 className="dot-matrix text-lg text-[#7d2a15]"></h3>
<p className="mt-2 text-sm text-[#7a5b46]">
/ ROI/CLV
</p>
<form className="mt-3" onSubmit={onSubmit}>
<label className="text-sm text-[#7a5b46]">
JSON
<textarea
className="mt-2 h-44 w-full rounded-lg border border-[#e0c6a8] bg-white/80 px-3 py-2 font-mono text-xs"
value={rawBetsText}
onChange={(event) => setRawBetsText(event.target.value)}
/>
</label>
<div className="mt-3 flex gap-2">
<button
type="submit"
className="rounded-lg bg-[#7d2a15] px-4 py-2 text-sm text-white"
disabled={loading}
>
{loading ? '分析中…' : '重新分析'}
</button>
<button
type="button"
className="rounded-lg bg-white/90 px-4 py-2 text-sm text-[#7d2a15]"
onClick={() => setRawBetsText(JSON.stringify(DEFAULT_BETS, null, 2))}
>
</button>
</div>
</form>
{message ? <p className="mt-3 text-sm text-[#8c2f2f]">{message}</p> : null}
</section>
<BettingLeaksDashboard data={report} />
</div>
);
}