알림목록 전략지표 없음 문제 수정
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user