횡보컨트롤 배경색 적용

This commit is contained in:
Macbook
2026-06-12 11:08:58 +09:00
parent 981f09cf29
commit 9208418c33
9 changed files with 538 additions and 118 deletions
+27 -26
View File
@@ -5,6 +5,7 @@
import type { LogicNode } from './strategyTypes';
import type { DefType } from './strategyEditorShared';
import { genId } from './strategyNodeIds';
import type { SidewaysFilterPaletteEntry } from './sidewaysFilterPaletteStorage';
import { THRESHOLD_HL_MID, THRESHOLD_HL_UNDER } from './thresholdSymbols';
export type SidewaysFilterMode = 'include' | 'exclude';
@@ -28,7 +29,9 @@ export interface SidewaysFilterItem {
category: SidewaysFilterCategory;
/** include=횡보 구간만, exclude=횡보 제외(추세만) */
mode: SidewaysFilterMode;
build: (def: DefType) => LogicNode;
builtIn?: boolean;
/** 모드(NOT) 적용 전 내부 조건 트리 */
buildInner: (def: DefType) => LogicNode;
}
export const SIDEWAYS_FILTER_CATEGORY_LABEL: Record<SidewaysFilterCategory, string> = {
@@ -158,14 +161,6 @@ function dcNarrow(period: number, threshold: number): LogicNode {
);
}
function buildInclude(inner: LogicNode): LogicNode {
return inner;
}
function buildExclude(inner: LogicNode): LogicNode {
return notNode(inner);
}
function item(
id: string,
label: string,
@@ -180,13 +175,25 @@ function item(
desc,
category,
mode,
build: def => {
const node = typeof inner === 'function' ? inner(def) : inner;
return mode === 'exclude' ? buildExclude(node) : buildInclude(node);
},
builtIn: true,
buildInner: def => (typeof inner === 'function' ? inner(def) : inner),
};
}
export function applySidewaysFilterMode(inner: LogicNode, mode: SidewaysFilterMode): LogicNode {
return mode === 'exclude' ? notNode(inner) : inner;
}
export function buildSidewaysFilterLogic(
templateId: string,
mode: SidewaysFilterMode,
def: DefType,
): LogicNode | null {
const tpl = BUILTIN_FILTER_MAP.get(templateId);
if (!tpl) return null;
return applySidewaysFilterMode(tpl.buildInner(def), mode);
}
/** 내장 횡보 필터 목록 */
export const DEFAULT_SIDEWAYS_FILTER_ITEMS: SidewaysFilterItem[] = [
// ── ADX ──
@@ -299,23 +306,17 @@ export const DEFAULT_SIDEWAYS_FILTER_ITEMS: SidewaysFilterItem[] = [
)),
];
const FILTER_MAP = new Map(DEFAULT_SIDEWAYS_FILTER_ITEMS.map(i => [i.id, i]));
const BUILTIN_FILTER_MAP = new Map(DEFAULT_SIDEWAYS_FILTER_ITEMS.map(i => [i.id, i]));
export function getSidewaysFilterItem(id: string): SidewaysFilterItem | undefined {
return FILTER_MAP.get(id);
}
export function buildSidewaysFilterNode(id: string, def: DefType): LogicNode | null {
const item = getSidewaysFilterItem(id);
if (!item) return null;
return item.build(def);
return BUILTIN_FILTER_MAP.get(id);
}
export function filterSidewaysItems(
items: SidewaysFilterItem[],
items: SidewaysFilterPaletteEntry[],
query: string,
modeFilter: 'all' | SidewaysFilterMode,
): SidewaysFilterItem[] {
): SidewaysFilterPaletteEntry[] {
const q = query.trim().toLowerCase();
return items.filter(i => {
if (modeFilter !== 'all' && i.mode !== modeFilter) return false;
@@ -332,12 +333,12 @@ export function filterSidewaysItems(
}
export function groupSidewaysFiltersByCategory(
items: SidewaysFilterItem[],
): { category: SidewaysFilterCategory; label: string; items: SidewaysFilterItem[] }[] {
items: SidewaysFilterPaletteEntry[],
): { category: SidewaysFilterCategory; label: string; items: SidewaysFilterPaletteEntry[] }[] {
const order: SidewaysFilterCategory[] = [
'adx', 'bollinger', 'ma', 'ichimoku', 'donchian', 'oscillator', 'cycle', 'dmi', 'volume', 'combo',
];
const grouped = new Map<SidewaysFilterCategory, SidewaysFilterItem[]>();
const grouped = new Map<SidewaysFilterCategory, SidewaysFilterPaletteEntry[]>();
for (const i of items) {
const list = grouped.get(i.category) ?? [];
list.push(i);
@@ -0,0 +1,102 @@
/** 횡보필터 팔레트 — DB ui_preferences 저장 */
import type { LogicNode } from './strategyTypes';
import type { DefType } from './strategyEditorShared';
import { getUiPreferences, patchUiPreferences } from './uiPreferencesDb';
import {
DEFAULT_SIDEWAYS_FILTER_ITEMS,
buildSidewaysFilterLogic,
type SidewaysFilterCategory,
type SidewaysFilterMode,
} from './sidewaysFilterPalette';
export interface SidewaysFilterPaletteEntry {
id: string;
label: string;
desc: string;
category: SidewaysFilterCategory;
mode: SidewaysFilterMode;
/** 내장 필터 템플릿 ID — 로직 생성에 사용 */
templateId: string;
builtIn?: boolean;
}
function defaultEntries(): SidewaysFilterPaletteEntry[] {
return DEFAULT_SIDEWAYS_FILTER_ITEMS.map(i => ({
id: i.id,
templateId: i.id,
label: i.label,
desc: i.desc,
category: i.category,
mode: i.mode,
builtIn: true,
}));
}
function parseStored(raw: unknown): SidewaysFilterPaletteEntry[] | null {
if (!Array.isArray(raw)) return null;
return raw.filter(
(e): e is SidewaysFilterPaletteEntry =>
e != null
&& typeof e === 'object'
&& typeof (e as SidewaysFilterPaletteEntry).id === 'string'
&& typeof (e as SidewaysFilterPaletteEntry).templateId === 'string'
&& typeof (e as SidewaysFilterPaletteEntry).label === 'string',
);
}
function mergeMissingBuiltins(
stored: SidewaysFilterPaletteEntry[],
defaults: SidewaysFilterPaletteEntry[],
): SidewaysFilterPaletteEntry[] {
const existing = new Set(stored.map(i => i.id));
const added = defaults.filter(d => !existing.has(d.id));
return added.length > 0 ? [...stored, ...added] : stored;
}
export function loadSidewaysPaletteItems(): SidewaysFilterPaletteEntry[] {
const raw = getUiPreferences().strategyEditor?.paletteSideways;
const stored = parseStored(raw);
const defaults = defaultEntries();
if (!stored?.length) return defaults;
return mergeMissingBuiltins(stored, defaults);
}
export function saveSidewaysPaletteItems(items: SidewaysFilterPaletteEntry[]): void {
patchUiPreferences({ strategyEditor: { paletteSideways: items } });
}
export function createSidewaysFilterPaletteId(): string {
return `range-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
}
export function buildSidewaysFilterNode(
id: string,
def: DefType,
entries?: SidewaysFilterPaletteEntry[],
): LogicNode | null {
const list = entries ?? loadSidewaysPaletteItems();
const entry = list.find(e => e.id === id);
if (!entry) return null;
return buildSidewaysFilterLogic(entry.templateId, entry.mode, def);
}
export const SIDEWAYS_TEMPLATE_OPTIONS = DEFAULT_SIDEWAYS_FILTER_ITEMS.map(i => ({
value: i.id,
label: i.label,
category: i.category,
mode: i.mode,
desc: i.desc,
}));
export const SIDEWAYS_CATEGORY_OPTIONS: { value: SidewaysFilterCategory; label: string }[] = [
{ value: 'adx', label: 'ADX · 추세강도' },
{ value: 'bollinger', label: '볼린저 밴드' },
{ value: 'ma', label: 'MA · 이동평균' },
{ value: 'ichimoku', label: '일목균형표' },
{ value: 'donchian', label: 'Donchian · 채널' },
{ value: 'oscillator', label: '오실레이터' },
{ value: 'cycle', label: 'Stoch·lookback' },
{ value: 'dmi', label: 'DMI' },
{ value: 'volume', label: '거래량 · VR' },
{ value: 'combo', label: '복합 필터' },
];