hyperprop-charting-library 0.1.136 → 0.1.137

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,8 +22,10 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  FIB_DEFAULT_PALETTE: () => FIB_DEFAULT_PALETTE,
24
24
  POSITION_DEFAULT_COLORS: () => POSITION_DEFAULT_COLORS,
25
+ SCRIPT_SOURCE_INPUT_VALUES: () => SCRIPT_SOURCE_INPUT_VALUES,
25
26
  compileScriptIndicator: () => compileScriptIndicator,
26
27
  createChart: () => createChart,
28
+ extractScriptInputs: () => extractScriptInputs,
27
29
  validateScriptSource: () => validateScriptSource
28
30
  });
29
31
  module.exports = __toCommonJS(index_exports);
@@ -2151,6 +2153,82 @@ var hashScriptSource = (source) => {
2151
2153
  }
2152
2154
  return (hash >>> 0).toString(36);
2153
2155
  };
2156
+ var SCRIPT_SOURCE_INPUT_VALUES = ["open", "high", "low", "close", "hl2", "hlc3", "ohlc4"];
2157
+ var slugifyInputLabel = (label) => {
2158
+ const slug = String(label).trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
2159
+ return slug || "input";
2160
+ };
2161
+ var createScriptInputHelper = (values, registry) => {
2162
+ const usedKeys = /* @__PURE__ */ new Set();
2163
+ const resolveKey = (label, options) => {
2164
+ let key = options?.key ?? slugifyInputLabel(label);
2165
+ if (usedKeys.has(key)) {
2166
+ let suffix = 2;
2167
+ while (usedKeys.has(`${key}_${suffix}`)) suffix += 1;
2168
+ key = `${key}_${suffix}`;
2169
+ }
2170
+ usedKeys.add(key);
2171
+ return key;
2172
+ };
2173
+ const resolve = (key, fallback, coerce) => {
2174
+ if (!values) return fallback;
2175
+ const raw = values[key];
2176
+ if (raw === void 0 || raw === null) return fallback;
2177
+ const coerced = coerce(raw);
2178
+ return coerced === void 0 ? fallback : coerced;
2179
+ };
2180
+ const coerceNumber = (raw) => {
2181
+ const num = typeof raw === "number" ? raw : Number(raw);
2182
+ return Number.isFinite(num) ? num : void 0;
2183
+ };
2184
+ const numberInput = (integer) => (label, defaultValue, options) => {
2185
+ const key = resolveKey(label, options);
2186
+ registry?.push({
2187
+ key,
2188
+ label,
2189
+ type: "number",
2190
+ default: defaultValue,
2191
+ ...options?.min !== void 0 ? { min: options.min } : {},
2192
+ ...options?.max !== void 0 ? { max: options.max } : {},
2193
+ ...options?.step !== void 0 ? { step: options.step } : integer ? { step: 1 } : {}
2194
+ });
2195
+ const value = resolve(key, defaultValue, coerceNumber);
2196
+ return integer ? Math.round(value) : value;
2197
+ };
2198
+ const selectInput = (label, defaultValue, choices, options) => {
2199
+ const key = resolveKey(label, options);
2200
+ const normalized = (Array.isArray(choices) ? choices : []).map(
2201
+ (choice) => typeof choice === "string" ? { value: choice, label: choice } : { value: String(choice.value), label: String(choice.label ?? choice.value) }
2202
+ );
2203
+ registry?.push({ key, label, type: "select", default: defaultValue, options: normalized });
2204
+ return resolve(key, defaultValue, (raw) => typeof raw === "string" ? raw : String(raw));
2205
+ };
2206
+ return Object.freeze({
2207
+ int: numberInput(true),
2208
+ float: numberInput(false),
2209
+ bool: (label, defaultValue = false, options) => {
2210
+ const key = resolveKey(label, options);
2211
+ registry?.push({ key, label, type: "boolean", default: defaultValue });
2212
+ return resolve(
2213
+ key,
2214
+ defaultValue,
2215
+ (raw) => typeof raw === "boolean" ? raw : raw === "true" ? true : raw === "false" ? false : void 0
2216
+ );
2217
+ },
2218
+ color: (label, defaultValue, options) => {
2219
+ const key = resolveKey(label, options);
2220
+ registry?.push({ key, label, type: "color", default: defaultValue });
2221
+ return resolve(key, defaultValue, (raw) => typeof raw === "string" ? raw : void 0);
2222
+ },
2223
+ string: (label, defaultValue = "", options) => {
2224
+ const key = resolveKey(label, options);
2225
+ registry?.push({ key, label, type: "string", default: defaultValue });
2226
+ return resolve(key, defaultValue, (raw) => typeof raw === "string" ? raw : String(raw));
2227
+ },
2228
+ select: selectInput,
2229
+ source: (label = "Source", defaultValue = "close", options) => selectInput(label, defaultValue, SCRIPT_SOURCE_INPUT_VALUES, options)
2230
+ });
2231
+ };
2154
2232
  var SCRIPT_GUARD_BUDGET_MS = 1e3;
