File size: 2,760 Bytes
9e89154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
"use client";

import { useCallback, useState } from "react";

import { useLedgerSession } from "@/context/LedgerSessionContext";
import { optimizePortfolio } from "@/lib/api";
import { DEFAULT_TICKERS, DEFAULT_WEIGHT_MAX, DEFAULT_WEIGHT_MIN } from "@/lib/defaultUniverse";

export interface ScenarioResult {
  name: string;
  objective: string;
  sharpe: number;
  ret: number;
  vol: number;
  nActive: number;
}

const OBJECTIVES = [
  "hybrid",
  "markowitz",
  "hrp",
  "qubo_sa",
  "min_variance",
  "equal_weight",
] as const;

function objectiveLabel(obj: string): string {
  return obj
    .replace(/_/g, " ")
    .replace(/\b\w/g, (c) => c.toUpperCase());
}

function mapOptimizeRow(obj: string, resp: Record<string, unknown>): ScenarioResult {
  const inner = (resp.qsw_result as Record<string, unknown> | undefined) || resp;
  const sharpe = Number(inner.sharpe_ratio ?? 0);
  const ret = Number(inner.expected_return ?? 0) * 100;
  const vol = Number(inner.volatility ?? 0) * 100;
  const nActive = Number(inner.n_active ?? 0);
  return {
    name: objectiveLabel(obj),
    objective: obj,
    sharpe,
    ret,
    vol,
    nActive,
  };
}

export function useSimulationComparison() {
  const { session } = useLedgerSession();
  const [scenarios, setScenarios] = useState<ScenarioResult[]>([]);
  const [running, setRunning] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const runComparison = useCallback(async () => {
    setRunning(true);
    setError(null);
    const results: ScenarioResult[] = [];
    let failures = 0;

    const tickers =
      session.tickers.length > 0 ? session.tickers : [...DEFAULT_TICKERS];
    const wMin = session.constraints.weightMin ?? DEFAULT_WEIGHT_MIN;
    const wMax = session.constraints.weightMax ?? DEFAULT_WEIGHT_MAX;

    for (const obj of OBJECTIVES) {
      try {
        const resp = (await optimizePortfolio({
          tickers,
          objective: obj,
          weight_min: wMin,
          maxWeight: wMax,
        })) as Record<string, unknown>;
        results.push(mapOptimizeRow(obj, resp));
      } catch {
        failures += 1;
        results.push({
          name: objectiveLabel(obj),
          objective: obj,
          sharpe: 0,
          ret: 0,
          vol: 0,
          nActive: 0,
        });
      }
    }

    setScenarios(results);
    if (failures === OBJECTIVES.length) {
      setError(
        "All optimization calls failed. Is the API running and reachable?"
      );
    } else if (failures > 0) {
      setError(
        `${failures} of ${OBJECTIVES.length} objectives failed (see zeros in table).`
      );
    }
    setRunning(false);
  }, [session]);

  return {
    scenarios,
    running,
    error,
    runComparison,
  };
}