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
@@ -0,0 +1,149 @@
/**
* 볼린저밴드 상·하단 사이 영역 채움 (업비트 "백그라운드 그리기")
*/
import type {
ISeriesPrimitive,
IPrimitivePaneView,
SeriesAttachedParameter,
Time,
SeriesType,
ISeriesApi,
} from 'lightweight-charts';
import type { CanvasRenderingTarget2D, MediaCoordinatesRenderingScope } from 'fancy-canvas';
interface ScreenPt { x: number; y: number }
function hexToRgba(hex: string, alpha: number): string {
const h = hex.startsWith('#') ? hex.slice(1) : hex;
const r = parseInt(h.slice(0, 2), 16);
const g = parseInt(h.slice(2, 4), 16);
const b = parseInt(h.slice(4, 6), 16);
const a = h.length >= 8 ? parseInt(h.slice(6, 8), 16) / 255 : alpha;
return `rgba(${r},${g},${b},${a})`;
}
function resolveFillColor(color: string): string {
if (color.length >= 9) {
return hexToRgba(color.slice(0, 7), parseInt(color.slice(7, 9), 16) / 255);
}
return hexToRgba(color.slice(0, 7), 0.15);
}
class BandFillRenderer {
constructor(
private readonly upper: ScreenPt[],
private readonly lower: ScreenPt[],
private readonly fillColor: string,
) {}
draw(target: CanvasRenderingTarget2D): void {
if (this.upper.length < 2 || this.lower.length < 2) return;
target.useMediaCoordinateSpace((scope: MediaCoordinatesRenderingScope) => {
const ctx = scope.context;
const n = this.upper.length;
ctx.save();
ctx.fillStyle = this.fillColor;
ctx.beginPath();
ctx.moveTo(this.upper[0].x, this.upper[0].y);
for (let i = 1; i < n; i++) ctx.lineTo(this.upper[i].x, this.upper[i].y);
for (let i = n - 1; i >= 0; i--) ctx.lineTo(this.lower[i].x, this.lower[i].y);
ctx.closePath();
ctx.fill();
ctx.restore();
});
}
}
class BandFillPaneView implements IPrimitivePaneView {
constructor(
private readonly upper: ScreenPt[],
private readonly lower: ScreenPt[],
private readonly fillColor: string,
) {}
renderer() { return new BandFillRenderer(this.upper, this.lower, this.fillColor); }
zOrder(): 'bottom' { return 'bottom'; }
}
export interface BandBackgroundOptions {
visible: boolean;
color: string;
}
/** basis 시리즈에 부착, upper/lower 데이터로 채움 */
export class BollingerBandFillPrimitive implements ISeriesPrimitive<Time> {
private _ap: SeriesAttachedParameter<Time> | null = null;
private _upper: ISeriesApi<SeriesType, Time>;
private _lower: ISeriesApi<SeriesType, Time>;
private _bg: BandBackgroundOptions;
private _upperPts: ScreenPt[] = [];
private _lowerPts: ScreenPt[] = [];
constructor(
upperSeries: ISeriesApi<SeriesType, Time>,
lowerSeries: ISeriesApi<SeriesType, Time>,
background: BandBackgroundOptions,
) {
this._upper = upperSeries;
this._lower = lowerSeries;
this._bg = background;
}
attached(ap: SeriesAttachedParameter<Time>): void {
this._ap = ap;
this._recompute();
}
detached(): void { this._ap = null; }
updateAllViews(): void { this._recompute(); }
paneViews(): readonly IPrimitivePaneView[] {
if (this._bg.visible === false || this._upperPts.length < 2) return [];
return [new BandFillPaneView(this._upperPts, this._lowerPts, resolveFillColor(this._bg.color))];
}
updateBackground(bg: BandBackgroundOptions): void {
this._bg = bg;
this._ap?.requestUpdate();
}
requestRefresh(): void {
this._recompute();
this._ap?.requestUpdate();
}
private _recompute(): void {
if (!this._ap || this._bg.visible === false) {
this._upperPts = [];
this._lowerPts = [];
return;
}
const ts = this._ap.chart.timeScale();
const upperRaw = this._upper.data() as readonly { time: Time; value?: number }[];
const lowerRaw = this._lower.data() as readonly { time: Time; value?: number }[];
const lowerByTime = new Map<Time, number>();
for (const d of lowerRaw) {
if (d.value !== undefined && d.value !== null && !Number.isNaN(d.value)) {
lowerByTime.set(d.time, d.value);
}
}
const upperPts: ScreenPt[] = [];
const lowerPts: ScreenPt[] = [];
for (const u of upperRaw) {
const lv = lowerByTime.get(u.time);
if (u.value === undefined || u.value === null || Number.isNaN(u.value)) continue;
if (lv === undefined || Number.isNaN(lv)) continue;
const x = ts.timeToCoordinate(u.time);
const uy = this._upper.priceToCoordinate(u.value);
const ly = this._lower.priceToCoordinate(lv);
if (x === null || uy === null || ly === null) continue;
upperPts.push({ x: x as number, y: uy as number });
lowerPts.push({ x: x as number, y: ly as number });
}
this._upperPts = upperPts;
this._lowerPts = lowerPts;
}
}