goldenChat base source add

This commit is contained in:
aidev
2026-05-23 15:11:48 +09:00
commit a4ea7762b5
2081 changed files with 1155760 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
/**
* 볼린저밴드 (업비트 BB 설정)
* - 길이(length) 기본 20, 곱(mult) 기본 2, 오프셋(offset) 기본 0
* - 기준선: 종가 SMA, 표준편차는 모집단(N) 기준
*/
import type { OHLCVBar, Timeframe } from '../types';
import type { PlotData } from './indicatorRegistry';
import { fetchUpbitCandles, isUpbitMarket } from './upbitApi';
export const BB_DEFAULT_LENGTH = 20;
export const BB_DEFAULT_MULT = 2;
export const BB_DEFAULT_OFFSET = 0;
/** 업비트 BB 백그라운드 그리기 기본 (반투명 파랑) */
export const DEFAULT_BB_BAND_BACKGROUND = {
visible: true,
color: '#42A5F528',
} as const;
const BB_PLOT_TITLES = ['중앙값', '어퍼', '로우어'] as const;
const BB_PLOT_IDS = ['plot0', 'plot1', 'plot2'] as const;
const BB_DEFAULT_COLORS = ['#FF9800', '#42A5F5', '#42A5F5'] as const;
export type BbSymbolMode = 'main' | 'other';
export type BbBandBackground = { visible: boolean; color: string };
export function resolveBbBandBackground(
bg?: Partial<BbBandBackground> | null,
): BbBandBackground {
return {
visible: bg?.visible !== false,
color: bg?.color ?? DEFAULT_BB_BAND_BACKGROUND.color,
};
}
/** 업비트 플롯 정의 병합 (중앙값·어퍼·로우어, 기본 색) */
export function mergeBbPlots(
saved: import('./indicatorRegistry').PlotDef[] | undefined,
defaults: import('./indicatorRegistry').PlotDef[],
): import('./indicatorRegistry').PlotDef[] {
const byId = new Map((saved ?? []).map(p => [p.id, p]));
return BB_PLOT_IDS.map((id, i) => {
const def = defaults.find(p => p.id === id) ?? defaults[i];
const prev = byId.get(id);
return {
...def,
...prev,
id,
title: BB_PLOT_TITLES[i],
color: prev?.color ?? BB_DEFAULT_COLORS[i],
};
});
}
/** 차트 레전드: BB 20 2 0 SMA */
export function formatBbLegendLabel(
params: Record<string, number | string | boolean>,
): string {
const p = normalizeBollingerParams(params);
const ma = String(p.maType ?? 'SMA').replace(/\s*\(.*\)\s*/g, '').trim();
return `BB ${p.length} ${p.mult} ${p.offset} ${ma}`;
}
export function normalizeBollingerParams(
params: Record<string, number | string | boolean>,
): Record<string, number | string | boolean> {
const length = Math.max(1, Math.round(Number(params.length ?? BB_DEFAULT_LENGTH) || BB_DEFAULT_LENGTH));
const mult = Math.max(0.001, Number(params.mult ?? BB_DEFAULT_MULT) || BB_DEFAULT_MULT);
const offset = Math.round(Number(params.offset ?? BB_DEFAULT_OFFSET) || 0);
const rawMode = String(params.symbolMode ?? 'main');
const symbolMode: BbSymbolMode = rawMode === 'other' ? 'other' : 'main';
const refSymbol = String(params.refSymbol ?? '').trim().toUpperCase();
return {
...params,
length,
mult,
offset,
symbolMode,
refSymbol,
src: 'close',
maType: 'SMA',
};
}
/** TradingView/업비트 오프셋: 양수면 플롯을 오른쪽(미래)으로 이동 */
export function applyPlotOffset(data: PlotData, offset: number): PlotData {
if (!offset) return data;
const n = data.length;
return data.map((point, i) => {
const srcIdx = i - offset;
if (srcIdx < 0 || srcIdx >= n) {
return { time: point.time, value: NaN };
}
return { time: point.time, value: data[srcIdx].value };
});
}
/** 다른 심볼 캔들을 메인 차트 시간축에 맞춤 (같은 time 키) */
export function alignBarsToMainTimeline(mainBars: OHLCVBar[], refBars: OHLCVBar[]): OHLCVBar[] {
const byTime = new Map(refBars.map(b => [b.time, b]));
return mainBars.map(mb => {
const ref = byTime.get(mb.time);
if (!ref) {
return { ...mb, open: NaN, high: NaN, low: NaN, close: NaN, volume: 0 };
}
return ref;
});
}
export async function resolveBollingerBars(
mainBars: OHLCVBar[],
params: Record<string, number | string | boolean>,
timeframe: Timeframe,
): Promise<OHLCVBar[]> {
const p = normalizeBollingerParams(params);
if (p.symbolMode !== 'other') return mainBars;
const ref = String(p.refSymbol ?? '');
if (!ref || !isUpbitMarket(ref)) return mainBars;
try {
const refBars = await fetchUpbitCandles(ref, timeframe, mainBars.length);
return alignBarsToMainTimeline(mainBars, refBars);
} catch (e) {
console.warn('[BB] ref symbol fetch failed:', ref, e);
return mainBars;
}
}