oakscriptjs
Advanced tools
Sorry, the diff of this file is too big to display
| "use strict"; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| // src/runtime/index.ts | ||
| var runtime_exports = {}; | ||
| __export(runtime_exports, { | ||
| LightweightChartsAdapter: () => LightweightChartsAdapter, | ||
| SimpleInputAdapter: () => SimpleInputAdapter, | ||
| clearContext: () => clearContext, | ||
| clearPlots: () => clearPlots, | ||
| disableAutoRecalculate: () => disableAutoRecalculate, | ||
| enableAutoRecalculate: () => enableAutoRecalculate, | ||
| getActivePlots: () => getActivePlots, | ||
| getContext: () => getContext, | ||
| hline: () => hline, | ||
| input_bool: () => input_bool, | ||
| input_float: () => input_float, | ||
| input_int: () => input_int, | ||
| input_source: () => input_source, | ||
| input_string: () => input_string, | ||
| plot: () => plot, | ||
| recalculate: () => recalculate, | ||
| registerCalculate: () => registerCalculate, | ||
| resetInputs: () => resetInputs, | ||
| setContext: () => setContext | ||
| }); | ||
| module.exports = __toCommonJS(runtime_exports); | ||
| // src/runtime/runtime.ts | ||
| var context = null; | ||
| var calculateFn = null; | ||
| var activePlots = []; | ||
| var plotCounter = 0; | ||
| function setContext(ctx) { | ||
| context = ctx; | ||
| plotCounter = 0; | ||
| clearPlots(); | ||
| } | ||
| function clearContext() { | ||
| clearPlots(); | ||
| context = null; | ||
| calculateFn = null; | ||
| plotCounter = 0; | ||
| } | ||
| function getContext() { | ||
| return context; | ||
| } | ||
| function registerCalculate(fn) { | ||
| calculateFn = fn; | ||
| } | ||
| function recalculate() { | ||
| if (calculateFn) { | ||
| clearPlots(); | ||
| plotCounter = 0; | ||
| calculateFn(); | ||
| } | ||
| } | ||
| function clearPlots() { | ||
| if (context) { | ||
| for (const plot2 of activePlots) { | ||
| try { | ||
| context.chart.removeSeries(plot2.series); | ||
| } catch { | ||
| } | ||
| } | ||
| } | ||
| activePlots.length = 0; | ||
| } | ||
| function getSeriesType(style) { | ||
| switch (style) { | ||
| case "histogram": | ||
| case "columns": | ||
| return "histogram"; | ||
| case "area": | ||
| case "areabr": | ||
| return "area"; | ||
| case "circles": | ||
| case "cross": | ||
| return "line"; | ||
| default: | ||
| return "line"; | ||
| } | ||
| } | ||
| function getLineStyle(style) { | ||
| switch (style) { | ||
| case "dotted": | ||
| return 1; | ||
| case "dashed": | ||
| return 2; | ||
| case "solid": | ||
| default: | ||
| return 0; | ||
| } | ||
| } | ||
| function plot(series, title, color, linewidth, style, colors) { | ||
| if (!context) { | ||
| throw new Error("OakScript context not set. Call setContext() before plotting."); | ||
| } | ||
| const plotId = `plot_${plotCounter++}${title ? "_" + title.replace(/\s+/g, "_") : ""}`; | ||
| const seriesType = getSeriesType(style); | ||
| const options = { | ||
| color, | ||
| lineWidth: linewidth, | ||
| lineStyle: getLineStyle(style) | ||
| }; | ||
| const seriesHandle = context.chart.addSeries(seriesType, options); | ||
| const data = []; | ||
| for (let index = 0; index < series.length; index++) { | ||
| const value = series[index]; | ||
| const time = context.ohlcv.time[index]; | ||
| if (time !== void 0 && value !== void 0 && !Number.isNaN(value)) { | ||
| const point = { time, value }; | ||
| if (colors && colors[index]) { | ||
| point.color = colors[index]; | ||
| } | ||
| data.push(point); | ||
| } | ||
| } | ||
| seriesHandle.setData(data); | ||
| activePlots.push({ id: plotId, series: seriesHandle }); | ||
| return plotId; | ||
| } | ||
| function hline(price, title, color, linestyle, linewidth) { | ||
| if (!context) { | ||
| throw new Error("OakScript context not set. Call setContext() before creating hlines."); | ||
| } | ||
| const hlineId = `hline_${plotCounter++}${title ? "_" + title.replace(/\s+/g, "_") : ""}`; | ||
| const options = { | ||
| color, | ||
| lineWidth: linewidth, | ||
| lineStyle: getLineStyle(linestyle) | ||
| }; | ||
| const seriesHandle = context.chart.addSeries("line", options); | ||
| const data = context.ohlcv.time.map((time) => ({ | ||
| time, | ||
| value: price | ||
| })); | ||
| seriesHandle.setData(data); | ||
| activePlots.push({ id: hlineId, series: seriesHandle }); | ||
| return hlineId; | ||
| } | ||
| function getActivePlots() { | ||
| return activePlots; | ||
| } | ||
| // src/runtime/inputs.ts | ||
| var registeredInputs = /* @__PURE__ */ new Map(); | ||
| var autoRecalculateEnabled = false; | ||
| function enableAutoRecalculate() { | ||
| autoRecalculateEnabled = true; | ||
| } | ||
| function disableAutoRecalculate() { | ||
| autoRecalculateEnabled = false; | ||
| } | ||
| function resetInputs() { | ||
| registeredInputs.clear(); | ||
| inputCounter = 0; | ||
| } | ||
| var inputCounter = 0; | ||
| function generateInputId(title, defval, type) { | ||
| if (title) { | ||
| return title.replace(/\s+/g, "_"); | ||
| } | ||
| return `${type}_${inputCounter++}_${String(defval)}`; | ||
| } | ||
| function input_int(defval, title, options) { | ||
| const context2 = getContext(); | ||
| if (!context2) { | ||
| return Math.floor(defval); | ||
| } | ||
| const id = generateInputId(title, defval, "int"); | ||
| const config = { | ||
| id, | ||
| type: "int", | ||
| defval: Math.floor(defval), | ||
| title, | ||
| min: options?.min, | ||
| max: options?.max, | ||
| step: options?.step ?? 1 | ||
| }; | ||
| if (!registeredInputs.has(id)) { | ||
| context2.inputs.registerInput(config); | ||
| registeredInputs.set(id, true); | ||
| if (autoRecalculateEnabled) { | ||
| context2.inputs.onInputChange((changedId) => { | ||
| if (changedId === id) { | ||
| recalculate(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| const value = context2.inputs.getValue(id); | ||
| return typeof value === "number" ? Math.floor(value) : Math.floor(defval); | ||
| } | ||
| function input_float(defval, title, options) { | ||
| const context2 = getContext(); | ||
| if (!context2) { | ||
| return defval; | ||
| } | ||
| const id = generateInputId(title, defval, "float"); | ||
| const config = { | ||
| id, | ||
| type: "float", | ||
| defval, | ||
| title, | ||
| min: options?.min, | ||
| max: options?.max, | ||
| step: options?.step ?? 0.1 | ||
| }; | ||
| if (!registeredInputs.has(id)) { | ||
| context2.inputs.registerInput(config); | ||
| registeredInputs.set(id, true); | ||
| if (autoRecalculateEnabled) { | ||
| context2.inputs.onInputChange((changedId) => { | ||
| if (changedId === id) { | ||
| recalculate(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| const value = context2.inputs.getValue(id); | ||
| return typeof value === "number" ? value : defval; | ||
| } | ||
| function input_bool(defval, title) { | ||
| const context2 = getContext(); | ||
| if (!context2) { | ||
| return defval; | ||
| } | ||
| const id = generateInputId(title, defval, "bool"); | ||
| const config = { | ||
| id, | ||
| type: "bool", | ||
| defval, | ||
| title | ||
| }; | ||
| if (!registeredInputs.has(id)) { | ||
| context2.inputs.registerInput(config); | ||
| registeredInputs.set(id, true); | ||
| if (autoRecalculateEnabled) { | ||
| context2.inputs.onInputChange((changedId) => { | ||
| if (changedId === id) { | ||
| recalculate(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| const value = context2.inputs.getValue(id); | ||
| return typeof value === "boolean" ? value : defval; | ||
| } | ||
| function input_string(defval, title, options) { | ||
| const context2 = getContext(); | ||
| if (!context2) { | ||
| return defval; | ||
| } | ||
| const id = generateInputId(title, defval, "string"); | ||
| const config = { | ||
| id, | ||
| type: "string", | ||
| defval, | ||
| title, | ||
| options | ||
| }; | ||
| if (!registeredInputs.has(id)) { | ||
| context2.inputs.registerInput(config); | ||
| registeredInputs.set(id, true); | ||
| if (autoRecalculateEnabled) { | ||
| context2.inputs.onInputChange((changedId) => { | ||
| if (changedId === id) { | ||
| recalculate(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| const value = context2.inputs.getValue(id); | ||
| return typeof value === "string" ? value : defval; | ||
| } | ||
| function input_source(defval, title) { | ||
| const context2 = getContext(); | ||
| if (!context2) { | ||
| return []; | ||
| } | ||
| const id = generateInputId(title, defval, "source"); | ||
| const config = { | ||
| id, | ||
| type: "source", | ||
| defval, | ||
| title, | ||
| options: ["open", "high", "low", "close", "volume", "hl2", "hlc3", "ohlc4"] | ||
| }; | ||
| if (!registeredInputs.has(id)) { | ||
| context2.inputs.registerInput(config); | ||
| registeredInputs.set(id, true); | ||
| if (autoRecalculateEnabled) { | ||
| context2.inputs.onInputChange((changedId) => { | ||
| if (changedId === id) { | ||
| recalculate(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| const sourceName = context2.inputs.getValue(id) ?? defval; | ||
| return getSourceData(context2.ohlcv, sourceName); | ||
| } | ||
| function getSourceData(ohlcv, sourceName) { | ||
| switch (sourceName) { | ||
| case "open": | ||
| return ohlcv.open; | ||
| case "high": | ||
| return ohlcv.high; | ||
| case "low": | ||
| return ohlcv.low; | ||
| case "close": | ||
| return ohlcv.close; | ||
| case "volume": | ||
| return ohlcv.volume; | ||
| case "hl2": | ||
| return ohlcv.high.map((h, i) => { | ||
| const l = ohlcv.low[i]; | ||
| return l !== void 0 ? (h + l) / 2 : NaN; | ||
| }); | ||
| case "hlc3": | ||
| return ohlcv.high.map((h, i) => { | ||
| const l = ohlcv.low[i]; | ||
| const c = ohlcv.close[i]; | ||
| return l !== void 0 && c !== void 0 ? (h + l + c) / 3 : NaN; | ||
| }); | ||
| case "ohlc4": | ||
| return ohlcv.open.map((o, i) => { | ||
| const h = ohlcv.high[i]; | ||
| const l = ohlcv.low[i]; | ||
| const c = ohlcv.close[i]; | ||
| return h !== void 0 && l !== void 0 && c !== void 0 ? (o + h + l + c) / 4 : NaN; | ||
| }); | ||
| default: | ||
| return ohlcv.close; | ||
| } | ||
| } | ||
| // src/runtime/adapters/LightweightChartsAdapter.ts | ||
| var LightweightChartsAdapter = class { | ||
| chart; | ||
| mainSeries; | ||
| paneCount = 0; | ||
| /** | ||
| * Create a new LightweightChartsAdapter | ||
| * @param chart - The lightweight-charts chart instance | ||
| * @param mainSeries - Optional main price series (candlestick/line series) | ||
| */ | ||
| constructor(chart, mainSeries) { | ||
| this.chart = chart; | ||
| if (mainSeries) { | ||
| this.mainSeries = this.wrapSeries(mainSeries); | ||
| } | ||
| } | ||
| /** | ||
| * Wrap a lightweight-charts series to implement SeriesHandle | ||
| * @param lwcSeries - The lightweight-charts series | ||
| * @returns SeriesHandle wrapper | ||
| */ | ||
| wrapSeries(lwcSeries) { | ||
| return { | ||
| setData: (data) => { | ||
| lwcSeries.setData(data); | ||
| }, | ||
| // Store reference for removal | ||
| _lwcSeries: lwcSeries | ||
| }; | ||
| } | ||
| /** | ||
| * Map series type string to lightweight-charts series definition | ||
| * @param type - Series type ('line', 'histogram', 'area') | ||
| * @returns Lightweight-charts series definition object | ||
| */ | ||
| getSeriesDefinition(type) { | ||
| switch (type) { | ||
| case "histogram": | ||
| return { type: "Histogram" }; | ||
| case "area": | ||
| return { type: "Area" }; | ||
| case "baseline": | ||
| return { type: "Baseline" }; | ||
| case "bar": | ||
| return { type: "Bar" }; | ||
| case "line": | ||
| default: | ||
| return { type: "Line" }; | ||
| } | ||
| } | ||
| /** | ||
| * Convert OakScriptJS series options to lightweight-charts options | ||
| * @param options - OakScriptJS series options | ||
| * @returns Lightweight-charts compatible options | ||
| */ | ||
| convertOptions(options) { | ||
| if (!options) { | ||
| return {}; | ||
| } | ||
| const lwcOptions = {}; | ||
| if (options.color) { | ||
| lwcOptions.color = options.color; | ||
| } | ||
| if (options.lineWidth !== void 0) { | ||
| lwcOptions.lineWidth = options.lineWidth; | ||
| } | ||
| if (options.lineStyle !== void 0) { | ||
| lwcOptions.lineStyle = options.lineStyle; | ||
| } | ||
| if (options.priceScaleId) { | ||
| lwcOptions.priceScaleId = options.priceScaleId; | ||
| } | ||
| return lwcOptions; | ||
| } | ||
| /** | ||
| * Add a new series to the chart | ||
| * @param type - Series type ('line', 'histogram', 'area') | ||
| * @param options - Series options | ||
| * @returns SeriesHandle for the new series | ||
| */ | ||
| addSeries(type, options) { | ||
| const seriesDefinition = this.getSeriesDefinition(type); | ||
| const lwcOptions = this.convertOptions(options); | ||
| const lwcSeries = this.chart.addSeries(seriesDefinition, lwcOptions); | ||
| return this.wrapSeries(lwcSeries); | ||
| } | ||
| /** | ||
| * Remove a series from the chart | ||
| * @param series - SeriesHandle to remove | ||
| */ | ||
| removeSeries(series) { | ||
| const wrappedSeries = series; | ||
| if (wrappedSeries._lwcSeries) { | ||
| this.chart.removeSeries(wrappedSeries._lwcSeries); | ||
| } | ||
| } | ||
| /** | ||
| * Get the main price series | ||
| * @returns Main series handle or undefined | ||
| */ | ||
| getMainSeries() { | ||
| return this.mainSeries; | ||
| } | ||
| /** | ||
| * Create a new pane (if supported by the chart configuration) | ||
| * @returns Index of the new pane | ||
| */ | ||
| createPane() { | ||
| return ++this.paneCount; | ||
| } | ||
| }; | ||
| // src/runtime/adapters/SimpleInputAdapter.ts | ||
| var SimpleInputAdapter = class { | ||
| inputs = /* @__PURE__ */ new Map(); | ||
| changeCallbacks = []; | ||
| /** | ||
| * Register an input configuration | ||
| * If already registered, returns current value without modifying config | ||
| * @param config - Input configuration | ||
| * @returns Current value (defval on first registration) | ||
| */ | ||
| registerInput(config) { | ||
| if (!this.inputs.has(config.id)) { | ||
| this.inputs.set(config.id, { config, value: config.defval }); | ||
| } | ||
| return this.inputs.get(config.id).value; | ||
| } | ||
| /** | ||
| * Get current value of an input | ||
| * @param id - Input identifier | ||
| * @returns Current value or undefined if not registered | ||
| */ | ||
| getValue(id) { | ||
| return this.inputs.get(id)?.value; | ||
| } | ||
| /** | ||
| * Set value of an input with validation | ||
| * Validates against config constraints (min/max/step for numerics, options for strings) | ||
| * @param id - Input identifier | ||
| * @param value - New value | ||
| */ | ||
| setValue(id, value) { | ||
| const input = this.inputs.get(id); | ||
| if (!input) { | ||
| return; | ||
| } | ||
| const { config } = input; | ||
| let validatedValue = value; | ||
| if ((config.type === "int" || config.type === "float") && typeof value === "number") { | ||
| if (config.min !== void 0 && value < config.min) { | ||
| validatedValue = config.min; | ||
| } | ||
| if (config.max !== void 0 && value > config.max) { | ||
| validatedValue = config.max; | ||
| } | ||
| if (config.type === "int") { | ||
| validatedValue = Math.floor(validatedValue); | ||
| } | ||
| } | ||
| if (config.type === "string" && config.options && typeof value === "string") { | ||
| if (!config.options.includes(value)) { | ||
| return; | ||
| } | ||
| } | ||
| if (config.type === "source" && config.options && typeof value === "string") { | ||
| if (!config.options.includes(value)) { | ||
| return; | ||
| } | ||
| } | ||
| input.value = validatedValue; | ||
| this.changeCallbacks.forEach((cb) => cb(id, validatedValue)); | ||
| } | ||
| /** | ||
| * Register a callback for input value changes | ||
| * @param callback - Function to call when any input value changes | ||
| */ | ||
| onInputChange(callback) { | ||
| this.changeCallbacks.push(callback); | ||
| } | ||
| /** | ||
| * Get all registered inputs (for UI rendering) | ||
| * @returns Map of input IDs to their configs and values | ||
| */ | ||
| getAllInputs() { | ||
| return new Map(this.inputs); | ||
| } | ||
| /** | ||
| * Clear all registered inputs | ||
| * Useful for testing or resetting state | ||
| */ | ||
| clear() { | ||
| this.inputs.clear(); | ||
| this.changeCallbacks = []; | ||
| } | ||
| /** | ||
| * Remove a specific change callback | ||
| * @param callback - The callback to remove | ||
| */ | ||
| removeChangeCallback(callback) { | ||
| const index = this.changeCallbacks.indexOf(callback); | ||
| if (index !== -1) { | ||
| this.changeCallbacks.splice(index, 1); | ||
| } | ||
| } | ||
| }; |
+6
-6
| { | ||
| "name": "oakscriptjs", | ||
| "version": "0.2.7", | ||
| "version": "0.2.8", | ||
| "type": "module", | ||
| "description": "PineScript v6 compatible technical analysis library for JavaScript/TypeScript", | ||
| "main": "dist/index.js", | ||
| "main": "dist/index.cjs", | ||
| "module": "dist/index.mjs", | ||
@@ -13,3 +13,3 @@ "types": "dist/index.d.ts", | ||
| "import": "./dist/index.mjs", | ||
| "require": "./dist/index.js" | ||
| "require": "./dist/index.cjs" | ||
| }, | ||
@@ -19,3 +19,3 @@ "./runtime": { | ||
| "import": "./dist/runtime/index.mjs", | ||
| "require": "./dist/runtime/index.js" | ||
| "require": "./dist/runtime/index.cjs" | ||
| } | ||
@@ -32,5 +32,5 @@ }, | ||
| "build:esm": "esbuild src/index.ts --bundle --format=esm --outfile=dist/index.mjs --platform=neutral --external:@types/* --external:lightweight-charts", | ||
| "build:cjs": "esbuild src/index.ts --bundle --format=cjs --outfile=dist/index.js --platform=neutral --external:@types/* --external:lightweight-charts", | ||
| "build:cjs": "esbuild src/index.ts --bundle --format=cjs --outfile=dist/index.cjs --platform=neutral --external:@types/* --external:lightweight-charts", | ||
| "build:runtime:esm": "esbuild src/runtime/index.ts --bundle --format=esm --outfile=dist/runtime/index.mjs --platform=neutral --external:@types/* --external:lightweight-charts", | ||
| "build:runtime:cjs": "esbuild src/runtime/index.ts --bundle --format=cjs --outfile=dist/runtime/index.js --platform=neutral --external:@types/* --external:lightweight-charts", | ||
| "build:runtime:cjs": "esbuild src/runtime/index.ts --bundle --format=cjs --outfile=dist/runtime/index.cjs --platform=neutral --external:@types/* --external:lightweight-charts", | ||
| "build:types": "tsc --emitDeclarationOnly --outDir dist", | ||
@@ -37,0 +37,0 @@ "clean": "rimraf dist", |
Sorry, the diff of this file is too big to display
| "use strict"; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| // src/runtime/index.ts | ||
| var runtime_exports = {}; | ||
| __export(runtime_exports, { | ||
| LightweightChartsAdapter: () => LightweightChartsAdapter, | ||
| SimpleInputAdapter: () => SimpleInputAdapter, | ||
| clearContext: () => clearContext, | ||
| clearPlots: () => clearPlots, | ||
| disableAutoRecalculate: () => disableAutoRecalculate, | ||
| enableAutoRecalculate: () => enableAutoRecalculate, | ||
| getActivePlots: () => getActivePlots, | ||
| getContext: () => getContext, | ||
| hline: () => hline, | ||
| input_bool: () => input_bool, | ||
| input_float: () => input_float, | ||
| input_int: () => input_int, | ||
| input_source: () => input_source, | ||
| input_string: () => input_string, | ||
| plot: () => plot, | ||
| recalculate: () => recalculate, | ||
| registerCalculate: () => registerCalculate, | ||
| resetInputs: () => resetInputs, | ||
| setContext: () => setContext | ||
| }); | ||
| module.exports = __toCommonJS(runtime_exports); | ||
| // src/runtime/runtime.ts | ||
| var context = null; | ||
| var calculateFn = null; | ||
| var activePlots = []; | ||
| var plotCounter = 0; | ||
| function setContext(ctx) { | ||
| context = ctx; | ||
| plotCounter = 0; | ||
| clearPlots(); | ||
| } | ||
| function clearContext() { | ||
| clearPlots(); | ||
| context = null; | ||
| calculateFn = null; | ||
| plotCounter = 0; | ||
| } | ||
| function getContext() { | ||
| return context; | ||
| } | ||
| function registerCalculate(fn) { | ||
| calculateFn = fn; | ||
| } | ||
| function recalculate() { | ||
| if (calculateFn) { | ||
| clearPlots(); | ||
| plotCounter = 0; | ||
| calculateFn(); | ||
| } | ||
| } | ||
| function clearPlots() { | ||
| if (context) { | ||
| for (const plot2 of activePlots) { | ||
| try { | ||
| context.chart.removeSeries(plot2.series); | ||
| } catch { | ||
| } | ||
| } | ||
| } | ||
| activePlots.length = 0; | ||
| } | ||
| function getSeriesType(style) { | ||
| switch (style) { | ||
| case "histogram": | ||
| case "columns": | ||
| return "histogram"; | ||
| case "area": | ||
| case "areabr": | ||
| return "area"; | ||
| case "circles": | ||
| case "cross": | ||
| return "line"; | ||
| default: | ||
| return "line"; | ||
| } | ||
| } | ||
| function getLineStyle(style) { | ||
| switch (style) { | ||
| case "dotted": | ||
| return 1; | ||
| case "dashed": | ||
| return 2; | ||
| case "solid": | ||
| default: | ||
| return 0; | ||
| } | ||
| } | ||
| function plot(series, title, color, linewidth, style, colors) { | ||
| if (!context) { | ||
| throw new Error("OakScript context not set. Call setContext() before plotting."); | ||
| } | ||
| const plotId = `plot_${plotCounter++}${title ? "_" + title.replace(/\s+/g, "_") : ""}`; | ||
| const seriesType = getSeriesType(style); | ||
| const options = { | ||
| color, | ||
| lineWidth: linewidth, | ||
| lineStyle: getLineStyle(style) | ||
| }; | ||
| const seriesHandle = context.chart.addSeries(seriesType, options); | ||
| const data = []; | ||
| for (let index = 0; index < series.length; index++) { | ||
| const value = series[index]; | ||
| const time = context.ohlcv.time[index]; | ||
| if (time !== void 0 && value !== void 0 && !Number.isNaN(value)) { | ||
| const point = { time, value }; | ||
| if (colors && colors[index]) { | ||
| point.color = colors[index]; | ||
| } | ||
| data.push(point); | ||
| } | ||
| } | ||
| seriesHandle.setData(data); | ||
| activePlots.push({ id: plotId, series: seriesHandle }); | ||
| return plotId; | ||
| } | ||
| function hline(price, title, color, linestyle, linewidth) { | ||
| if (!context) { | ||
| throw new Error("OakScript context not set. Call setContext() before creating hlines."); | ||
| } | ||
| const hlineId = `hline_${plotCounter++}${title ? "_" + title.replace(/\s+/g, "_") : ""}`; | ||
| const options = { | ||
| color, | ||
| lineWidth: linewidth, | ||
| lineStyle: getLineStyle(linestyle) | ||
| }; | ||
| const seriesHandle = context.chart.addSeries("line", options); | ||
| const data = context.ohlcv.time.map((time) => ({ | ||
| time, | ||
| value: price | ||
| })); | ||
| seriesHandle.setData(data); | ||
| activePlots.push({ id: hlineId, series: seriesHandle }); | ||
| return hlineId; | ||
| } | ||
| function getActivePlots() { | ||
| return activePlots; | ||
| } | ||
| // src/runtime/inputs.ts | ||
| var registeredInputs = /* @__PURE__ */ new Map(); | ||
| var autoRecalculateEnabled = false; | ||
| function enableAutoRecalculate() { | ||
| autoRecalculateEnabled = true; | ||
| } | ||
| function disableAutoRecalculate() { | ||
| autoRecalculateEnabled = false; | ||
| } | ||
| function resetInputs() { | ||
| registeredInputs.clear(); | ||
| inputCounter = 0; | ||
| } | ||
| var inputCounter = 0; | ||
| function generateInputId(title, defval, type) { | ||
| if (title) { | ||
| return title.replace(/\s+/g, "_"); | ||
| } | ||
| return `${type}_${inputCounter++}_${String(defval)}`; | ||
| } | ||
| function input_int(defval, title, options) { | ||
| const context2 = getContext(); | ||
| if (!context2) { | ||
| return Math.floor(defval); | ||
| } | ||
| const id = generateInputId(title, defval, "int"); | ||
| const config = { | ||
| id, | ||
| type: "int", | ||
| defval: Math.floor(defval), | ||
| title, | ||
| min: options?.min, | ||
| max: options?.max, | ||
| step: options?.step ?? 1 | ||
| }; | ||
| if (!registeredInputs.has(id)) { | ||
| context2.inputs.registerInput(config); | ||
| registeredInputs.set(id, true); | ||
| if (autoRecalculateEnabled) { | ||
| context2.inputs.onInputChange((changedId) => { | ||
| if (changedId === id) { | ||
| recalculate(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| const value = context2.inputs.getValue(id); | ||
| return typeof value === "number" ? Math.floor(value) : Math.floor(defval); | ||
| } | ||
| function input_float(defval, title, options) { | ||
| const context2 = getContext(); | ||
| if (!context2) { | ||
| return defval; | ||
| } | ||
| const id = generateInputId(title, defval, "float"); | ||
| const config = { | ||
| id, | ||
| type: "float", | ||
| defval, | ||
| title, | ||
| min: options?.min, | ||
| max: options?.max, | ||
| step: options?.step ?? 0.1 | ||
| }; | ||
| if (!registeredInputs.has(id)) { | ||
| context2.inputs.registerInput(config); | ||
| registeredInputs.set(id, true); | ||
| if (autoRecalculateEnabled) { | ||
| context2.inputs.onInputChange((changedId) => { | ||
| if (changedId === id) { | ||
| recalculate(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| const value = context2.inputs.getValue(id); | ||
| return typeof value === "number" ? value : defval; | ||
| } | ||
| function input_bool(defval, title) { | ||
| const context2 = getContext(); | ||
| if (!context2) { | ||
| return defval; | ||
| } | ||
| const id = generateInputId(title, defval, "bool"); | ||
| const config = { | ||
| id, | ||
| type: "bool", | ||
| defval, | ||
| title | ||
| }; | ||
| if (!registeredInputs.has(id)) { | ||
| context2.inputs.registerInput(config); | ||
| registeredInputs.set(id, true); | ||
| if (autoRecalculateEnabled) { | ||
| context2.inputs.onInputChange((changedId) => { | ||
| if (changedId === id) { | ||
| recalculate(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| const value = context2.inputs.getValue(id); | ||
| return typeof value === "boolean" ? value : defval; | ||
| } | ||
| function input_string(defval, title, options) { | ||
| const context2 = getContext(); | ||
| if (!context2) { | ||
| return defval; | ||
| } | ||
| const id = generateInputId(title, defval, "string"); | ||
| const config = { | ||
| id, | ||
| type: "string", | ||
| defval, | ||
| title, | ||
| options | ||
| }; | ||
| if (!registeredInputs.has(id)) { | ||
| context2.inputs.registerInput(config); | ||
| registeredInputs.set(id, true); | ||
| if (autoRecalculateEnabled) { | ||
| context2.inputs.onInputChange((changedId) => { | ||
| if (changedId === id) { | ||
| recalculate(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| const value = context2.inputs.getValue(id); | ||
| return typeof value === "string" ? value : defval; | ||
| } | ||
| function input_source(defval, title) { | ||
| const context2 = getContext(); | ||
| if (!context2) { | ||
| return []; | ||
| } | ||
| const id = generateInputId(title, defval, "source"); | ||
| const config = { | ||
| id, | ||
| type: "source", | ||
| defval, | ||
| title, | ||
| options: ["open", "high", "low", "close", "volume", "hl2", "hlc3", "ohlc4"] | ||
| }; | ||
| if (!registeredInputs.has(id)) { | ||
| context2.inputs.registerInput(config); | ||
| registeredInputs.set(id, true); | ||
| if (autoRecalculateEnabled) { | ||
| context2.inputs.onInputChange((changedId) => { | ||
| if (changedId === id) { | ||
| recalculate(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| const sourceName = context2.inputs.getValue(id) ?? defval; | ||
| return getSourceData(context2.ohlcv, sourceName); | ||
| } | ||
| function getSourceData(ohlcv, sourceName) { | ||
| switch (sourceName) { | ||
| case "open": | ||
| return ohlcv.open; | ||
| case "high": | ||
| return ohlcv.high; | ||
| case "low": | ||
| return ohlcv.low; | ||
| case "close": | ||
| return ohlcv.close; | ||
| case "volume": | ||
| return ohlcv.volume; | ||
| case "hl2": | ||
| return ohlcv.high.map((h, i) => { | ||
| const l = ohlcv.low[i]; | ||
| return l !== void 0 ? (h + l) / 2 : NaN; | ||
| }); | ||
| case "hlc3": | ||
| return ohlcv.high.map((h, i) => { | ||
| const l = ohlcv.low[i]; | ||
| const c = ohlcv.close[i]; | ||
| return l !== void 0 && c !== void 0 ? (h + l + c) / 3 : NaN; | ||
| }); | ||
| case "ohlc4": | ||
| return ohlcv.open.map((o, i) => { | ||
| const h = ohlcv.high[i]; | ||
| const l = ohlcv.low[i]; | ||
| const c = ohlcv.close[i]; | ||
| return h !== void 0 && l !== void 0 && c !== void 0 ? (o + h + l + c) / 4 : NaN; | ||
| }); | ||
| default: | ||
| return ohlcv.close; | ||
| } | ||
| } | ||
| // src/runtime/adapters/LightweightChartsAdapter.ts | ||
| var LightweightChartsAdapter = class { | ||
| chart; | ||
| mainSeries; | ||
| paneCount = 0; | ||
| /** | ||
| * Create a new LightweightChartsAdapter | ||
| * @param chart - The lightweight-charts chart instance | ||
| * @param mainSeries - Optional main price series (candlestick/line series) | ||
| */ | ||
| constructor(chart, mainSeries) { | ||
| this.chart = chart; | ||
| if (mainSeries) { | ||
| this.mainSeries = this.wrapSeries(mainSeries); | ||
| } | ||
| } | ||
| /** | ||
| * Wrap a lightweight-charts series to implement SeriesHandle | ||
| * @param lwcSeries - The lightweight-charts series | ||
| * @returns SeriesHandle wrapper | ||
| */ | ||
| wrapSeries(lwcSeries) { | ||
| return { | ||
| setData: (data) => { | ||
| lwcSeries.setData(data); | ||
| }, | ||
| // Store reference for removal | ||
| _lwcSeries: lwcSeries | ||
| }; | ||
| } | ||
| /** | ||
| * Map series type string to lightweight-charts series definition | ||
| * @param type - Series type ('line', 'histogram', 'area') | ||
| * @returns Lightweight-charts series definition object | ||
| */ | ||
| getSeriesDefinition(type) { | ||
| switch (type) { | ||
| case "histogram": | ||
| return { type: "Histogram" }; | ||
| case "area": | ||
| return { type: "Area" }; | ||
| case "baseline": | ||
| return { type: "Baseline" }; | ||
| case "bar": | ||
| return { type: "Bar" }; | ||
| case "line": | ||
| default: | ||
| return { type: "Line" }; | ||
| } | ||
| } | ||
| /** | ||
| * Convert OakScriptJS series options to lightweight-charts options | ||
| * @param options - OakScriptJS series options | ||
| * @returns Lightweight-charts compatible options | ||
| */ | ||
| convertOptions(options) { | ||
| if (!options) { | ||
| return {}; | ||
| } | ||
| const lwcOptions = {}; | ||
| if (options.color) { | ||
| lwcOptions.color = options.color; | ||
| } | ||
| if (options.lineWidth !== void 0) { | ||
| lwcOptions.lineWidth = options.lineWidth; | ||
| } | ||
| if (options.lineStyle !== void 0) { | ||
| lwcOptions.lineStyle = options.lineStyle; | ||
| } | ||
| if (options.priceScaleId) { | ||
| lwcOptions.priceScaleId = options.priceScaleId; | ||
| } | ||
| return lwcOptions; | ||
| } | ||
| /** | ||
| * Add a new series to the chart | ||
| * @param type - Series type ('line', 'histogram', 'area') | ||
| * @param options - Series options | ||
| * @returns SeriesHandle for the new series | ||
| */ | ||
| addSeries(type, options) { | ||
| const seriesDefinition = this.getSeriesDefinition(type); | ||
| const lwcOptions = this.convertOptions(options); | ||
| const lwcSeries = this.chart.addSeries(seriesDefinition, lwcOptions); | ||
| return this.wrapSeries(lwcSeries); | ||
| } | ||
| /** | ||
| * Remove a series from the chart | ||
| * @param series - SeriesHandle to remove | ||
| */ | ||
| removeSeries(series) { | ||
| const wrappedSeries = series; | ||
| if (wrappedSeries._lwcSeries) { | ||
| this.chart.removeSeries(wrappedSeries._lwcSeries); | ||
| } | ||
| } | ||
| /** | ||
| * Get the main price series | ||
| * @returns Main series handle or undefined | ||
| */ | ||
| getMainSeries() { | ||
| return this.mainSeries; | ||
| } | ||
| /** | ||
| * Create a new pane (if supported by the chart configuration) | ||
| * @returns Index of the new pane | ||
| */ | ||
| createPane() { | ||
| return ++this.paneCount; | ||
| } | ||
| }; | ||
| // src/runtime/adapters/SimpleInputAdapter.ts | ||
| var SimpleInputAdapter = class { | ||
| inputs = /* @__PURE__ */ new Map(); | ||
| changeCallbacks = []; | ||
| /** | ||
| * Register an input configuration | ||
| * If already registered, returns current value without modifying config | ||
| * @param config - Input configuration | ||
| * @returns Current value (defval on first registration) | ||
| */ | ||
| registerInput(config) { | ||
| if (!this.inputs.has(config.id)) { | ||
| this.inputs.set(config.id, { config, value: config.defval }); | ||
| } | ||
| return this.inputs.get(config.id).value; | ||
| } | ||
| /** | ||
| * Get current value of an input | ||
| * @param id - Input identifier | ||
| * @returns Current value or undefined if not registered | ||
| */ | ||
| getValue(id) { | ||
| return this.inputs.get(id)?.value; | ||
| } | ||
| /** | ||
| * Set value of an input with validation | ||
| * Validates against config constraints (min/max/step for numerics, options for strings) | ||
| * @param id - Input identifier | ||
| * @param value - New value | ||
| */ | ||
| setValue(id, value) { | ||
| const input = this.inputs.get(id); | ||
| if (!input) { | ||
| return; | ||
| } | ||
| const { config } = input; | ||
| let validatedValue = value; | ||
| if ((config.type === "int" || config.type === "float") && typeof value === "number") { | ||
| if (config.min !== void 0 && value < config.min) { | ||
| validatedValue = config.min; | ||
| } | ||
| if (config.max !== void 0 && value > config.max) { | ||
| validatedValue = config.max; | ||
| } | ||
| if (config.type === "int") { | ||
| validatedValue = Math.floor(validatedValue); | ||
| } | ||
| } | ||
| if (config.type === "string" && config.options && typeof value === "string") { | ||
| if (!config.options.includes(value)) { | ||
| return; | ||
| } | ||
| } | ||
| if (config.type === "source" && config.options && typeof value === "string") { | ||
| if (!config.options.includes(value)) { | ||
| return; | ||
| } | ||
| } | ||
| input.value = validatedValue; | ||
| this.changeCallbacks.forEach((cb) => cb(id, validatedValue)); | ||
| } | ||
| /** | ||
| * Register a callback for input value changes | ||
| * @param callback - Function to call when any input value changes | ||
| */ | ||
| onInputChange(callback) { | ||
| this.changeCallbacks.push(callback); | ||
| } | ||
| /** | ||
| * Get all registered inputs (for UI rendering) | ||
| * @returns Map of input IDs to their configs and values | ||
| */ | ||
| getAllInputs() { | ||
| return new Map(this.inputs); | ||
| } | ||
| /** | ||
| * Clear all registered inputs | ||
| * Useful for testing or resetting state | ||
| */ | ||
| clear() { | ||
| this.inputs.clear(); | ||
| this.changeCallbacks = []; | ||
| } | ||
| /** | ||
| * Remove a specific change callback | ||
| * @param callback - The callback to remove | ||
| */ | ||
| removeChangeCallback(callback) { | ||
| const index = this.changeCallbacks.indexOf(callback); | ||
| if (index !== -1) { | ||
| this.changeCallbacks.splice(index, 1); | ||
| } | ||
| } | ||
| }; |
527229
0