167 lines
6.0 KiB
TypeScript
167 lines
6.0 KiB
TypeScript
/**
|
|
* 추세검색 앱 설정 (gc_app_settings.trend_search_settings_json)
|
|
*/
|
|
import type { TrendSearchRequest } from './backendApi';
|
|
import { DEFAULT_TREND_SEARCH_REQUEST } from './backendApi';
|
|
import {
|
|
BULLISH_WEIGHT_ITEMS,
|
|
DEFAULT_BULLISH_WEIGHTS,
|
|
type BullishWeightKey,
|
|
} from './trendSearchBullishWeights';
|
|
|
|
/** 결과 목록 표시: chart | summary(신호) */
|
|
export type TrendSearchDisplayMode = 'chart' | 'summary';
|
|
|
|
export interface TrendSearchAppSettings {
|
|
/** 결과 UI 표시 모드 (기본 summary) */
|
|
displayMode?: TrendSearchDisplayMode;
|
|
weightMaAlignment: number;
|
|
weightMaSlope: number;
|
|
weightAdxTrend: number;
|
|
weightMacdMomentum: number;
|
|
weightPricePosition: number;
|
|
minTrendScore: number;
|
|
/** 결과 목록 최대 개수 */
|
|
limit: number;
|
|
/** 스캔 대상 종목 수 (거래대금 상위) */
|
|
scanLimit: number;
|
|
/** 자동 갱신 ON (백엔드 스케줄러·화면 폴링) */
|
|
autoRefreshEnabled: boolean;
|
|
/** 자동 갱신 간격(초) */
|
|
autoRefreshSeconds: number;
|
|
/** 검색 후 상위 N위까지 투자대상 자동추가 */
|
|
autoAddTargetsEnabled: boolean;
|
|
autoAddTopRank: number;
|
|
/** 자동추가 시 추세점수(matchRate) 하한 */
|
|
autoAddMinScore: number;
|
|
}
|
|
|
|
export const DEFAULT_TREND_SEARCH_APP_SETTINGS: TrendSearchAppSettings = {
|
|
displayMode: 'summary',
|
|
weightMaAlignment: DEFAULT_BULLISH_WEIGHTS.weightMaAlignment,
|
|
weightMaSlope: DEFAULT_BULLISH_WEIGHTS.weightMaSlope,
|
|
weightAdxTrend: DEFAULT_BULLISH_WEIGHTS.weightAdxTrend,
|
|
weightMacdMomentum: DEFAULT_BULLISH_WEIGHTS.weightMacdMomentum,
|
|
weightPricePosition: DEFAULT_BULLISH_WEIGHTS.weightPricePosition,
|
|
minTrendScore: DEFAULT_TREND_SEARCH_REQUEST.minTrendScore,
|
|
limit: DEFAULT_TREND_SEARCH_REQUEST.limit,
|
|
scanLimit: DEFAULT_TREND_SEARCH_REQUEST.scanLimit,
|
|
autoRefreshEnabled: true,
|
|
autoRefreshSeconds: 3,
|
|
autoAddTargetsEnabled: false,
|
|
autoAddTopRank: 5,
|
|
autoAddMinScore: DEFAULT_TREND_SEARCH_REQUEST.minTrendScore,
|
|
};
|
|
|
|
/** 자동 갱신 간격 선택지 (초) */
|
|
export const TREND_SEARCH_REFRESH_OPTIONS = [3, 5, 10, 30, 60] as const;
|
|
|
|
export function resolveTrendSearchAppSettings(
|
|
raw?: Partial<TrendSearchAppSettings> | null,
|
|
): TrendSearchAppSettings {
|
|
const d = DEFAULT_TREND_SEARCH_APP_SETTINGS;
|
|
if (!raw || typeof raw !== 'object') return { ...d };
|
|
const sec = Number(raw.autoRefreshSeconds);
|
|
const rank = Number(raw.autoAddTopRank);
|
|
const limit = Number(raw.limit);
|
|
const scanLimit = Number(raw.scanLimit);
|
|
const displayMode = raw.displayMode === 'chart' || raw.displayMode === 'summary'
|
|
? raw.displayMode
|
|
: d.displayMode ?? 'summary';
|
|
return {
|
|
displayMode,
|
|
weightMaAlignment: clampWeight(raw.weightMaAlignment, d.weightMaAlignment),
|
|
weightMaSlope: clampWeight(raw.weightMaSlope, d.weightMaSlope),
|
|
weightAdxTrend: clampWeight(raw.weightAdxTrend, d.weightAdxTrend),
|
|
weightMacdMomentum: clampWeight(raw.weightMacdMomentum, d.weightMacdMomentum),
|
|
weightPricePosition: clampWeight(raw.weightPricePosition, d.weightPricePosition),
|
|
minTrendScore: clampInt(raw.minTrendScore, 0, 100, d.minTrendScore),
|
|
limit: clampInt(limit, 5, 50, d.limit),
|
|
scanLimit: clampInt(scanLimit, 20, 120, d.scanLimit),
|
|
autoRefreshEnabled: raw.autoRefreshEnabled !== false,
|
|
autoRefreshSeconds: TREND_SEARCH_REFRESH_OPTIONS.includes(sec as typeof TREND_SEARCH_REFRESH_OPTIONS[number])
|
|
? sec
|
|
: d.autoRefreshSeconds,
|
|
autoAddTargetsEnabled: raw.autoAddTargetsEnabled === true,
|
|
autoAddTopRank: clampInt(rank, 1, 50, d.autoAddTopRank),
|
|
autoAddMinScore: clampInt(raw.autoAddMinScore, 0, 100, d.autoAddMinScore),
|
|
};
|
|
}
|
|
|
|
function clampWeight(v: unknown, fallback: number): number {
|
|
const n = Number(v);
|
|
if (!Number.isFinite(n)) return fallback;
|
|
return Math.max(0, Math.min(100, Math.round(n / 5) * 5));
|
|
}
|
|
|
|
function clampInt(v: unknown, min: number, max: number, fallback: number): number {
|
|
const n = Number(v);
|
|
if (!Number.isFinite(n)) return fallback;
|
|
return Math.min(max, Math.max(min, Math.floor(n)));
|
|
}
|
|
|
|
export function mergeTrendSearchRequest(
|
|
base: TrendSearchRequest,
|
|
settings: TrendSearchAppSettings,
|
|
): TrendSearchRequest {
|
|
return {
|
|
...base,
|
|
weightMaAlignment: settings.weightMaAlignment,
|
|
weightMaSlope: settings.weightMaSlope,
|
|
weightAdxTrend: settings.weightAdxTrend,
|
|
weightMacdMomentum: settings.weightMacdMomentum,
|
|
weightPricePosition: settings.weightPricePosition,
|
|
minTrendScore: settings.minTrendScore,
|
|
limit: settings.limit,
|
|
scanLimit: settings.scanLimit,
|
|
};
|
|
}
|
|
|
|
/** 검색 요청 → 앱 설정에 반영할 필드 */
|
|
export function extractTrendSearchAppSettings(
|
|
req: Pick<TrendSearchRequest, BullishWeightKey | 'minTrendScore' | 'limit' | 'scanLimit'>,
|
|
): Pick<
|
|
TrendSearchAppSettings,
|
|
BullishWeightKey | 'minTrendScore' | 'limit' | 'scanLimit'
|
|
> {
|
|
return {
|
|
weightMaAlignment: req.weightMaAlignment,
|
|
weightMaSlope: req.weightMaSlope,
|
|
weightAdxTrend: req.weightAdxTrend,
|
|
weightMacdMomentum: req.weightMacdMomentum,
|
|
weightPricePosition: req.weightPricePosition,
|
|
minTrendScore: req.minTrendScore,
|
|
limit: req.limit,
|
|
scanLimit: req.scanLimit,
|
|
};
|
|
}
|
|
|
|
export function formatTrendRefreshLabel(seconds: number): string {
|
|
if (seconds < 60) return `${seconds}초`;
|
|
return `${seconds / 60}분`;
|
|
}
|
|
|
|
/** 설정 변경 감지용 (useEffect 의존성) */
|
|
export function trendSearchSettingsSignature(s: TrendSearchAppSettings): string {
|
|
return JSON.stringify(s);
|
|
}
|
|
|
|
/** 캐시/설정 → 추세검색 조건 패널 필드 병합 (timeframe 등 화면 전용 값 유지) */
|
|
export function mergeFiltersFromTrendSearchSettings(
|
|
filters: TrendSearchRequest,
|
|
settings: TrendSearchAppSettings,
|
|
): TrendSearchRequest {
|
|
return mergeTrendSearchRequest(filters, settings);
|
|
}
|
|
|
|
/** 추세검색 조건 변경 → 저장할 전체 추세설정 */
|
|
export function mergeTrendSearchSettingsFromRequest(
|
|
current: TrendSearchAppSettings,
|
|
req: Pick<TrendSearchRequest, BullishWeightKey | 'minTrendScore' | 'limit' | 'scanLimit'>,
|
|
): TrendSearchAppSettings {
|
|
return resolveTrendSearchAppSettings({
|
|
...current,
|
|
...extractTrendSearchAppSettings(req),
|
|
});
|
|
}
|