알림목록 전략지표 없음 문제 수정

This commit is contained in:
Macbook
2026-06-08 01:31:02 +09:00
parent 0229799383
commit 2f1a4d3bca
9 changed files with 230 additions and 36 deletions
@@ -9,22 +9,24 @@ import {
resolveNotificationStrategy,
normalizeStrategyId,
} from '../utils/resolveNotificationStrategy';
import { hydrateStrategyDto, normalizeMarketCode } from '../utils/strategyHydrate';
import {
fetchLiveSnapshot,
type VirtualIndicatorSnapshot,
} from './useVirtualIndicatorSnapshots';
const MAX_EMPTY_RETRIES = 10;
const MAX_EMPTY_RETRIES = 15;
function staticFallback(
market: string,
strategyId: number,
strategy: StrategyDto,
): VirtualIndicatorSnapshot | undefined {
const baseRows = extractVirtualConditions(strategy);
const hydrated = hydrateStrategyDto(strategy);
const baseRows = extractVirtualConditions(hydrated);
if (baseRows.length === 0) return undefined;
return {
market,
market: normalizeMarketCode(market),
strategyId,
timeframe: [...new Set(baseRows.map(r => r.timeframe))].join(', '),
rows: baseRows.map(r => normalizeConditionRow({ ...r, currentValue: null, satisfied: null })),
@@ -49,6 +51,7 @@ export function useTradeNotificationSignalSnapshot(
const refreshInFlightRef = useRef(false);
const resolvedStrategyRef = useRef<StrategyDto | undefined>();
const effectiveStrategyIdRef = useRef<number | null>(null);
const normalizedMarket = normalizeMarketCode(market);
const refresh = useCallback(async () => {
const rawId = normalizeStrategyId(strategyId);
@@ -65,14 +68,19 @@ export function useTradeNotificationSignalSnapshot(
setLoading(true);
try {
let effectiveStrategy = strategy ?? resolvedStrategyRef.current;
let effectiveStrategy = strategy ? hydrateStrategyDto(strategy) : resolvedStrategyRef.current;
let effectiveId = effectiveStrategy?.id ?? effectiveStrategyIdRef.current ?? rawId;
if (!effectiveStrategy || effectiveStrategy.id !== rawId) {
const needsResolve = !effectiveStrategy
|| extractVirtualConditions(effectiveStrategy).length === 0
|| effectiveStrategy.id !== rawId;
if (needsResolve) {
const resolved = await resolveNotificationStrategy(
rawId,
strategyName,
prefetchedStrategies,
normalizedMarket,
);
if (gen !== genRef.current) return;
if (resolved.strategy) {
@@ -86,11 +94,17 @@ export function useTradeNotificationSignalSnapshot(
}
const pinFirst = emptyRetryRef.current === 0;
let snap = await fetchLiveSnapshot(market, effectiveId, effectiveStrategy, pinFirst);
let snap = await fetchLiveSnapshot(
normalizedMarket,
effectiveId,
effectiveStrategy,
pinFirst,
{ readOnlyPin: true },
);
if (gen !== genRef.current) return;
if (!snap && effectiveStrategy) {
snap = staticFallback(market, effectiveId, effectiveStrategy) ?? null;
if ((!snap || snap.rows.length === 0) && effectiveStrategy) {
snap = staticFallback(normalizedMarket, effectiveId, effectiveStrategy) ?? snap;
}
if (snap && snap.rows.length > 0) {
@@ -108,7 +122,15 @@ export function useTradeNotificationSignalSnapshot(
} finally {
refreshInFlightRef.current = false;
}
}, [market, strategyId, strategy, strategyName, enabled, prefetchedStrategies]);
}, [
market,
normalizedMarket,
strategyId,
strategy,
strategyName,
enabled,
prefetchedStrategies,
]);
useEffect(() => {
const rawId = normalizeStrategyId(strategyId);
@@ -121,7 +143,7 @@ export function useTradeNotificationSignalSnapshot(
return;
}
emptyRetryRef.current = 0;
resolvedStrategyRef.current = strategy;
resolvedStrategyRef.current = strategy ? hydrateStrategyDto(strategy) : undefined;
effectiveStrategyIdRef.current = strategy?.id ?? rawId;
setLoading(true);
void refresh();
@@ -14,10 +14,11 @@ import {
pinCandleWatch,
type LiveConditionRowDto,
} from '../utils/backendApi';
import { pinStrategyEvaluationTimeframes } from '../utils/strategyTimeframePin';
import { pinStrategyEvaluationTimeframes, pinReadOnlyStrategyTimeframes } from '../utils/strategyTimeframePin';
import { formatIndicatorDisplayLabel } from '../utils/indicatorRegistry';
import { coerceFiniteNumber } from '../utils/safeFormat';
import { normalizeStrategyId } from '../utils/resolveNotificationStrategy';
import { hydrateStrategyDto, normalizeMarketCode } from '../utils/strategyHydrate';
import {
extractVirtualConditions,
type VirtualConditionRow,
@@ -113,51 +114,67 @@ function staticSnapshot(
}
function statusMatches(status: { market?: string; strategyId?: unknown }, market: string, strategyId: number): boolean {
if (!status?.market || status.market !== market) return false;
if (!status?.market) return false;
if (normalizeMarketCode(status.market) !== normalizeMarketCode(market)) return false;
const sid = normalizeStrategyId(status.strategyId);
const expected = normalizeStrategyId(strategyId);
return sid != null && expected != null && sid === expected;
}
export interface FetchLiveSnapshotOptions {
/** true — syncStrategyTimeframes·repair 생략 (알림 목록) */
readOnlyPin?: boolean;
}
/** 백엔드가 업비트 REST·WS로 캔들 warm-up 후 Ta4j 평가 */
export async function fetchLiveSnapshot(
market: string,
strategyId: number,
strategy: StrategyDto | undefined,
pinFirst: boolean,
options?: FetchLiveSnapshotOptions,
): Promise<VirtualIndicatorSnapshot | null> {
const baseRows = strategy ? extractVirtualConditions(strategy) : [];
const normalizedMarket = normalizeMarketCode(market);
const hydratedStrategy = strategy ? hydrateStrategyDto(strategy) : undefined;
const baseRows = hydratedStrategy ? extractVirtualConditions(hydratedStrategy) : [];
if (pinFirst) {
try {
await pinStrategyEvaluationTimeframes(market, strategyId);
if (options?.readOnlyPin) {
await pinReadOnlyStrategyTimeframes(normalizedMarket, strategyId, hydratedStrategy);
} else {
await pinStrategyEvaluationTimeframes(normalizedMarket, strategyId);
}
} catch {
try {
await pinCandleWatch(market, '1m');
await pinCandleWatch(normalizedMarket, '3m');
} catch { /* ignore */ }
try {
await pinCandleWatch(normalizedMarket, '1m');
} catch { /* ignore */ }
}
}
let status: Awaited<ReturnType<typeof fetchLiveConditionStatus>> = null;
try {
status = await fetchLiveConditionStatus(market, strategyId);
status = await fetchLiveConditionStatus(normalizedMarket, strategyId);
} catch {
status = null;
}
if (!status || !statusMatches(status, market, strategyId)) {
if (!status || !statusMatches(status, normalizedMarket, strategyId)) {
if (baseRows.length === 0) return null;
return staticSnapshot(market, strategyId, baseRows);
return staticSnapshot(normalizedMarket, strategyId, baseRows);
}
const rows = mergeRows(baseRows, status.rows ?? []);
if (rows.length === 0) {
if (baseRows.length === 0) return null;
return staticSnapshot(market, strategyId, baseRows);
return staticSnapshot(normalizedMarket, strategyId, baseRows);
}
return {
market,
market: normalizedMarket,
strategyId,
timeframe: status.timeframe || [...new Set(baseRows.map(r => r.timeframe))].join(', '),
rows,