Closes #022 - Isolated crypto basis arbitrage bot integration
This commit is contained in:
@@ -100,6 +100,15 @@ interface Forecast {
|
||||
results?: Record<string, 'SUCCESS' | 'FAILURE'>;
|
||||
}
|
||||
|
||||
interface BasisArbitrageData {
|
||||
ticker: string;
|
||||
spotPrice: number;
|
||||
futuresPrice: number;
|
||||
basisSpread: number;
|
||||
fundingRate: number;
|
||||
basisApy: number;
|
||||
}
|
||||
|
||||
export default function CryptoDemo() {
|
||||
const { addModelTrial } = useSandboxStore();
|
||||
|
||||
@@ -126,6 +135,12 @@ export default function CryptoDemo() {
|
||||
const [isShieldActive, setIsShieldActive] = useState(true);
|
||||
const [coins, setCoins] = useState<Record<string, CoinData>>(defaultCoins);
|
||||
const [feedbackFilterAsset, setFeedbackFilterAsset] = useState<'BTC' | 'ETH' | 'SOL'>('BTC');
|
||||
const [rightColTab, setRightColTab] = useState<'radar' | 'basis'>('radar');
|
||||
const [basisData, setBasisData] = useState<BasisArbitrageData[]>([
|
||||
{ ticker: 'BTC', spotPrice: 69450, futuresPrice: 69505.5, basisSpread: 55.5, fundingRate: -0.015, basisApy: -15.15 },
|
||||
{ ticker: 'ETH', spotPrice: 3820, futuresPrice: 3824.58, basisSpread: 4.58, fundingRate: 0.045, basisApy: 63.60 },
|
||||
{ ticker: 'SOL', spotPrice: 184.20, futuresPrice: 184.54, basisSpread: 0.34, fundingRate: 0.082, basisApy: 145.45 }
|
||||
]);
|
||||
|
||||
// Safely load counters and forecasts from localStorage on client mount
|
||||
useEffect(() => {
|
||||
@@ -313,6 +328,24 @@ export default function CryptoDemo() {
|
||||
});
|
||||
return updatedCoins;
|
||||
});
|
||||
|
||||
const basisList: BasisArbitrageData[] = [];
|
||||
results.forEach((r: any) => {
|
||||
const cleanTicker = r.ticker.replace('-USD', '');
|
||||
if (cleanTicker === 'BTC' || cleanTicker === 'ETH' || cleanTicker === 'SOL') {
|
||||
basisList.push({
|
||||
ticker: cleanTicker,
|
||||
spotPrice: r.currentPrice || 0,
|
||||
futuresPrice: r.futuresPrice || 0,
|
||||
basisSpread: r.basisSpread || 0,
|
||||
fundingRate: r.fundingRate || 0,
|
||||
basisApy: r.basisApy || 0
|
||||
});
|
||||
}
|
||||
});
|
||||
if (basisList.length > 0) {
|
||||
setBasisData(basisList);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch crypto prices:", err);
|
||||
}
|
||||
@@ -796,60 +829,122 @@ export default function CryptoDemo() {
|
||||
{/* Right Column: Multi-Model Ensemble & Walk-Forward Radar Table */}
|
||||
<div className="bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-2xl p-6 text-slate-100 shadow-xl space-y-6">
|
||||
<div className="flex justify-between items-center border-b border-slate-800 pb-3">
|
||||
<h3 className="text-base font-bold text-white flex items-center gap-2">
|
||||
<Compass className="text-cyan-400 w-5 h-5" /> Walk-Forward Ensemble Radar
|
||||
</h3>
|
||||
{loadingEnsemble && (
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setRightColTab('radar')}
|
||||
className={`text-sm font-bold flex items-center gap-2 pb-1 transition-all ${rightColTab === 'radar' ? 'text-white border-b-2 border-cyan-500' : 'text-slate-400 hover:text-slate-200'}`}
|
||||
>
|
||||
<Compass className="text-cyan-400 w-4 h-4" /> Walk-Forward Ensemble Radar
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setRightColTab('basis')}
|
||||
className={`text-sm font-bold flex items-center gap-2 pb-1 transition-all ${rightColTab === 'basis' ? 'text-white border-b-2 border-cyan-500' : 'text-slate-400 hover:text-slate-200'}`}
|
||||
>
|
||||
<TrendingUp className="text-cyan-400 w-4 h-4" /> Basis Arbitrage Matrix
|
||||
</button>
|
||||
</div>
|
||||
{loadingEnsemble && rightColTab === 'radar' && (
|
||||
<RefreshCw className="w-4 h-4 text-cyan-400 animate-spin" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-slate-400 leading-relaxed">
|
||||
Displays predictions and live calibration metrics (<InlineMath math="E[\theta] = \alpha / (\alpha + \beta)" />) across 15 independent trackers.
|
||||
</div>
|
||||
{rightColTab === 'radar' ? (
|
||||
<>
|
||||
<div className="text-xs text-slate-400 leading-relaxed">
|
||||
Displays predictions and live calibration metrics (<InlineMath math="E[\theta] = \alpha / (\alpha + \beta)" />) across 15 independent trackers.
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto rounded-xl border border-slate-850 bg-slate-950/40">
|
||||
<table className="w-full border-collapse text-left text-[11px] font-mono">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-800 text-slate-400 font-semibold bg-slate-900/40">
|
||||
<th className="p-2">Estimator</th>
|
||||
<th className="p-2 text-center">T+1</th>
|
||||
<th className="p-2 text-center">T+5</th>
|
||||
<th className="p-2 text-center">T+10</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ESTIMATORS.map((est) => (
|
||||
<tr key={est.id} className="border-b border-slate-900 hover:bg-slate-850/10">
|
||||
<td className="p-2 font-semibold text-slate-300">{est.name}</td>
|
||||
{HORIZONS.map((h) => {
|
||||
const trackerKey = `${est.id}_${h.id}`;
|
||||
const tracker = trackers[trackerKey] || { alpha: 1, beta: 1 };
|
||||
const prob = getPredictionProb(activeTicker, est.id, h.id);
|
||||
const direction = prob > 0.5 ? 'UP' : 'DOWN';
|
||||
const expValue = tracker.alpha / (tracker.alpha + tracker.beta);
|
||||
|
||||
<div className="overflow-x-auto rounded-xl border border-slate-850 bg-slate-950/40">
|
||||
<table className="w-full border-collapse text-left text-[11px] font-mono">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-800 text-slate-400 font-semibold bg-slate-900/40">
|
||||
<th className="p-2">Estimator</th>
|
||||
<th className="p-2 text-center">T+1</th>
|
||||
<th className="p-2 text-center">T+5</th>
|
||||
<th className="p-2 text-center">T+10</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ESTIMATORS.map((est) => (
|
||||
<tr key={est.id} className="border-b border-slate-900 hover:bg-slate-850/10">
|
||||
<td className="p-2 font-semibold text-slate-300">{est.name}</td>
|
||||
{HORIZONS.map((h) => {
|
||||
const trackerKey = `${est.id}_${h.id}`;
|
||||
const tracker = trackers[trackerKey] || { alpha: 1, beta: 1 };
|
||||
const prob = getPredictionProb(activeTicker, est.id, h.id);
|
||||
const direction = prob > 0.5 ? 'UP' : 'DOWN';
|
||||
const expValue = tracker.alpha / (tracker.alpha + tracker.beta);
|
||||
|
||||
return (
|
||||
<td key={h.id} className="p-2 text-center border-l border-slate-900">
|
||||
<div className="flex flex-col items-center">
|
||||
<span className={`font-bold ${direction === 'UP' ? 'text-emerald-400' : 'text-rose-400'}`}>
|
||||
{direction === 'UP' ? '▲' : '▼'} {(prob * 100).toFixed(0)}%
|
||||
</span>
|
||||
<span className="text-[9px] text-slate-500 mt-0.5">
|
||||
{tracker.alpha}/{tracker.beta}
|
||||
</span>
|
||||
<span className="text-[9px] text-cyan-400 font-semibold mt-0.5">
|
||||
E: {(expValue * 100).toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-xs text-slate-400 leading-relaxed">
|
||||
Monitors basis spread and compounding funding rate APY (<InlineMath math="\text{APY} = (1 + F_{\text{8h}})^{1095} - 1" />) for cash-and-carry arbitrage.
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto rounded-xl border border-slate-850 bg-slate-950/40">
|
||||
<table className="w-full border-collapse text-left text-[11px] font-mono">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-800 text-slate-400 font-semibold bg-slate-900/40">
|
||||
<th className="p-2">Ticker</th>
|
||||
<th className="p-2">Spot</th>
|
||||
<th className="p-2">Futures</th>
|
||||
<th className="p-2 text-right">Spread</th>
|
||||
<th className="p-2 text-center">Funding</th>
|
||||
<th className="p-2 text-right">APY</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{basisData.map((data) => {
|
||||
const isPositive = data.basisSpread >= 0;
|
||||
const isPositiveApy = data.basisApy >= 0;
|
||||
return (
|
||||
<td key={h.id} className="p-2 text-center border-l border-slate-900">
|
||||
<div className="flex flex-col items-center">
|
||||
<span className={`font-bold ${direction === 'UP' ? 'text-emerald-400' : 'text-rose-400'}`}>
|
||||
{direction === 'UP' ? '▲' : '▼'} {(prob * 100).toFixed(0)}%
|
||||
</span>
|
||||
<span className="text-[9px] text-slate-500 mt-0.5">
|
||||
{tracker.alpha}/{tracker.beta}
|
||||
</span>
|
||||
<span className="text-[9px] text-cyan-400 font-semibold mt-0.5">
|
||||
E: {(expValue * 100).toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<tr key={data.ticker} className="border-b border-slate-900 hover:bg-slate-850/10">
|
||||
<td className="p-2 font-bold text-cyan-400">{data.ticker}</td>
|
||||
<td className="p-2 text-slate-300">
|
||||
${data.spotPrice.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
</td>
|
||||
<td className="p-2 text-slate-300">
|
||||
${data.futuresPrice.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
</td>
|
||||
<td className={`p-2 text-right font-semibold ${isPositive ? 'text-emerald-400' : 'text-rose-400'}`}>
|
||||
{isPositive ? '+' : ''}${data.basisSpread.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
</td>
|
||||
<td className={`p-2 text-center font-mono ${data.fundingRate >= 0 ? 'text-emerald-400' : 'text-rose-400'}`}>
|
||||
{data.fundingRate >= 0 ? '+' : ''}{data.fundingRate.toFixed(4)}%
|
||||
</td>
|
||||
<td className={`p-2 text-right font-bold ${isPositiveApy ? 'text-emerald-400' : 'text-rose-400'}`}>
|
||||
{isPositiveApy ? '+' : ''}{data.basisApy.toFixed(2)}%
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Model Calibration Log & Simulation */}
|
||||
<div className="p-4 rounded-xl border border-slate-850 bg-slate-950/40 space-y-3">
|
||||
|
||||
Reference in New Issue
Block a user