goldenChat base source add
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Ichimoku Cloud Fill Plugin
|
||||
* LWC v5 ISeriesPrimitive를 이용해 선행스팬A/B 사이 구름 영역을 캔버스에 직접 렌더링합니다.
|
||||
*
|
||||
* 한국 관례:
|
||||
* SpanA > SpanB → 양운 (빨간 구름, 상승 신호)
|
||||
* SpanA <= SpanB → 음운 (청록 구름, 하락 신호)
|
||||
*/
|
||||
import type {
|
||||
ISeriesPrimitive,
|
||||
IPrimitivePaneView,
|
||||
IPrimitivePaneRenderer,
|
||||
SeriesAttachedParameter,
|
||||
ISeriesApi,
|
||||
SeriesType,
|
||||
Time,
|
||||
IChartApiBase,
|
||||
} from 'lightweight-charts';
|
||||
|
||||
export interface IchimokuCloudPoint {
|
||||
time: number; // Unix timestamp (seconds)
|
||||
spanA: number;
|
||||
spanB: number;
|
||||
}
|
||||
|
||||
function toCanvasFillColor(color: string): string {
|
||||
if (color.startsWith('rgba(') || color.startsWith('rgb(')) return color;
|
||||
const hex = color.startsWith('#') ? color.slice(1) : color;
|
||||
if (hex.length < 6) return color;
|
||||
const r = parseInt(hex.slice(0, 2), 16);
|
||||
const g = parseInt(hex.slice(2, 4), 16);
|
||||
const b = parseInt(hex.slice(4, 6), 16);
|
||||
const a = hex.length >= 8
|
||||
? parseInt(hex.slice(6, 8), 16) / 255
|
||||
: 1;
|
||||
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||
}
|
||||
|
||||
// ─── Renderer ──────────────────────────────────────────────────────────────
|
||||
class CloudRenderer implements IPrimitivePaneRenderer {
|
||||
private _chart: IChartApiBase<Time>;
|
||||
private _series: ISeriesApi<SeriesType>;
|
||||
private _data: IchimokuCloudPoint[];
|
||||
private _bullishColor: string;
|
||||
private _bearishColor: string;
|
||||
|
||||
constructor(
|
||||
chart: IChartApiBase<Time>,
|
||||
series: ISeriesApi<SeriesType>,
|
||||
data: IchimokuCloudPoint[],
|
||||
bullishColor: string,
|
||||
bearishColor: string,
|
||||
) {
|
||||
this._chart = chart;
|
||||
this._series = series;
|
||||
this._data = data;
|
||||
this._bullishColor = bullishColor;
|
||||
this._bearishColor = bearishColor;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
draw(target: any): void {
|
||||
if (this._data.length < 2) return;
|
||||
const timeScale = this._chart.timeScale();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
target.useBitmapCoordinateSpace(({ context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr }: any) => {
|
||||
for (let i = 0; i < this._data.length - 1; i++) {
|
||||
const a = this._data[i];
|
||||
const b = this._data[i + 1];
|
||||
|
||||
const x1 = timeScale.timeToCoordinate(a.time as Time);
|
||||
const x2 = timeScale.timeToCoordinate(b.time as Time);
|
||||
if (x1 == null || x2 == null) continue;
|
||||
|
||||
const aAy = this._series.priceToCoordinate(a.spanA);
|
||||
const aBy = this._series.priceToCoordinate(a.spanB);
|
||||
const bAy = this._series.priceToCoordinate(b.spanA);
|
||||
const bBy = this._series.priceToCoordinate(b.spanB);
|
||||
if (aAy == null || aBy == null || bAy == null || bBy == null) continue;
|
||||
|
||||
// 한국 관례: SpanA > SpanB → 양운(빨강), SpanA <= SpanB → 음운(청록)
|
||||
const bullish = (a.spanA + b.spanA) > (a.spanB + b.spanB);
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1 * hpr, aAy * vpr);
|
||||
ctx.lineTo(x2 * hpr, bAy * vpr);
|
||||
ctx.lineTo(x2 * hpr, bBy * vpr);
|
||||
ctx.lineTo(x1 * hpr, aBy * vpr);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = bullish ? this._bullishColor : this._bearishColor;
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Pane View ─────────────────────────────────────────────────────────────
|
||||
class CloudPaneView implements IPrimitivePaneView {
|
||||
private _plugin: IchimokuCloudPlugin;
|
||||
constructor(plugin: IchimokuCloudPlugin) { this._plugin = plugin; }
|
||||
|
||||
renderer(): IPrimitivePaneRenderer | null {
|
||||
const chart = this._plugin.getChart();
|
||||
const series = this._plugin.getSeries();
|
||||
if (!chart || !series) return null;
|
||||
return new CloudRenderer(
|
||||
chart,
|
||||
series,
|
||||
this._plugin.getCloudData(),
|
||||
this._plugin.getBullishColor(),
|
||||
this._plugin.getBearishColor(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Plugin ────────────────────────────────────────────────────────────────
|
||||
export class IchimokuCloudPlugin implements ISeriesPrimitive<Time> {
|
||||
private _chart: IChartApiBase<Time> | null = null;
|
||||
private _series: ISeriesApi<SeriesType> | null = null;
|
||||
private _data: IchimokuCloudPoint[] = [];
|
||||
private _bullishColor = 'rgba(239, 83, 80, 0.2)';
|
||||
private _bearishColor = 'rgba(38, 166, 154, 0.2)';
|
||||
private _updateFn: (() => void) | null = null;
|
||||
|
||||
attached({ chart, series, requestUpdate }: SeriesAttachedParameter<Time>): void {
|
||||
this._chart = chart;
|
||||
this._series = series;
|
||||
this._updateFn = requestUpdate;
|
||||
}
|
||||
|
||||
detached(): void {
|
||||
this._chart = null;
|
||||
this._series = null;
|
||||
}
|
||||
|
||||
setCloudData(data: IchimokuCloudPoint[]): void {
|
||||
this._data = data;
|
||||
this._updateFn?.();
|
||||
}
|
||||
|
||||
setColors(bullishColor: string, bearishColor: string): void {
|
||||
this._bullishColor = toCanvasFillColor(bullishColor);
|
||||
this._bearishColor = toCanvasFillColor(bearishColor);
|
||||
this._updateFn?.();
|
||||
}
|
||||
|
||||
getBullishColor() { return this._bullishColor; }
|
||||
getBearishColor() { return this._bearishColor; }
|
||||
|
||||
paneViews(): readonly IPrimitivePaneView[] {
|
||||
if (!this._chart || !this._series) return [];
|
||||
return [new CloudPaneView(this)];
|
||||
}
|
||||
|
||||
getChart() { return this._chart; }
|
||||
getSeries() { return this._series; }
|
||||
getCloudData() { return this._data; }
|
||||
}
|
||||
Reference in New Issue
Block a user