횡보컨트롤 배경색 적용
This commit is contained in:
@@ -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: '복합 필터' },
|
||||
];
|
||||
Reference in New Issue
Block a user