feat(stock): /stock/screener 페이지 골격 + hooks 4개 + 컴포넌트 stub 6개

This commit is contained in:
2026-05-12 14:15:36 +09:00
parent cd6072727f
commit bc2c020f71
12 changed files with 284 additions and 5 deletions

View File

@@ -0,0 +1,32 @@
import { useEffect, useState } from 'react';
import { listScreenerRuns, getScreenerRun } from '../../../../api';
export function useScreenerHistory() {
const [runs, setRuns] = useState([]);
const [loading, setLoading] = useState(true);
const [selectedRun, setSelectedRun] = useState(null);
useEffect(() => {
listScreenerRuns(30).then((r) => { setRuns(r); setLoading(false); });
}, []);
async function selectRun(id) {
if (!id) { setSelectedRun(null); return; }
const detail = await getScreenerRun(id);
setSelectedRun({
asof: detail.meta.asof,
mode: detail.meta.mode,
status: detail.meta.status,
run_id: detail.meta.id,
survivors_count: detail.meta.survivors_count,
weights: detail.meta.weights,
top_n: detail.meta.top_n,
results: detail.results,
telegram_payload: null,
warnings: [],
meta: detail.meta,
});
}
return { runs, runs_loading: loading, selectedRun, selectRun };
}

View File

@@ -0,0 +1,11 @@
import { useEffect, useState } from 'react';
import { getScreenerNodes } from '../../../../api';
export function useScreenerMeta() {
const [meta, setMeta] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
getScreenerNodes().then((m) => { setMeta(m); setLoading(false); });
}, []);
return { meta, loading };
}

View File

@@ -0,0 +1,31 @@
import { useState } from 'react';
import { runScreener } from '../../../../api';
export function useScreenerRun() {
const [result, setResult] = useState(null);
const [running, setRunning] = useState(false);
async function call(mode, settings) {
setRunning(true);
try {
const body = {
mode,
weights: settings.weights,
node_params: settings.node_params,
gate_params: settings.gate_params,
top_n: settings.top_n,
};
const r = await runScreener(body);
setResult(r);
return r;
} finally {
setRunning(false);
}
}
return {
result, running,
runPreview: (s) => call('preview', s),
runSave: (s) => call('manual_save', s),
};
}

View File

@@ -0,0 +1,26 @@
import { useEffect, useState } from 'react';
import { getScreenerSettings, saveScreenerSettings } from '../../../../api';
export function useScreenerSettings() {
const [remote, setRemote] = useState(null);
const [local, setLocal] = useState(null);
useEffect(() => {
getScreenerSettings().then((s) => { setRemote(s); setLocal(s); });
}, []);
const dirty = remote && local && JSON.stringify(remote) !== JSON.stringify(local);
async function save() {
if (!local) return;
const saved = await saveScreenerSettings({
weights: local.weights, node_params: local.node_params, gate_params: local.gate_params,
top_n: local.top_n, rr_ratio: local.rr_ratio,
atr_window: local.atr_window, atr_stop_mult: local.atr_stop_mult,
});
setRemote(saved);
setLocal(saved);
}
return { settings: local, dirty, setLocal, save };
}