2155
2233
  var ScriptBudgetError = class extends Error {
2156
2234
  constructor() {
@@ -2425,31 +2503,34 @@ var injectLoopGuards = (source, guardName) => {
2425
2503
  }
2426
2504
  return out;
2427
2505
  };
2428
- var compileScriptCompute = (source, guard) => {
2506
+ var compileScriptFactory = (source) => {
2429
2507
  const makeFactory = (body) => new Function(
2430
2508
  "__hpGuard",
2509
+ "input",
2431
2510
  `"use strict";
2432
2511
  ${body}
2433
2512
  ;if (typeof compute !== "function") { throw new Error("Script must define function compute(bars, inputs, hp)"); }
2434
2513
  return compute;`
2435
2514
  );
2436
- let factory;
2437
2515
  try {
2438
- factory = makeFactory(injectLoopGuards(source, "__hpGuard"));
2516
+ return makeFactory(injectLoopGuards(source, "__hpGuard"));
2439
2517
  } catch {
2440
- factory = makeFactory(source);
2518
+ return makeFactory(source);
2441
2519
  }
2442
- guard.reset();
2443
- return factory(guard);
2444
2520
  };
2445
- var validateScriptSource = (source) => {
2521
+ var extractScriptInputs = (source) => {
2522
+ const registry = [];
2446
2523
  try {
2447
- compileScriptCompute(source, createScriptGuard());
2448
- return null;
2524
+ const factory = compileScriptFactory(source);
2525
+ const guard = createScriptGuard();
2526
+ guard.reset();
2527
+ factory(guard, createScriptInputHelper(null, registry));
2528
+ return { inputs: registry, error: null };
2449
2529
  } catch (error) {
2450
- return error instanceof Error ? error.message : String(error);
2530
+ return { inputs: registry, error: error instanceof Error ? error.message : String(error) };
2451
2531
  }
2452
2532
  };
2533
+ var validateScriptSource = (source) => extractScriptInputs(source).error;
2453
2534
  var drawScriptError = (ctx, renderContext, name, message) => {
2454
2535
  ctx.save();
2455
2536
  ctx.font = "11px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
@@ -2466,18 +2547,24 @@ var compileScriptIndicator = (definition) => {
2466
2547
  for (const input of definition.inputs ?? []) {
2467
2548
  defaultInputs[input.key] = input.default;
2468
2549
  }
2469
- let compiled = null;
2550
+ let factory = null;
2470
2551
  let compileError = null;
2471
2552
  const guard = createScriptGuard();
2472
2553
  try {
2473
- compiled = compileScriptCompute(definition.source, guard);
2554
+ factory = compileScriptFactory(definition.source);
2555
+ const declared = [];
2556
+ guard.reset();
2557
+ factory(guard, createScriptInputHelper(null, declared));
2558
+ for (const input of declared) {
2559
+ defaultInputs[input.key] = input.default;
2560
+ }
2474
2561
  } catch (error) {
2475
2562
  compileError = error instanceof Error ? error.message : String(error);
2476
2563
  }
2477
2564
  const sourceKey = hashScriptSource(definition.source);
2478
2565
  let trippedError = null;
2479
2566
  const runCompute = (data, inputs) => {
2480
- if (!compiled) {
2567
+ if (!factory || compileError) {
2481
2568
  return { error: compileError ?? "Script failed to compile" };
2482
2569
  }
2483
2570
  if (trippedError) {
@@ -2485,6 +2572,7 @@ var compileScriptIndicator = (definition) => {
2485
2572
  }
2486
2573
  try {
2487
2574
  guard.reset();
2575
+ const compiled = factory(guard, createScriptInputHelper(inputs, null));
2488
2576
  const result = compiled(data, inputs, SCRIPT_HELPERS);
2489
2577
  if (!result || !Array.isArray(result.plots)) {
2490
2578
  return { error: "compute() must return { plots: [...] }" };
@@ -2979,6 +3067,24 @@ function createChart(element, options = {}) {
2979
3067
  let drawingDoubleClickHandler = null;
2980
3068
  let drawingEditTextHandler = null;
2981
3069
  let magnetMode = "none";
3070
+ let priceScaleMode = "linear";
3071
+ let priceScaleInverted = false;
3072
+ const PRICE_SCALE_LOG_FLOOR = 1e-9;
3073
+ const priceScaleTransform = (price) => priceScaleMode === "log" ? Math.log10(Math.max(PRICE_SCALE_LOG_FLOOR, price)) : price;
3074
+ const priceScaleUntransform = (value) => priceScaleMode === "log" ? Math.pow(10, value) : value;
3075
+ const percentBaselineForXStart = (xStartValue) => {
3076
+ if (priceScaleMode !== "percent") return null;
3077
+ const bar = data[clamp(Math.round(xStartValue), 0, Math.max(0, data.length - 1))];
3078
+ return bar && bar.c > 0 ? bar.c : null;
3079
+ };
3080
+ const formatPriceForScale = (price, percentBaseline) => {
3081
+ if (percentBaseline !== null) {
3082
+ const pct = (price / percentBaseline - 1) * 100;
3083
+ const rounded = Math.abs(pct) < 5e-3 ? 0 : pct;
3084
+ return `${rounded > 0 ? "+" : ""}${rounded.toFixed(2)}%`;
3085
+ }
3086
+ return formatPrice(price);
3087
+ };
2982
3088
  let magnetModifierActive = false;
2983
3089
  let shiftKeyActive = false;
2984
3090
  const MAGNET_WEAK_THRESHOLD_PX = 16;
@@ -4312,9 +4418,14 @@ function createChart(element, options = {}) {
4312
4418
  yMax
4313
4419
  };
4314
4420
  plotBottomForHit = fullChartBottom;
4421
+ const scaledYMin = priceScaleTransform(yMin);
4422
+ const scaledYRange = priceScaleTransform(yMax) - scaledYMin || 1;
4315
4423
  const yFromPrice = (price) => {
4316
- return chartBottom - (price - yMin) / yRange * chartHeight;
4424
+ const ratio = (priceScaleTransform(price) - scaledYMin) / scaledYRange;
4425
+ return priceScaleInverted ? chartTop + ratio * chartHeight : chartBottom - ratio * chartHeight;
4317
4426
  };
4427
+ const percentBaseline = percentBaselineForXStart(xStart);
4428
+ const formatAxisPrice = (price) => formatPriceForScale(price, percentBaseline);
4318
4429
  const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
4319
4430
  const FIB_RATIOS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
4320
4431
  const FIB_EXT_RATIOS = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
@@ -5221,19 +5332,40 @@ function createChart(element, options = {}) {
5221
5332
  const yTicks = Math.max(1, Math.floor(yTickCountInput));
5222
5333
  const gridColor = grid.color ?? mergedOptions.gridColor;
5223
5334
  const priceTicks = [];
5224
- {
5225
- const targetStep = yRange / Math.max(1, yTicks);
5226
- let step;
5227
- if (targetStep > 0 && Number.isFinite(targetStep)) {
5228
- const base = Math.pow(10, Math.floor(Math.log10(targetStep)));
5229
- const frac = targetStep / base;
5230
- step = (frac <= 1 ? 1 : frac <= 2 ? 2 : frac <= 2.5 ? 2.5 : frac <= 5 ? 5 : 10) * base;
5231
- const instrumentTick = getConfiguredTickSize();
5232
- if (instrumentTick > 0 && step < instrumentTick * 1e6) {
5233
- step = Math.max(instrumentTick, Math.round(step / instrumentTick) * instrumentTick);
5335
+ const pickNiceStep = (targetStep) => {
5336
+ if (!(targetStep > 0) || !Number.isFinite(targetStep)) return 1;
5337
+ const base = Math.pow(10, Math.floor(Math.log10(targetStep)));
5338
+ const frac = targetStep / base;
5339
+ return (frac <= 1 ? 1 : frac <= 2 ? 2 : frac <= 2.5 ? 2.5 : frac <= 5 ? 5 : 10) * base;
5340
+ };
5341
+ if (percentBaseline !== null) {
5342
+ const pctMin = (yMin / percentBaseline - 1) * 100;
5343
+ const pctMax = (yMax / percentBaseline - 1) * 100;
5344
+ const step = pickNiceStep((pctMax - pctMin) / Math.max(1, yTicks));
5345
+ const firstTick = Math.ceil(pctMin / step) * step;
5346
+ for (let i = 0; ; i += 1) {
5347
+ const pct = firstTick + i * step;
5348
+ if (pct > pctMax + step * 1e-6) break;
5349
+ priceTicks.push(percentBaseline * (1 + pct / 100));
5350
+ if (priceTicks.length > 200) break;
5351
+ }
5352
+ } else if (priceScaleMode === "log" && yMin > 0 && yMax / yMin > 4) {
5353
+ const mantissas = Math.log10(yMax / yMin) > 8 ? [1] : [1, 2, 5];
5354
+ const firstDecade = Math.floor(Math.log10(Math.max(PRICE_SCALE_LOG_FLOOR, yMin)));
5355
+ const lastDecade = Math.ceil(Math.log10(yMax));
5356
+ for (let decade = firstDecade; decade <= lastDecade; decade += 1) {
5357
+ for (const mantissa of mantissas) {
5358
+ const price = mantissa * Math.pow(10, decade);
5359
+ if (price >= yMin && price <= yMax) priceTicks.push(price);
5360
+ if (priceTicks.length > 200) break;
5234
5361
  }
5235
- } else {
5236
- step = 1;
5362
+ }
5363
+ } else {
5364
+ const targetStep = yRange / Math.max(1, yTicks);
5365
+ let step = pickNiceStep(targetStep);
5366
+ const instrumentTick = getConfiguredTickSize();
5367
+ if (targetStep > 0 && Number.isFinite(targetStep) && instrumentTick > 0 && step < instrumentTick * 1e6) {
5368
+ step = Math.max(instrumentTick, Math.round(step / instrumentTick) * instrumentTick);
5237
5369
  }
5238
5370
  const firstTick = Math.ceil(yMin / step) * step;
5239
5371
  for (let i = 0; ; i += 1) {
@@ -5996,7 +6128,7 @@ function createChart(element, options = {}) {
5996
6128
  ctx.font = `${yAxisFontSize}px ${mergedOptions.fontFamily}`;
5997
6129
  for (const price of priceTicks) {
5998
6130
  const y = clamp(yFromPrice(price), chartTop + priceScaleTickLabelInset, chartBottom - priceScaleTickLabelInset);
5999
- drawText(formatPrice(price), chartRight + 6, y, "left", "middle", yAxis.textColor);
6131
+ drawText(formatAxisPrice(price), chartRight + 6, y, "left", "middle", yAxis.textColor);
6000
6132
  }
6001
6133
  ctx.font = prevFont;
6002
6134
  }
@@ -6071,7 +6203,7 @@ function createChart(element, options = {}) {
6071
6203
  ...ticker.showCountdownInLabel ? [getCountdownText()] : []
6072
6204
  ].map((value) => value === null || value === void 0 ? "" : String(value).trim()).filter((value) => value.length > 0);
6073
6205
  addPriceAxisLabel({
6074
- text: formatPrice(tickerPrice),
6206
+ text: formatAxisPrice(tickerPrice),
6075
6207
  ...tickerSubtexts.length > 0 ? { subtexts: tickerSubtexts } : {},
6076
6208
  subtextColor: ticker.labelSubtextColor ?? ticker.labelTextColor ?? "#0b1220",
6077
6209
  ...ticker.labelSubtextFontSize === void 0 ? {} : { subtextFontSize: ticker.labelSubtextFontSize },
@@ -6098,7 +6230,7 @@ function createChart(element, options = {}) {
6098
6230
  const previousClose = previousCloseCandidate;
6099
6231
  drawReferenceLine(previousClose, labels.previousCloseColor, "dashed");
6100
6232
  addPriceAxisLabel({
6101
- text: `PDC ${formatPrice(previousClose)}`,
6233
+ text: `PDC ${formatAxisPrice(previousClose)}`,
6102
6234
  price: previousClose,
6103
6235
  backgroundColor: labels.backgroundColor,
6104
6236
  textColor: labels.mutedTextColor,
@@ -6117,7 +6249,7 @@ function createChart(element, options = {}) {
6117
6249
  if (point.l < visibleLow) visibleLow = point.l;
6118
6250
  }
6119
6251
  addPriceAxisLabel({
6120
- text: `H ${formatPrice(visibleHigh)}`,
6252
+ text: `H ${formatAxisPrice(visibleHigh)}`,
6121
6253
  price: visibleHigh,
6122
6254
  backgroundColor: labels.backgroundColor,
6123
6255
  textColor: labels.textColor,
@@ -6125,7 +6257,7 @@ function createChart(element, options = {}) {
6125
6257
  priority: 40
6126
6258
  });
6127
6259
  addPriceAxisLabel({
6128
- text: `L ${formatPrice(visibleLow)}`,
6260
+ text: `L ${formatAxisPrice(visibleLow)}`,
6129
6261
  price: visibleLow,
6130
6262
  backgroundColor: labels.backgroundColor,
6131
6263
  textColor: labels.textColor,
@@ -6137,7 +6269,7 @@ function createChart(element, options = {}) {
6137
6269
  if (Number.isFinite(labels.bidPrice)) {
6138
6270
  drawReferenceLine(labels.bidPrice, labels.bidColor, "dotted");
6139
6271
  addPriceAxisLabel({
6140
- text: `B ${formatPrice(labels.bidPrice)}`,
6272
+ text: `B ${formatAxisPrice(labels.bidPrice)}`,
6141
6273
  price: labels.bidPrice,
6142
6274
  backgroundColor: labels.bidColor,
6143
6275
  textColor: "#0b1220",
@@ -6148,7 +6280,7 @@ function createChart(element, options = {}) {
6148
6280
  if (Number.isFinite(labels.askPrice)) {
6149
6281
  drawReferenceLine(labels.askPrice, labels.askColor, "dotted");
6150
6282
  addPriceAxisLabel({
6151
- text: `A ${formatPrice(labels.askPrice)}`,
6283
+ text: `A ${formatAxisPrice(labels.askPrice)}`,
6152
6284
  price: labels.askPrice,
6153
6285
  backgroundColor: labels.askColor,
6154
6286
  textColor: "#0b1220",
@@ -6409,8 +6541,11 @@ function createChart(element, options = {}) {
6409
6541
  ctx.restore();
6410
6542
  };
6411
6543
  if (crosshair.showPriceLabel) {
6412
- const hoverPrice = yMin + (chartBottom - cy) / chartHeight * yRange;
6413
- const priceText = formatPrice(hoverPrice);
6544
+ const scaledYMin = priceScaleTransform(yMin);
6545
+ const scaledYRange = priceScaleTransform(yMin + yRange) - scaledYMin || 1;
6546
+ const hoverRatio = priceScaleInverted ? (cy - chartTop) / chartHeight : (chartBottom - cy) / chartHeight;
6547
+ const hoverPrice = priceScaleUntransform(scaledYMin + hoverRatio * scaledYRange);
6548
+ const priceText = formatPriceForScale(hoverPrice, percentBaselineForXStart(xStart));
6414
6549
  const priceWidth = getRightAxisLabelWidth(chartRight, getPriceLabelWidth(priceText, labelPaddingX));
6415
6550
  const priceX = getRightAxisLabelX(chartRight);
6416
6551
  const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
@@ -6578,8 +6713,8 @@ function createChart(element, options = {}) {
6578
6713
  const maxRange = bounds.hardSpan;
6579
6714
  const nextRange = clamp(currentRange * factor, minRange, maxRange);
6580
6715
  const anchorRatio = clamp((anchorY - drawState.chartTop) / drawState.chartHeight, 0, 1);
6581
- const anchorPrice = currentMax - anchorRatio * currentRange;
6582
- const nextMax = anchorPrice + anchorRatio * nextRange;
6716
+ const anchorPrice = priceScaleInverted ? currentMin + anchorRatio * currentRange : currentMax - anchorRatio * currentRange;
6717
+ const nextMax = priceScaleInverted ? anchorPrice + (1 - anchorRatio) * nextRange : anchorPrice + anchorRatio * nextRange;
6583
6718
  const nextMin = nextMax - nextRange;
6584
6719
  const clamped = clampYRange(nextMin, nextMax);
6585
6720
  yMinOverride = clamped.min;
@@ -6603,7 +6738,7 @@ function createChart(element, options = {}) {
6603
6738
  const currentMin = yMinOverride ?? drawState.yMin;
6604
6739
  const currentMax = yMaxOverride ?? drawState.yMax;
6605
6740
  const range = currentMax - currentMin || 1;
6606
- const priceShift = deltaY / drawState.chartHeight * range;
6741
+ const priceShift = deltaY / drawState.chartHeight * range * (priceScaleInverted ? -1 : 1);
6607
6742
  const clamped = clampYRange(currentMin + priceShift, currentMax + priceShift);
6608
6743
  yMinOverride = clamped.min;
6609
6744
  yMaxOverride = clamped.max;
@@ -6836,8 +6971,14 @@ function createChart(element, options = {}) {
6836
6971
  if (!drawState) {
6837
6972
  return 0;
6838
6973
  }
6839
- const ratio = clamp((drawState.chartBottom - y) / drawState.chartHeight, 0, 1);
6840
- return drawState.yMin + ratio * (drawState.yMax - drawState.yMin);
6974
+ const ratio = clamp(
6975
+ priceScaleInverted ? (y - drawState.chartTop) / drawState.chartHeight : (drawState.chartBottom - y) / drawState.chartHeight,
6976
+ 0,
6977
+ 1
6978
+ );
6979
+ const scaledMin = priceScaleTransform(drawState.yMin);
6980
+ const scaledMax = priceScaleTransform(drawState.yMax);
6981
+ return priceScaleUntransform(scaledMin + ratio * (scaledMax - scaledMin));
6841
6982
  };
6842
6983
  const applyMagnet = (y, index, price) => {
6843
6984
  const effective = magnetModifierActive ? "strong" : magnetMode;
@@ -6909,8 +7050,10 @@ function createChart(element, options = {}) {
6909
7050
  };
6910
7051
  const canvasYFromDrawingPrice = (price) => {
6911
7052
  if (!drawState) return 0;
6912
- const range = drawState.yMax - drawState.yMin || 1;
6913
- return drawState.chartBottom - (price - drawState.yMin) / range * drawState.chartHeight;
7053
+ const scaledMin = priceScaleTransform(drawState.yMin);
7054
+ const scaledRange = priceScaleTransform(drawState.yMax) - scaledMin || 1;
7055
+ const ratio = (priceScaleTransform(price) - scaledMin) / scaledRange;
7056
+ return priceScaleInverted ? drawState.chartTop + ratio * drawState.chartHeight : drawState.chartBottom - ratio * drawState.chartHeight;
6914
7057
  };
6915
7058
  const distanceToSegment = (x, y, x1, y1, x2, y2) => {
6916
7059
  const dx = x2 - x1;
@@ -8924,6 +9067,16 @@ function createChart(element, options = {}) {
8924
9067
  magnetMode = mode;
8925
9068
  };
8926
9069
  const getMagnetMode = () => magnetMode;
9070
+ const setPriceScale = (options2) => {
9071
+ if (options2.mode === "linear" || options2.mode === "log" || options2.mode === "percent") {
9072
+ priceScaleMode = options2.mode;
9073
+ }
9074
+ if (typeof options2.inverted === "boolean") {
9075
+ priceScaleInverted = options2.inverted;
9076
+ }
9077
+ scheduleDraw();
9078
+ };
9079
+ const getPriceScale = () => ({ mode: priceScaleMode, inverted: priceScaleInverted });
8927
9080
  const getDrawings = () => drawings.map((drawing) => serializeDrawing(drawing));
8928
9081
  const setDrawings = (nextDrawings) => {
8929
9082
  drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
@@ -8993,6 +9146,7 @@ function createChart(element, options = {}) {
8993
9146
  drawings: getDrawings(),
8994
9147
  indicators: getIndicators(),
8995
9148
  magnetMode: getMagnetMode(),
9149
+ priceScale: getPriceScale(),
8996
9150
  drawingDefaults
8997
9151
  };
8998
9152
  };
@@ -9012,6 +9166,9 @@ function createChart(element, options = {}) {
9012
9166
  if (state.magnetMode === "none" || state.magnetMode === "weak" || state.magnetMode === "strong") {
9013
9167
  setMagnetMode(state.magnetMode);
9014
9168
  }
9169
+ if (state.priceScale && typeof state.priceScale === "object") {
9170
+ setPriceScale(state.priceScale);
9171
+ }
9015
9172
  if (state.drawingDefaults && typeof state.drawingDefaults === "object") {
9016
9173
  for (const [tool, defaults] of Object.entries(state.drawingDefaults)) {
9017
9174
  if (defaults && typeof defaults === "object") {
@@ -9106,6 +9263,8 @@ function createChart(element, options = {}) {
9106
9263
  onDrawingEditText,
9107
9264
  setMagnetMode,
9108
9265
  getMagnetMode,
9266
+ setPriceScale,
9267
+ getPriceScale,
9109
9268
  cancelDrawing,
9110
9269
  setTradeMarkers,
9111
9270
  setSelectedDrawing,
@@ -9132,7 +9291,9 @@ function createChart(element, options = {}) {
9132
9291
  0 && (module.exports = {
9133
9292
  FIB_DEFAULT_PALETTE,
9134
9293
  POSITION_DEFAULT_COLORS,
9294
+ SCRIPT_SOURCE_INPUT_VALUES,
9135
9295
  compileScriptIndicator,
9136
9296
  createChart,
9297
+ extractScriptInputs,
9137
9298
  validateScriptSource
9138
9299
  });
@@ -583,6 +583,15 @@ interface ChartInstance {
583
583
  onDrawingEditText: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
584
584
  setMagnetMode: (mode: "none" | "weak" | "strong") => void;
585
585
  getMagnetMode: () => "none" | "weak" | "strong";
586
+ /**
587
+ * Change the price-scale mode ("linear" | "log" | "percent") and/or invert
588
+ * the axis. Log maps prices through log10, percent relabels the axis
589
+ * relative to the first visible bar's close (TradingView behavior), and
590
+ * invert flips the axis top-to-bottom. Only the main price pane is
591
+ * affected; indicator panes keep their own scales.
592
+ */
593
+ setPriceScale: (options: Partial<PriceScaleOptions>) => void;
594
+ getPriceScale: () => PriceScaleOptions;
586
595
  cancelDrawing: () => boolean;
587
596
  setTradeMarkers: (markers: TradeMarkerOptions[]) => void;
588
597
  onDrawingHover: (handler: ((event: DrawingHoverEvent) => void) | null) => void;
@@ -657,8 +666,15 @@ interface ChartSavedState {
657
666
  drawings: DrawingObjectOptions[];
658
667
  indicators: IndicatorInstanceOptions[];
659
668
  magnetMode: "none" | "weak" | "strong";
669
+ /** Optional for backward compatibility with pre-0.1.137 blobs. */
670
+ priceScale?: PriceScaleOptions;
660
671
  drawingDefaults: Partial<Record<DrawingToolType, DrawingDefaults>>;
661
672
  }
673
+ type PriceScaleMode = "linear" | "log" | "percent";
674
+ interface PriceScaleOptions {
675
+ mode: PriceScaleMode;
676
+ inverted: boolean;
677
+ }
662
678
 
663
679
  interface ScriptIndicatorInputDef {
664
680
  key: string;
@@ -706,6 +722,45 @@ interface ScriptComputeResult {
706
722
  guides?: number[];
707
723
  decimals?: number;
708
724
  }
725
+ interface ScriptInputOptions {
726
+ /** Stable settings key; defaults to a slug of the label. */
727
+ key?: string;
728
+ min?: number;
729
+ max?: number;
730
+ step?: number;
731
+ }
732
+ declare const SCRIPT_SOURCE_INPUT_VALUES: readonly ["open", "high", "low", "close", "hl2", "hlc3", "ohlc4"];
733
+ /**
734
+ * The `input` object passed to script top level. In value mode it resolves
735
+ * each declaration against the current settings; in recording mode it
736
+ * additionally appends every declaration to `registry`. Keys are assigned
737
+ * deterministically (same order both modes), so values always line up with
738
+ * the extracted schema.
739
+ */
740
+ declare const createScriptInputHelper: (values: Record<string, unknown> | null, registry: ScriptIndicatorInputDef[] | null) => Readonly<{
741
+ int: (label: string, defaultValue: number, options?: ScriptInputOptions) => number;
742
+ float: (label: string, defaultValue: number, options?: ScriptInputOptions) => number;
743
+ bool: (label: string, defaultValue?: boolean, options?: ScriptInputOptions) => boolean;
744
+ color: (label: string, defaultValue: string, options?: ScriptInputOptions) => string;
745
+ string: (label: string, defaultValue?: string, options?: ScriptInputOptions) => string;
746
+ select: (label: string, defaultValue: string, choices: ReadonlyArray<string | {
747
+ value: string;
748
+ label: string;
749
+ }>, options?: ScriptInputOptions) => string;
750
+ source: (label?: string, defaultValue?: string, options?: ScriptInputOptions) => string;
751
+ }>;
752
+ type ScriptInputHelper = ReturnType<typeof createScriptInputHelper>;
753
+ /**
754
+ * Discover a script's Pine-style `input.*` declarations by running its top
755
+ * level with a recording helper. Returns the declared schema (empty when the
756
+ * script declares nothing) plus a compile/runtime error message when the
757
+ * script is broken. Runs under the standard execution guard, so an infinite
758
+ * top-level loop returns a budget error instead of hanging the caller.
759
+ */
760
+ declare const extractScriptInputs: (source: string) => {
761
+ inputs: ScriptIndicatorInputDef[];
762
+ error: string | null;
763
+ };
709
764
  /**
710
765
  * Compile-check a script without registering it: returns an error message, or
711
766
  * `null` when the script compiles. Runs with the same execution guard as the
@@ -726,4 +781,4 @@ declare const compileScriptIndicator: (definition: ScriptIndicatorDefinition) =>
726
781
 
727
782
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
728
783
 
729
- export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type ChartSavedState, type ChartType, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptPlotSpec, type TickerLineOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart, validateScriptSource };
784
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type ChartSavedState, type ChartType, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type PriceScaleMode, type PriceScaleOptions, SCRIPT_SOURCE_INPUT_VALUES, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptInputHelper, type ScriptInputOptions, type ScriptPlotSpec, type TickerLineOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart, extractScriptInputs, validateScriptSource };