매매 시그널 알림 화면 수정
This commit is contained in:
@@ -36,6 +36,11 @@ import { resolveChartLegendOptions } from '../types/chartLegend';
|
||||
import type { ChartLegendVisibility } from '../types/chartLegend';
|
||||
import { DEFAULT_MAIN_CHART_STYLE } from '../utils/storage';
|
||||
import { DEFAULT_SYNC, type SyncOptions } from '../utils/layoutTypes';
|
||||
import {
|
||||
DEFAULT_CHART_TIME_FORMAT,
|
||||
normalizeChartTimeFormat,
|
||||
setChartTimeFormat,
|
||||
} from '../utils/chartTimeFormat';
|
||||
import { DEFAULT_DISPLAY_TIMEZONE, normalizeTimezone, setDisplayTimezone } from '../utils/timezone';
|
||||
import {
|
||||
clampVirtualTargetMax,
|
||||
@@ -170,6 +175,7 @@ export function resolveAppDefaults(s: AppSettingsDto) {
|
||||
liveAutoTradeBudgetPct: s.liveAutoTradeBudgetPct ?? 95,
|
||||
fcmPushEnabled: s.fcmPushEnabled ?? false,
|
||||
displayTimezone: normalizeTimezone(s.displayTimezone ?? DEFAULT_DISPLAY_TIMEZONE),
|
||||
chartTimeFormat: normalizeChartTimeFormat(s.chartTimeFormat ?? DEFAULT_CHART_TIME_FORMAT),
|
||||
trendSearchSettings: resolveTrendSearchAppSettings(
|
||||
s.trendSearchSettings as Partial<TrendSearchAppSettings> | null | undefined,
|
||||
),
|
||||
@@ -194,6 +200,7 @@ export function useAppSettings(sessionKey = 0) {
|
||||
if (_cache !== null) {
|
||||
setSettings(_cache);
|
||||
setDisplayTimezone(normalizeTimezone(_cache.displayTimezone ?? DEFAULT_DISPLAY_TIMEZONE));
|
||||
setChartTimeFormat(normalizeChartTimeFormat(_cache.chartTimeFormat ?? DEFAULT_CHART_TIME_FORMAT));
|
||||
setIsLoaded(true);
|
||||
} else {
|
||||
setIsLoaded(false);
|
||||
@@ -202,6 +209,7 @@ export function useAppSettings(sessionKey = 0) {
|
||||
} else if (_cache !== null) {
|
||||
setSettings(_cache);
|
||||
setDisplayTimezone(normalizeTimezone(_cache.displayTimezone ?? DEFAULT_DISPLAY_TIMEZONE));
|
||||
setChartTimeFormat(normalizeChartTimeFormat(_cache.chartTimeFormat ?? DEFAULT_CHART_TIME_FORMAT));
|
||||
setIsLoaded(true);
|
||||
} else {
|
||||
setIsLoaded(false);
|
||||
@@ -211,6 +219,7 @@ export function useAppSettings(sessionKey = 0) {
|
||||
if (mountedRef.current) {
|
||||
setSettings(data);
|
||||
setDisplayTimezone(normalizeTimezone(data.displayTimezone ?? DEFAULT_DISPLAY_TIMEZONE));
|
||||
setChartTimeFormat(normalizeChartTimeFormat(data.chartTimeFormat ?? DEFAULT_CHART_TIME_FORMAT));
|
||||
setIsLoaded(true);
|
||||
}
|
||||
}).catch(err => {
|
||||
@@ -238,6 +247,9 @@ export function useAppSettings(sessionKey = 0) {
|
||||
if (patch.displayTimezone != null) {
|
||||
setDisplayTimezone(normalizeTimezone(patch.displayTimezone));
|
||||
}
|
||||
if (patch.chartTimeFormat != null) {
|
||||
setChartTimeFormat(normalizeChartTimeFormat(patch.chartTimeFormat));
|
||||
}
|
||||
saveAppSettings(patch).then(updated => {
|
||||
if (updated) {
|
||||
_cache = { ...(_cache ?? {}), ...updated };
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useLayoutEffect, useRef } from 'react';
|
||||
|
||||
/**
|
||||
* 신호등+하단 %/문구(.vtd-sig-light-rail) 높이를 측정해
|
||||
* 이퀄라이저 pane 눈금·막대 높이(--vtd-sig-rail-height)와 동기화
|
||||
*/
|
||||
export function useSignalVisualRailHeight(deps: unknown[] = []) {
|
||||
const visualRef = useRef<HTMLDivElement>(null);
|
||||
const lightRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const visual = visualRef.current;
|
||||
const light = lightRef.current;
|
||||
if (!visual || !light) return;
|
||||
|
||||
const sync = () => {
|
||||
const h = Math.ceil(light.getBoundingClientRect().height);
|
||||
if (h > 0) {
|
||||
visual.style.setProperty('--vtd-sig-rail-height', `${h}px`);
|
||||
}
|
||||
};
|
||||
|
||||
sync();
|
||||
const ro = new ResizeObserver(sync);
|
||||
ro.observe(light);
|
||||
window.addEventListener('resize', sync);
|
||||
return () => {
|
||||
ro.disconnect();
|
||||
window.removeEventListener('resize', sync);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, deps);
|
||||
|
||||
return { visualRef, lightRef };
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 매매 시그널 알림 목록 행 — 종목×전략 실시간 신호 스냅샷 (가상매매 카드 요약형과 동일 소스)
|
||||
*/
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { StrategyDto } from '../utils/backendApi';
|
||||
import {
|
||||
fetchLiveSnapshot,
|
||||
type VirtualIndicatorSnapshot,
|
||||
} from './useVirtualIndicatorSnapshots';
|
||||
|
||||
export function useTradeNotificationSignalSnapshot(
|
||||
market: string,
|
||||
strategyId: number | null | undefined,
|
||||
strategy: StrategyDto | undefined,
|
||||
enabled: boolean,
|
||||
pollMs = 3000,
|
||||
): { snapshot: VirtualIndicatorSnapshot | undefined; loading: boolean } {
|
||||
const [snapshot, setSnapshot] = useState<VirtualIndicatorSnapshot | undefined>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const emptyRetryRef = useRef(0);
|
||||
const genRef = useRef(0);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (!enabled || strategyId == null) {
|
||||
setSnapshot(undefined);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const gen = ++genRef.current;
|
||||
setLoading(true);
|
||||
const pinFirst = emptyRetryRef.current === 0;
|
||||
const snap = await fetchLiveSnapshot(market, strategyId, strategy, pinFirst);
|
||||
if (gen !== genRef.current) return;
|
||||
|
||||
if (snap && snap.rows.length > 0) {
|
||||
emptyRetryRef.current = 0;
|
||||
} else {
|
||||
emptyRetryRef.current += 1;
|
||||
}
|
||||
|
||||
setSnapshot(snap ?? undefined);
|
||||
setLoading(false);
|
||||
}, [market, strategyId, strategy, enabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || strategyId == null) {
|
||||
setSnapshot(undefined);
|
||||
setLoading(false);
|
||||
emptyRetryRef.current = 0;
|
||||
return;
|
||||
}
|
||||
emptyRetryRef.current = 0;
|
||||
void refresh();
|
||||
const id = window.setInterval(() => void refresh(), pollMs);
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
genRef.current += 1;
|
||||
};
|
||||
}, [market, strategyId, enabled, pollMs, refresh]);
|
||||
|
||||
return { snapshot, loading };
|
||||
}
|
||||
@@ -108,7 +108,7 @@ function staticSnapshot(
|
||||
}
|
||||
|
||||
/** 백엔드가 업비트 REST·WS로 캔들 warm-up 후 Ta4j 평가 */
|
||||
async function fetchLiveSnapshot(
|
||||
export async function fetchLiveSnapshot(
|
||||
market: string,
|
||||
strategyId: number,
|
||||
strategy: StrategyDto | undefined,
|
||||
|
||||
Reference in New Issue
Block a user