192 lines
5.5 KiB
TypeScript
192 lines
5.5 KiB
TypeScript
/** 추세검색 조건 정의 — 정배열 / 상승세 / 거래량 / 보조지표 */
|
||
export type TrendConditionMode = 'filter';
|
||
|
||
export type TrendCategoryKey =
|
||
| 'maAlignEnabled'
|
||
| 'trendEnabled'
|
||
| 'volumePowerEnabled'
|
||
| 'indicatorEnabled';
|
||
|
||
export interface TrendConditionDef {
|
||
id: string;
|
||
category: 'ma' | 'trend' | 'volume' | 'indicator';
|
||
categoryLabel: string;
|
||
label: string;
|
||
desc: string;
|
||
mode: TrendConditionMode;
|
||
requestKey: keyof import('./backendApi').TrendSearchRequest;
|
||
categoryKey: TrendCategoryKey;
|
||
/** 조건 파라미터 UI */
|
||
params?: 'maConvergencePct' | 'ma20SlopeBars' | 'newHighBreakout' | 'volumeVsPrior' | 'minTurnover' | 'rsiBand' | 'adxMin';
|
||
}
|
||
|
||
export const TREND_CONDITION_DEFS: TrendConditionDef[] = [
|
||
// Ⅰ. 정배열
|
||
{
|
||
id: 'priceAboveMa',
|
||
category: 'ma',
|
||
categoryLabel: 'Ⅰ. 정배열',
|
||
label: '주가 > 5·20·60 이평',
|
||
desc: '종가가 5·20·60일 이동평균선 위',
|
||
mode: 'filter',
|
||
requestKey: 'priceAboveMa',
|
||
categoryKey: 'maAlignEnabled',
|
||
},
|
||
{
|
||
id: 'maAlignment',
|
||
category: 'ma',
|
||
categoryLabel: 'Ⅰ. 정배열',
|
||
label: '5 > 20 > 60 > 120 정배열',
|
||
desc: '단기 이평이 장기 이평 위에 배열',
|
||
mode: 'filter',
|
||
requestKey: 'maAlignment',
|
||
categoryKey: 'maAlignEnabled',
|
||
},
|
||
{
|
||
id: 'maConvergence',
|
||
category: 'ma',
|
||
categoryLabel: 'Ⅰ. 정배열',
|
||
label: '이평밀집도 수렴 (선택)',
|
||
desc: '5·20·60 이평 간격이 N% 이내 (확산 전 포착)',
|
||
mode: 'filter',
|
||
requestKey: 'maConvergence',
|
||
categoryKey: 'maAlignEnabled',
|
||
params: 'maConvergencePct',
|
||
},
|
||
// Ⅱ. 상승세
|
||
{
|
||
id: 'ma20SlopeUp',
|
||
category: 'trend',
|
||
categoryLabel: 'Ⅱ. 상승세',
|
||
label: '20일 이평 연속 상승',
|
||
desc: '20일선(생명선) N봉 연속 우상향',
|
||
mode: 'filter',
|
||
requestKey: 'ma20SlopeUp',
|
||
categoryKey: 'trendEnabled',
|
||
params: 'ma20SlopeBars',
|
||
},
|
||
{
|
||
id: 'newHighBreakout',
|
||
category: 'trend',
|
||
categoryLabel: 'Ⅱ. 상승세',
|
||
label: '신고가 돌파·근접',
|
||
desc: 'N일 고가 돌파 또는 고가 대비 -M% 이내',
|
||
mode: 'filter',
|
||
requestKey: 'newHighBreakout',
|
||
categoryKey: 'trendEnabled',
|
||
params: 'newHighBreakout',
|
||
},
|
||
{
|
||
id: 'ichimokuAboveCloud',
|
||
category: 'trend',
|
||
categoryLabel: 'Ⅱ. 상승세',
|
||
label: '일목 구름대 상단',
|
||
desc: '주가 > 선행스팬1 · 선행스팬2',
|
||
mode: 'filter',
|
||
requestKey: 'ichimokuAboveCloud',
|
||
categoryKey: 'trendEnabled',
|
||
},
|
||
// Ⅲ. 돈과 힘
|
||
{
|
||
id: 'volumeVsPrior200',
|
||
category: 'volume',
|
||
categoryLabel: 'Ⅲ. 돈과 힘',
|
||
label: '직전 봉 대비 거래량',
|
||
desc: '일봉=전일, 분봉=직전 봉 대비 거래량 %',
|
||
mode: 'filter',
|
||
requestKey: 'volumeVsPrior200',
|
||
categoryKey: 'volumePowerEnabled',
|
||
params: 'volumeVsPrior',
|
||
},
|
||
{
|
||
id: 'minTurnover5dAvg',
|
||
category: 'volume',
|
||
categoryLabel: 'Ⅲ. 돈과 힘',
|
||
label: '5일 평균 거래대금',
|
||
desc: '최근 5일 평균 거래대금 하한 (억 원)',
|
||
mode: 'filter',
|
||
requestKey: 'minTurnover5dAvg',
|
||
categoryKey: 'volumePowerEnabled',
|
||
params: 'minTurnover',
|
||
},
|
||
{
|
||
id: 'volMa5Over20',
|
||
category: 'volume',
|
||
categoryLabel: 'Ⅲ. 돈과 힘',
|
||
label: '거래량 이평 정배열',
|
||
desc: '5일 거래량 이평 > 20일 거래량 이평',
|
||
mode: 'filter',
|
||
requestKey: 'volMa5Over20',
|
||
categoryKey: 'volumePowerEnabled',
|
||
},
|
||
// Ⅳ. 보조지표
|
||
{
|
||
id: 'macdGoldenCross',
|
||
category: 'indicator',
|
||
categoryLabel: 'Ⅳ. 보조지표',
|
||
label: 'MACD 골든크로스',
|
||
desc: 'MACD Line > Signal 또는 MACD > 0',
|
||
mode: 'filter',
|
||
requestKey: 'macdGoldenCross',
|
||
categoryKey: 'indicatorEnabled',
|
||
},
|
||
{
|
||
id: 'rsiBand',
|
||
category: 'indicator',
|
||
categoryLabel: 'Ⅳ. 보조지표',
|
||
label: 'RSI 구간',
|
||
desc: '과열·침체 구간 제외 (기본 50~70)',
|
||
mode: 'filter',
|
||
requestKey: 'rsiBand',
|
||
categoryKey: 'indicatorEnabled',
|
||
params: 'rsiBand',
|
||
},
|
||
{
|
||
id: 'dmiBullish',
|
||
category: 'indicator',
|
||
categoryLabel: 'Ⅳ. 보조지표',
|
||
label: '+DI > -DI · ADX',
|
||
desc: '추세 강도 ADX 하한 이상',
|
||
mode: 'filter',
|
||
requestKey: 'dmiBullish',
|
||
categoryKey: 'indicatorEnabled',
|
||
params: 'adxMin',
|
||
},
|
||
];
|
||
|
||
export const TREND_CATEGORY_ORDER: {
|
||
key: TrendCategoryKey;
|
||
label: string;
|
||
cat: TrendConditionDef['category'];
|
||
en: string;
|
||
}[] = [
|
||
{ key: 'maAlignEnabled', label: 'Ⅰ. 정배열', cat: 'ma', en: 'Moving Average Alignment' },
|
||
{ key: 'trendEnabled', label: 'Ⅱ. 상승세', cat: 'trend', en: 'Trend & Momentum' },
|
||
{ key: 'volumePowerEnabled', label: 'Ⅲ. 돈과 힘', cat: 'volume', en: 'Volume & Liquidity' },
|
||
{ key: 'indicatorEnabled', label: 'Ⅳ. 보조지표', cat: 'indicator', en: 'Indicators' },
|
||
];
|
||
|
||
/** 카테고리 OFF 시 하위 조건까지 함께 끄기 위한 패치 */
|
||
export function buildCategoryTogglePatch(
|
||
catKey: TrendCategoryKey,
|
||
on: boolean,
|
||
): Partial<import('./backendApi').TrendSearchRequest> {
|
||
const patch: Partial<import('./backendApi').TrendSearchRequest> = { [catKey]: on };
|
||
if (!on) {
|
||
for (const def of TREND_CONDITION_DEFS.filter(d => d.categoryKey === catKey)) {
|
||
patch[def.requestKey] = false as never;
|
||
}
|
||
}
|
||
return patch;
|
||
}
|
||
|
||
export function countActiveTrendFilters(
|
||
filters: import('./backendApi').TrendSearchRequest,
|
||
): number {
|
||
let n = 0;
|
||
for (const def of TREND_CONDITION_DEFS) {
|
||
if (filters[def.categoryKey] && filters[def.requestKey]) n++;
|
||
}
|
||
return n;
|
||
}
|