acttrader-charts 1.0.19 → 1.0.21
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.
- package/dist/{MACD-DZxpf0eQ.d.cts → MACD-DGFPNwy9.d.cts} +27 -0
- package/dist/{MACD-DZxpf0eQ.d.ts → MACD-DGFPNwy9.d.ts} +27 -0
- package/dist/index.cjs +508 -168
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +103 -5
- package/dist/index.d.ts +103 -5
- package/dist/index.js +508 -168
- package/dist/index.js.map +1 -1
- package/dist/indicators/index.d.cts +2 -2
- package/dist/indicators/index.d.ts +2 -2
- package/dist/webview/chart.html +7 -7
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -264,6 +264,8 @@ function resolveCssVarsInTheme(theme, container) {
|
|
|
264
264
|
dropdownHoverBg: resolve(theme.indicatorOverlay.dropdownHoverBg)
|
|
265
265
|
},
|
|
266
266
|
tradeLevels: {
|
|
267
|
+
buySide: resolve(theme.tradeLevels.buySide),
|
|
268
|
+
sellSide: resolve(theme.tradeLevels.sellSide),
|
|
267
269
|
tradeLine: resolve(theme.tradeLevels.tradeLine),
|
|
268
270
|
positionLine: resolve(theme.tradeLevels.positionLine),
|
|
269
271
|
pendingBuyLine: resolve(theme.tradeLevels.pendingBuyLine),
|
|
@@ -340,6 +342,8 @@ var DARK_THEME = {
|
|
|
340
342
|
dropdownHoverBg: "#1c2a35"
|
|
341
343
|
},
|
|
342
344
|
tradeLevels: {
|
|
345
|
+
buySide: "#26a69a",
|
|
346
|
+
sellSide: "#ef5350",
|
|
343
347
|
tradeLine: "#5caf79",
|
|
344
348
|
positionLine: "#7b61ff",
|
|
345
349
|
pendingBuyLine: "rgba(92,175,121,0.75)",
|
|
@@ -412,6 +416,8 @@ var LIGHT_THEME = {
|
|
|
412
416
|
dropdownHoverBg: "#f0f0f0"
|
|
413
417
|
},
|
|
414
418
|
tradeLevels: {
|
|
419
|
+
buySide: "#26a69a",
|
|
420
|
+
sellSide: "#ef5350",
|
|
415
421
|
tradeLine: "#5caf79",
|
|
416
422
|
positionLine: "#6d28d9",
|
|
417
423
|
pendingBuyLine: "rgba(92,175,121,0.75)",
|
|
@@ -697,6 +703,7 @@ var DEFAULT_LABELS = {
|
|
|
697
703
|
limit: "Limit",
|
|
698
704
|
stop: "Stop",
|
|
699
705
|
market: "Market",
|
|
706
|
+
at: "at",
|
|
700
707
|
qty: "Qty",
|
|
701
708
|
placeOrderTitle: "Place order at this price"
|
|
702
709
|
},
|
|
@@ -1672,16 +1679,18 @@ function computeFanOutLayout(cluster, boxH, gapPx = 4) {
|
|
|
1672
1679
|
return result;
|
|
1673
1680
|
}
|
|
1674
1681
|
function buildClusterBadgeLayout(cluster, chartW, ctx) {
|
|
1675
|
-
const
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1682
|
+
const posCount = cluster.members.filter(
|
|
1683
|
+
(m) => m.level.type === "position" && !m.level.entryPriceEditable
|
|
1684
|
+
).length;
|
|
1685
|
+
const orderCount = cluster.members.filter(
|
|
1686
|
+
(m) => m.level.type === "pending" || m.level.entryPriceEditable === true
|
|
1687
|
+
).length;
|
|
1688
|
+
const tradeCount = cluster.members.filter((m) => m.level.type === "trade").length;
|
|
1689
|
+
const parts = [];
|
|
1690
|
+
if (posCount > 0) parts.push(`${posCount} Position${posCount > 1 ? "s" : ""}`);
|
|
1691
|
+
if (orderCount > 0) parts.push(`${orderCount} Order${orderCount > 1 ? "s" : ""}`);
|
|
1692
|
+
if (tradeCount > 0) parts.push(`${tradeCount} Trade${tradeCount > 1 ? "s" : ""}`);
|
|
1693
|
+
const text = parts.join(" / ");
|
|
1685
1694
|
const PAD_X = 12;
|
|
1686
1695
|
const textW = ctx.measureText(text).width;
|
|
1687
1696
|
const EXPAND_ICON_W = 16;
|
|
@@ -2089,6 +2098,22 @@ function renderTradeLevels(ctx, levels, scale, priceRange, theme, chartW, priceH
|
|
|
2089
2098
|
const offMainDragActive = draggingLabel === offLevel.label;
|
|
2090
2099
|
const offSlPrice = offMainDragActive && dragNewSLPrice !== null ? dragNewSLPrice : bh.stopLossPrice;
|
|
2091
2100
|
const offTpPrice = offMainDragActive && dragNewTPPrice !== null ? dragNewTPPrice : bh.takeProfitPrice;
|
|
2101
|
+
if (offSlPrice !== void 0 && offTpPrice !== void 0) {
|
|
2102
|
+
const slY = scale.yToPixel(offSlPrice, priceRange, priceH);
|
|
2103
|
+
const tpY = scale.yToPixel(offTpPrice, priceRange, priceH);
|
|
2104
|
+
const bracketBH = Math.max(BRACKET_BOX_H, CLOSE_R * 2 + BRACKET_BOX_PAD_Y * 2);
|
|
2105
|
+
ctx.save();
|
|
2106
|
+
ctx.font = FONT_BRACKET;
|
|
2107
|
+
const closeSlot = CLOSE_R * 2 + BRACKET_BOX_PAD_X / 2;
|
|
2108
|
+
const slBW = BRACKET_BOX_PAD_X + ctx.measureText(`SL ${offSlPrice.toFixed(precision)}`).width + BRACKET_BOX_PAD_X + closeSlot;
|
|
2109
|
+
const tpBW = BRACKET_BOX_PAD_X + ctx.measureText(`TP ${offTpPrice.toFixed(precision)}`).width + BRACKET_BOX_PAD_X + closeSlot;
|
|
2110
|
+
ctx.restore();
|
|
2111
|
+
const connX = ((bracketLineXMap.get(slDragKey) ?? fallbackX) - slBW / 2 - 2 + ((bracketLineXMap.get(tpDragKey) ?? fallbackX) - tpBW / 2 - 2)) / 2;
|
|
2112
|
+
const slPillY = bracketBoxYMap.get(slDragKey) ?? slY - bracketBH / 2;
|
|
2113
|
+
const tpPillY = bracketBoxYMap.get(tpDragKey) ?? tpY - bracketBH / 2;
|
|
2114
|
+
const [upperY, lowerY] = tpPillY < slPillY ? [tpPillY, slPillY] : [slPillY, tpPillY];
|
|
2115
|
+
drawSLTPConnector(ctx, connX, upperY + bracketBH, lowerY, theme);
|
|
2116
|
+
}
|
|
2092
2117
|
if (offSlPrice !== void 0) {
|
|
2093
2118
|
drawBracketLine(
|
|
2094
2119
|
ctx,
|
|
@@ -2222,6 +2247,20 @@ function drawFanConnector(ctx, fanCenterY, realY, chartW, theme) {
|
|
|
2222
2247
|
ctx.stroke();
|
|
2223
2248
|
ctx.restore();
|
|
2224
2249
|
}
|
|
2250
|
+
function drawSLTPConnector(ctx, x, yTop, yBottom, theme) {
|
|
2251
|
+
if (yBottom - yTop < 2) return;
|
|
2252
|
+
ctx.save();
|
|
2253
|
+
ctx.strokeStyle = monoColor(theme);
|
|
2254
|
+
ctx.globalAlpha = 0.5;
|
|
2255
|
+
ctx.lineWidth = 1;
|
|
2256
|
+
ctx.setLineDash([3, 4]);
|
|
2257
|
+
ctx.beginPath();
|
|
2258
|
+
ctx.moveTo(x, yTop);
|
|
2259
|
+
ctx.lineTo(x, yBottom);
|
|
2260
|
+
ctx.stroke();
|
|
2261
|
+
ctx.setLineDash([]);
|
|
2262
|
+
ctx.restore();
|
|
2263
|
+
}
|
|
2225
2264
|
function drawDotHoverBox(ctx, level, y, dotX, dotR, theme, precision) {
|
|
2226
2265
|
const pl = level;
|
|
2227
2266
|
const rows = [];
|
|
@@ -2333,6 +2372,24 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2333
2372
|
const mainDragActive = draggingLabel === level.label;
|
|
2334
2373
|
const slRenderPrice = mainDragActive && dragNewSLPrice !== null ? dragNewSLPrice : bracketHost.stopLossPrice;
|
|
2335
2374
|
const tpRenderPrice = mainDragActive && dragNewTPPrice !== null ? dragNewTPPrice : bracketHost.takeProfitPrice;
|
|
2375
|
+
if (selected && slRenderPrice !== void 0 && tpRenderPrice !== void 0) {
|
|
2376
|
+
const slY = scale.yToPixel(slRenderPrice, priceRange, priceH);
|
|
2377
|
+
const tpY = scale.yToPixel(tpRenderPrice, priceRange, priceH);
|
|
2378
|
+
const bracketBH = hideConfirmCancel ? BRACKET_BOX_H : Math.max(BRACKET_BOX_H, closeR * 2 + BRACKET_BOX_PAD_Y * 2);
|
|
2379
|
+
ctx.save();
|
|
2380
|
+
ctx.font = FONT_BRACKET;
|
|
2381
|
+
const closeSlot = hideConfirmCancel ? 0 : closeR * 2 + BRACKET_BOX_PAD_X / 2;
|
|
2382
|
+
const slBW = BRACKET_BOX_PAD_X + ctx.measureText(`SL ${slRenderPrice.toFixed(precision)}`).width + BRACKET_BOX_PAD_X + closeSlot;
|
|
2383
|
+
const tpBW = BRACKET_BOX_PAD_X + ctx.measureText(`TP ${tpRenderPrice.toFixed(precision)}`).width + BRACKET_BOX_PAD_X + closeSlot;
|
|
2384
|
+
ctx.restore();
|
|
2385
|
+
const slBLineX = bracketLineXMap.get(slDragKey) ?? chartW * BRACKET_BASE_RATIO;
|
|
2386
|
+
const tpBLineX = bracketLineXMap.get(tpDragKey) ?? chartW * BRACKET_BASE_RATIO;
|
|
2387
|
+
const connX = (slBLineX - slBW / 2 - 2 + (tpBLineX - tpBW / 2 - 2)) / 2;
|
|
2388
|
+
const slPillY = bracketBoxYMap?.get(slDragKey) ?? slY - bracketBH / 2;
|
|
2389
|
+
const tpPillY = bracketBoxYMap?.get(tpDragKey) ?? tpY - bracketBH / 2;
|
|
2390
|
+
const [upperY, lowerY] = tpPillY < slPillY ? [tpPillY, slPillY] : [slPillY, tpPillY];
|
|
2391
|
+
drawSLTPConnector(ctx, connX, upperY + bracketBH, lowerY, theme);
|
|
2392
|
+
}
|
|
2336
2393
|
if (slRenderPrice !== void 0) {
|
|
2337
2394
|
const bLineX = bracketLineXMap.get(slDragKey) ?? chartW * BRACKET_BASE_RATIO;
|
|
2338
2395
|
drawBracketLine(
|
|
@@ -11331,6 +11388,14 @@ var DrawingManager = class {
|
|
|
11331
11388
|
this.onDrawingsChange = null;
|
|
11332
11389
|
/** Called when a text-type drawing is first completed — ChartEngine shows the text input. */
|
|
11333
11390
|
this.onStartTextEdit = null;
|
|
11391
|
+
/**
|
|
11392
|
+
* Called after drag/resize mutates a drawing's points so ChartEngine can
|
|
11393
|
+
* refresh each point's `timestamp` from the current dataStore. Required
|
|
11394
|
+
* because DrawingManager has no access to the data layer, and drag
|
|
11395
|
+
* reconstructs points from raw (barIndex, price) deltas that strip the
|
|
11396
|
+
* original point's timestamp.
|
|
11397
|
+
*/
|
|
11398
|
+
this.onPointsMutated = null;
|
|
11334
11399
|
/** Set to true by ChartEngine while the text input is focused — prevents Delete/Backspace from deleting the drawing. */
|
|
11335
11400
|
this.editingText = false;
|
|
11336
11401
|
// Drag state (move whole drawing)
|
|
@@ -11425,14 +11490,18 @@ var DrawingManager = class {
|
|
|
11425
11490
|
const dBar = point.barIndex - this.dragStartMouse.barIndex;
|
|
11426
11491
|
const dPrice = point.price - this.dragStartMouse.price;
|
|
11427
11492
|
for (let i = 0; i < this.dragDrawing.points.length; i++) {
|
|
11493
|
+
const start = this.dragStartPoints[i];
|
|
11494
|
+
if (!start) continue;
|
|
11428
11495
|
this.dragDrawing.points[i] = {
|
|
11429
|
-
barIndex:
|
|
11430
|
-
price:
|
|
11496
|
+
barIndex: start.barIndex + dBar,
|
|
11497
|
+
price: start.price + dPrice
|
|
11431
11498
|
};
|
|
11432
11499
|
}
|
|
11500
|
+
this.onPointsMutated?.(this.dragDrawing);
|
|
11433
11501
|
}
|
|
11434
11502
|
if (this.resizeDrawing && this.resizePointIndex >= 0) {
|
|
11435
11503
|
this.resizeDrawing.points[this.resizePointIndex] = { ...point };
|
|
11504
|
+
this.onPointsMutated?.(this.resizeDrawing);
|
|
11436
11505
|
}
|
|
11437
11506
|
}
|
|
11438
11507
|
/**
|
|
@@ -13189,8 +13258,8 @@ var TradeButton = class {
|
|
|
13189
13258
|
this.el.style.opacity = "1";
|
|
13190
13259
|
Object.assign(this.el.style, {
|
|
13191
13260
|
background: theme.tooltip.background,
|
|
13192
|
-
color:
|
|
13193
|
-
border:
|
|
13261
|
+
color: theme.tradeLevels.buySide,
|
|
13262
|
+
border: `1px solid ${theme.tradeLevels.buySide}`
|
|
13194
13263
|
});
|
|
13195
13264
|
});
|
|
13196
13265
|
this.el.addEventListener("mouseleave", (e) => {
|
|
@@ -13253,11 +13322,12 @@ var TradeButton = class {
|
|
|
13253
13322
|
|
|
13254
13323
|
// src/ui/TradePopover.ts
|
|
13255
13324
|
var TradePopover = class {
|
|
13256
|
-
constructor(theme, lots, onDraftCreate, labels = DEFAULT_LABELS.trade, onVisibilityChange) {
|
|
13325
|
+
constructor(theme, lots, symbol, onDraftCreate, labels = DEFAULT_LABELS.trade, onVisibilityChange) {
|
|
13257
13326
|
this._price = 0;
|
|
13258
13327
|
this._pricePrecision = 2;
|
|
13259
13328
|
this._buyOrderType = "limit";
|
|
13260
13329
|
this._sellOrderType = "limit";
|
|
13330
|
+
this._compact = false;
|
|
13261
13331
|
this.onDocMouseDown = (e) => {
|
|
13262
13332
|
if (this.isVisible && !this.el.contains(e.target)) {
|
|
13263
13333
|
this.hide();
|
|
@@ -13265,6 +13335,7 @@ var TradePopover = class {
|
|
|
13265
13335
|
};
|
|
13266
13336
|
this.theme = theme;
|
|
13267
13337
|
this.lots = lots;
|
|
13338
|
+
this.symbol = symbol;
|
|
13268
13339
|
this.onDraftCreate = onDraftCreate;
|
|
13269
13340
|
this.labels = labels;
|
|
13270
13341
|
this.onVisibilityChange = onVisibilityChange;
|
|
@@ -13273,7 +13344,6 @@ var TradePopover = class {
|
|
|
13273
13344
|
position: "absolute",
|
|
13274
13345
|
display: "none",
|
|
13275
13346
|
flexDirection: "column",
|
|
13276
|
-
gap: "1px",
|
|
13277
13347
|
background: theme.tooltip.background,
|
|
13278
13348
|
border: `1px solid ${theme.axisBorder}`,
|
|
13279
13349
|
borderRadius: "5px",
|
|
@@ -13282,7 +13352,7 @@ var TradePopover = class {
|
|
|
13282
13352
|
boxShadow: "0 4px 16px rgba(0,0,0,0.25)",
|
|
13283
13353
|
fontFamily: "'Inter', system-ui, sans-serif",
|
|
13284
13354
|
fontSize: "12px",
|
|
13285
|
-
|
|
13355
|
+
width: "max-content",
|
|
13286
13356
|
pointerEvents: "auto"
|
|
13287
13357
|
});
|
|
13288
13358
|
document.addEventListener("mousedown", this.onDocMouseDown);
|
|
@@ -13296,10 +13366,8 @@ var TradePopover = class {
|
|
|
13296
13366
|
this._sellOrderType = sellOrderType;
|
|
13297
13367
|
this.rebuild();
|
|
13298
13368
|
this.el.style.display = "flex";
|
|
13299
|
-
const popW = 145;
|
|
13300
13369
|
this.el.style.right = `${buttonRight}px`;
|
|
13301
13370
|
this.el.style.top = `${buttonY - 16}px`;
|
|
13302
|
-
this.el.style.width = `${popW}px`;
|
|
13303
13371
|
if (!wasVisible) this.onVisibilityChange?.();
|
|
13304
13372
|
}
|
|
13305
13373
|
hide() {
|
|
@@ -13313,6 +13381,12 @@ var TradePopover = class {
|
|
|
13313
13381
|
setLots(lots) {
|
|
13314
13382
|
this.lots = lots;
|
|
13315
13383
|
}
|
|
13384
|
+
setSymbol(symbol) {
|
|
13385
|
+
this.symbol = symbol;
|
|
13386
|
+
}
|
|
13387
|
+
setCompact(compact) {
|
|
13388
|
+
this._compact = compact;
|
|
13389
|
+
}
|
|
13316
13390
|
setTheme(theme) {
|
|
13317
13391
|
this.theme = theme;
|
|
13318
13392
|
Object.assign(this.el.style, {
|
|
@@ -13326,31 +13400,46 @@ var TradePopover = class {
|
|
|
13326
13400
|
// ── Private ───────────────────────────────────────────────────────────────
|
|
13327
13401
|
rebuild() {
|
|
13328
13402
|
this.el.innerHTML = "";
|
|
13403
|
+
const divider = document.createElement("div");
|
|
13404
|
+
Object.assign(divider.style, {
|
|
13405
|
+
height: "1px",
|
|
13406
|
+
background: this.theme.axisBorder,
|
|
13407
|
+
margin: "0 8px"
|
|
13408
|
+
});
|
|
13329
13409
|
this.el.append(
|
|
13330
|
-
this.makeRow(this.
|
|
13331
|
-
|
|
13410
|
+
this.makeRow(this.theme.tradeLevels.sellSide, "sell", this._sellOrderType),
|
|
13411
|
+
divider,
|
|
13412
|
+
this.makeRow(this.theme.tradeLevels.buySide, "buy", this._buyOrderType)
|
|
13332
13413
|
);
|
|
13333
13414
|
}
|
|
13334
|
-
makeRow(
|
|
13415
|
+
makeRow(color, side, orderType) {
|
|
13335
13416
|
const btn = document.createElement("button");
|
|
13336
|
-
const priceStr = this._price.toFixed(this._pricePrecision);
|
|
13337
|
-
btn.textContent = `${label} ${orderType === "limit" ? this.labels.limit : this.labels.stop} ${priceStr}`;
|
|
13338
13417
|
Object.assign(btn.style, {
|
|
13339
|
-
display: "
|
|
13418
|
+
display: "flex",
|
|
13419
|
+
alignItems: "center",
|
|
13420
|
+
gap: "8px",
|
|
13340
13421
|
width: "100%",
|
|
13341
13422
|
background: "transparent",
|
|
13342
|
-
color,
|
|
13343
13423
|
border: "none",
|
|
13344
13424
|
borderRadius: "3px",
|
|
13345
|
-
padding: "
|
|
13346
|
-
textAlign: "left",
|
|
13425
|
+
padding: this._compact ? "5px 8px" : "8px 10px",
|
|
13347
13426
|
cursor: "pointer",
|
|
13348
|
-
fontSize: "12px",
|
|
13349
13427
|
fontFamily: "'Inter', system-ui, sans-serif",
|
|
13350
|
-
|
|
13428
|
+
fontSize: this._compact ? "11px" : "12px"
|
|
13351
13429
|
});
|
|
13430
|
+
btn.appendChild(this.makeChevron(color, side));
|
|
13431
|
+
const sideLabel = side === "sell" ? this.labels.sell : this.labels.buy;
|
|
13432
|
+
const orderTypeLabel = orderType === "limit" ? this.labels.limit : this.labels.stop;
|
|
13433
|
+
const priceStr = this._price.toFixed(this._pricePrecision);
|
|
13434
|
+
const colored = document.createElement("span");
|
|
13435
|
+
colored.textContent = `${sideLabel} ${orderTypeLabel}`;
|
|
13436
|
+
Object.assign(colored.style, { color, fontWeight: "600" });
|
|
13437
|
+
const plain = document.createElement("span");
|
|
13438
|
+
plain.textContent = ` ${this.lots} ${this.symbol} ${this.labels.at} ${priceStr}`;
|
|
13439
|
+
Object.assign(plain.style, { color: "#ffffff", fontWeight: "400" });
|
|
13440
|
+
btn.append(colored, plain);
|
|
13352
13441
|
btn.addEventListener("mouseenter", () => {
|
|
13353
|
-
btn.style.background =
|
|
13442
|
+
btn.style.background = "rgba(255,255,255,0.06)";
|
|
13354
13443
|
});
|
|
13355
13444
|
btn.addEventListener("mouseleave", () => {
|
|
13356
13445
|
btn.style.background = "transparent";
|
|
@@ -13362,6 +13451,23 @@ var TradePopover = class {
|
|
|
13362
13451
|
});
|
|
13363
13452
|
return btn;
|
|
13364
13453
|
}
|
|
13454
|
+
makeChevron(color, side) {
|
|
13455
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
13456
|
+
const svg = document.createElementNS(ns, "svg");
|
|
13457
|
+
svg.setAttribute("width", "10");
|
|
13458
|
+
svg.setAttribute("height", "8");
|
|
13459
|
+
svg.setAttribute("viewBox", "0 0 10 8");
|
|
13460
|
+
svg.style.flexShrink = "0";
|
|
13461
|
+
const path = document.createElementNS(ns, "path");
|
|
13462
|
+
path.setAttribute("d", side === "sell" ? "M1 1 L5 7 L9 1" : "M1 7 L5 1 L9 7");
|
|
13463
|
+
path.setAttribute("stroke", color);
|
|
13464
|
+
path.setAttribute("fill", "none");
|
|
13465
|
+
path.setAttribute("stroke-width", "1.5");
|
|
13466
|
+
path.setAttribute("stroke-linecap", "round");
|
|
13467
|
+
path.setAttribute("stroke-linejoin", "round");
|
|
13468
|
+
svg.appendChild(path);
|
|
13469
|
+
return svg;
|
|
13470
|
+
}
|
|
13365
13471
|
};
|
|
13366
13472
|
|
|
13367
13473
|
// src/ui/DraftQtyFlyout.ts
|
|
@@ -17572,6 +17678,13 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17572
17678
|
/** Pending resize — committed only after mouse moves past DRAW_GESTURE_THRESHOLD. */
|
|
17573
17679
|
this._pendingDrawResize = null;
|
|
17574
17680
|
// px
|
|
17681
|
+
/**
|
|
17682
|
+
* Pending trade-level drag — committed only after mouse moves past TRADE_DRAG_PIXEL_THRESHOLD.
|
|
17683
|
+
* A mouseup before the threshold means it was a click: route to _openTradeLevelEditForm instead
|
|
17684
|
+
* of finishing a zero-delta drag (which would otherwise stage a spurious pending change).
|
|
17685
|
+
*/
|
|
17686
|
+
this._pendingTradeDrag = null;
|
|
17687
|
+
// px
|
|
17575
17688
|
// Pane resize
|
|
17576
17689
|
this.paneHeightOverrides = /* @__PURE__ */ new Map();
|
|
17577
17690
|
this.separatorDrag = null;
|
|
@@ -17587,6 +17700,21 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17587
17700
|
// lerp target; null = snap on next render
|
|
17588
17701
|
this._lockedAutoFitBase = null;
|
|
17589
17702
|
// non-null = user has manually adjusted Y-axis
|
|
17703
|
+
/**
|
|
17704
|
+
* When setState() restores a persisted viewport but data isn't loaded yet,
|
|
17705
|
+
* we stash the desired time window here and apply it from loadData() once
|
|
17706
|
+
* bars are available. Cleared after one application.
|
|
17707
|
+
*/
|
|
17708
|
+
this._pendingViewportRestore = null;
|
|
17709
|
+
/**
|
|
17710
|
+
* Debounces viewport/priceScale stateChange emissions during continuous
|
|
17711
|
+
* gestures (pan, wheel-zoom, price-scale drag). Pan/zoom don't emit
|
|
17712
|
+
* stateChange directly because doing so per-frame would be wasteful; this
|
|
17713
|
+
* timer coalesces bursts so the persisted viewport catches up a few ms
|
|
17714
|
+
* after the user stops interacting. Paired with the consumer's own
|
|
17715
|
+
* debounce (e.g. CRM's 800ms save) for minimal traffic.
|
|
17716
|
+
*/
|
|
17717
|
+
this._viewportPersistTimer = null;
|
|
17590
17718
|
this.axisDrag = null;
|
|
17591
17719
|
this.subPaneScaleFactors = /* @__PURE__ */ new Map();
|
|
17592
17720
|
this._mobileCrosshairActive = false;
|
|
@@ -17747,14 +17875,14 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17747
17875
|
const dx = x - this._pendingDrawDrag.startX;
|
|
17748
17876
|
const dy = y - this._pendingDrawDrag.startY;
|
|
17749
17877
|
if (Math.sqrt(dx * dx + dy * dy) > _ChartEngine.DRAW_GESTURE_THRESHOLD) {
|
|
17750
|
-
this.drawingManager.startDrag(
|
|
17751
|
-
|
|
17752
|
-
price
|
|
17753
|
-
|
|
17878
|
+
this.drawingManager.startDrag(
|
|
17879
|
+
this._pendingDrawDrag.drawing,
|
|
17880
|
+
this.makeDrawingPoint(rawBarIndex, price)
|
|
17881
|
+
);
|
|
17754
17882
|
this._pendingDrawDrag = null;
|
|
17755
17883
|
}
|
|
17756
17884
|
}
|
|
17757
|
-
this.drawingManager.onMouseMove(
|
|
17885
|
+
this.drawingManager.onMouseMove(this.makeDrawingPoint(rawBarIndex, price));
|
|
17758
17886
|
if (this.axisDrag) {
|
|
17759
17887
|
if (this.axisDrag.axis === "price") {
|
|
17760
17888
|
const delta = y - this.axisDrag.startPos;
|
|
@@ -17796,6 +17924,18 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17796
17924
|
this.scheduleRender();
|
|
17797
17925
|
return;
|
|
17798
17926
|
}
|
|
17927
|
+
if (this._pendingTradeDrag) {
|
|
17928
|
+
const dx = x - this._pendingTradeDrag.startX;
|
|
17929
|
+
const dy = y - this._pendingTradeDrag.startY;
|
|
17930
|
+
if (Math.sqrt(dx * dx + dy * dy) > _ChartEngine.TRADE_DRAG_PIXEL_THRESHOLD) {
|
|
17931
|
+
const { dragHit, level, hitY } = this._pendingTradeDrag;
|
|
17932
|
+
this._pendingTradeDrag = null;
|
|
17933
|
+
this._startTradeDragFromHit(dragHit, level, hitY);
|
|
17934
|
+
this.drawCanvas.style.cursor = "ns-resize";
|
|
17935
|
+
} else {
|
|
17936
|
+
return;
|
|
17937
|
+
}
|
|
17938
|
+
}
|
|
17799
17939
|
if (this.tradeDragHandler?.active) {
|
|
17800
17940
|
const moveResult = this.tradeDragHandler.onMouseMove(
|
|
17801
17941
|
y,
|
|
@@ -17836,6 +17976,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17836
17976
|
const dragHover = hitTestTradeLevelDrag(x, y, this.tradeLevelDragAreas);
|
|
17837
17977
|
if (dragHover) {
|
|
17838
17978
|
this.drawCanvas.style.cursor = "ns-resize";
|
|
17979
|
+
if (dragHover.label !== this.hoveredTradeLabel) {
|
|
17980
|
+
this.hoveredTradeLabel = dragHover.label;
|
|
17981
|
+
this.renderTradeLayer();
|
|
17982
|
+
}
|
|
17839
17983
|
this.scheduleRender();
|
|
17840
17984
|
return;
|
|
17841
17985
|
}
|
|
@@ -17905,6 +18049,16 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17905
18049
|
this.scheduleRender();
|
|
17906
18050
|
};
|
|
17907
18051
|
this.onMouseUp = () => {
|
|
18052
|
+
if (this._pendingTradeDrag) {
|
|
18053
|
+
const { level } = this._pendingTradeDrag;
|
|
18054
|
+
this._pendingTradeDrag = null;
|
|
18055
|
+
if (this._editOpenLabel === level.label && !this.pendingLevelDrags.has(level.label)) {
|
|
18056
|
+
this._cancelEditAndDeselect(level.label);
|
|
18057
|
+
return;
|
|
18058
|
+
}
|
|
18059
|
+
this._openTradeLevelEditForm(level);
|
|
18060
|
+
return;
|
|
18061
|
+
}
|
|
17908
18062
|
if (this.tradeDragHandler?.active) {
|
|
17909
18063
|
const result = this.tradeDragHandler.endDrag();
|
|
17910
18064
|
if (result) {
|
|
@@ -17921,9 +18075,11 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17921
18075
|
return;
|
|
17922
18076
|
}
|
|
17923
18077
|
if (this.axisDrag) {
|
|
18078
|
+
const wasPriceAxis = this.axisDrag.axis === "price" || this.axisDrag.axis === "time";
|
|
17924
18079
|
this.axisDrag = null;
|
|
17925
18080
|
this.drawCanvas.style.cursor = "default";
|
|
17926
18081
|
this.scheduleRender();
|
|
18082
|
+
if (wasPriceAxis) this.scheduleViewportPersist();
|
|
17927
18083
|
}
|
|
17928
18084
|
if (this.separatorDrag) {
|
|
17929
18085
|
this.separatorDrag = null;
|
|
@@ -18085,22 +18241,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18085
18241
|
if (editHit) {
|
|
18086
18242
|
e.preventDefault();
|
|
18087
18243
|
const level = this.levels.find((l) => l.label === editHit.label);
|
|
18088
|
-
if (level)
|
|
18089
|
-
this.selectedTradeLabel = level.label;
|
|
18090
|
-
this.renderTradeLayer();
|
|
18091
|
-
const { slPrice: _slE, tpPrice: _tpE } = this._resolveBracketPrices(level);
|
|
18092
|
-
this._editOpenLabel = level.label;
|
|
18093
|
-
this.emitter.emit("tradeLevelEditOpen", {
|
|
18094
|
-
label: level.label,
|
|
18095
|
-
type: level.type,
|
|
18096
|
-
data: level.data,
|
|
18097
|
-
price: level.price,
|
|
18098
|
-
side: level.side,
|
|
18099
|
-
stopLossPrice: _slE,
|
|
18100
|
-
takeProfitPrice: _tpE,
|
|
18101
|
-
isFullscreen: this.isFullscreen
|
|
18102
|
-
});
|
|
18103
|
-
}
|
|
18244
|
+
if (level) this._openTradeLevelEditForm(level);
|
|
18104
18245
|
return;
|
|
18105
18246
|
}
|
|
18106
18247
|
}
|
|
@@ -18115,23 +18256,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18115
18256
|
if (this.hideLevelConfirmCancel && !isMobileDraggable) {
|
|
18116
18257
|
const hasPending = this.pendingLevelDrags.has(level.label);
|
|
18117
18258
|
if (this.selectedTradeLabel === level.label && !hasPending) {
|
|
18118
|
-
this.
|
|
18119
|
-
|
|
18120
|
-
this.selectedTradeLabel = level.label;
|
|
18259
|
+
this._cancelEditAndDeselect(level.label);
|
|
18260
|
+
return;
|
|
18121
18261
|
}
|
|
18122
|
-
this.
|
|
18123
|
-
const { slPrice: _sl1, tpPrice: _tp1 } = this._resolveBracketPrices(level);
|
|
18124
|
-
this._editOpenLabel = level.label;
|
|
18125
|
-
this.emitter.emit("tradeLevelEditOpen", {
|
|
18126
|
-
label: level.label,
|
|
18127
|
-
type: level.type,
|
|
18128
|
-
data: level.data,
|
|
18129
|
-
price: level.price,
|
|
18130
|
-
side: level.side,
|
|
18131
|
-
stopLossPrice: _sl1,
|
|
18132
|
-
takeProfitPrice: _tp1,
|
|
18133
|
-
isFullscreen: this.isFullscreen
|
|
18134
|
-
});
|
|
18262
|
+
this._openTradeLevelEditForm(level);
|
|
18135
18263
|
return;
|
|
18136
18264
|
}
|
|
18137
18265
|
this._startTradeDragFromHit(dragHit, level, y);
|
|
@@ -18155,23 +18283,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18155
18283
|
if (level) {
|
|
18156
18284
|
const hasPending = this.pendingLevelDrags.has(hoverHit.label);
|
|
18157
18285
|
if (this.selectedTradeLabel === hoverHit.label && !hasPending) {
|
|
18158
|
-
this.
|
|
18159
|
-
|
|
18160
|
-
this.selectedTradeLabel = hoverHit.label;
|
|
18286
|
+
this._cancelEditAndDeselect(hoverHit.label);
|
|
18287
|
+
return;
|
|
18161
18288
|
}
|
|
18162
|
-
|
|
18163
|
-
this._editOpenLabel = level.label;
|
|
18164
|
-
this.renderTradeLayer();
|
|
18165
|
-
this.emitter.emit("tradeLevelEditOpen", {
|
|
18166
|
-
label: level.label,
|
|
18167
|
-
type: level.type,
|
|
18168
|
-
data: level.data,
|
|
18169
|
-
price: level.price,
|
|
18170
|
-
side: level.side,
|
|
18171
|
-
stopLossPrice: _slH,
|
|
18172
|
-
takeProfitPrice: _tpH,
|
|
18173
|
-
isFullscreen: this.isFullscreen
|
|
18174
|
-
});
|
|
18289
|
+
this._openTradeLevelEditForm(level);
|
|
18175
18290
|
return;
|
|
18176
18291
|
}
|
|
18177
18292
|
}
|
|
@@ -18370,8 +18485,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18370
18485
|
return;
|
|
18371
18486
|
}
|
|
18372
18487
|
if (this.axisDrag) {
|
|
18488
|
+
const wasPriceAxis = this.axisDrag.axis === "price" || this.axisDrag.axis === "time";
|
|
18373
18489
|
this.axisDrag = null;
|
|
18374
18490
|
this.scheduleRender();
|
|
18491
|
+
if (wasPriceAxis) this.scheduleViewportPersist();
|
|
18375
18492
|
return;
|
|
18376
18493
|
}
|
|
18377
18494
|
const wasAnchorTouch = this._drawTouchAnchorActive;
|
|
@@ -18456,22 +18573,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18456
18573
|
const editHit = hitTestTradeLevelEdit(x, y, this.tradeLevelEditAreas);
|
|
18457
18574
|
if (editHit) {
|
|
18458
18575
|
const level = this.levels.find((l) => l.label === editHit.label);
|
|
18459
|
-
if (level)
|
|
18460
|
-
this.selectedTradeLabel = level.label;
|
|
18461
|
-
this.renderTradeLayer();
|
|
18462
|
-
const { slPrice: _sl2, tpPrice: _tp2 } = this._resolveBracketPrices(level);
|
|
18463
|
-
this._editOpenLabel = level.label;
|
|
18464
|
-
this.emitter.emit("tradeLevelEditOpen", {
|
|
18465
|
-
label: level.label,
|
|
18466
|
-
type: level.type,
|
|
18467
|
-
data: level.data,
|
|
18468
|
-
price: level.price,
|
|
18469
|
-
side: level.side,
|
|
18470
|
-
stopLossPrice: _sl2,
|
|
18471
|
-
takeProfitPrice: _tp2,
|
|
18472
|
-
isFullscreen: this.isFullscreen
|
|
18473
|
-
});
|
|
18474
|
-
}
|
|
18576
|
+
if (level) this._openTradeLevelEditForm(level);
|
|
18475
18577
|
return;
|
|
18476
18578
|
}
|
|
18477
18579
|
}
|
|
@@ -18489,8 +18591,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18489
18591
|
const level = this.levels.find((l) => l.label === dragHit.label);
|
|
18490
18592
|
if (level) {
|
|
18491
18593
|
if (!(this.hideLevelConfirmCancel && !dragHit.isBracket)) {
|
|
18492
|
-
this.
|
|
18493
|
-
this.drawCanvas.style.cursor = "ns-resize";
|
|
18594
|
+
this._pendingTradeDrag = { dragHit, level, hitY: y, startX: x, startY: y };
|
|
18494
18595
|
return;
|
|
18495
18596
|
}
|
|
18496
18597
|
}
|
|
@@ -18500,49 +18601,24 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18500
18601
|
if (hoverHit) {
|
|
18501
18602
|
const hasPending = this.pendingLevelDrags.has(hoverHit.label);
|
|
18502
18603
|
if (this.selectedTradeLabel === hoverHit.label && !hasPending) {
|
|
18503
|
-
this.
|
|
18604
|
+
this._cancelEditAndDeselect(hoverHit.label);
|
|
18605
|
+
return;
|
|
18606
|
+
}
|
|
18607
|
+
const level = this.levels.find((l) => l.label === hoverHit.label);
|
|
18608
|
+
if (level) {
|
|
18609
|
+
this._openTradeLevelEditForm(level);
|
|
18504
18610
|
} else {
|
|
18505
18611
|
this.selectedTradeLabel = hoverHit.label;
|
|
18506
|
-
|
|
18507
|
-
this.renderTradeLayer();
|
|
18508
|
-
if (this.hideLevelConfirmCancel) {
|
|
18509
|
-
const level = this.levels.find((l) => l.label === hoverHit.label);
|
|
18510
|
-
if (level) {
|
|
18511
|
-
const { slPrice: _sl3, tpPrice: _tp3 } = this._resolveBracketPrices(level);
|
|
18512
|
-
this._editOpenLabel = level.label;
|
|
18513
|
-
this.emitter.emit("tradeLevelEditOpen", {
|
|
18514
|
-
label: level.label,
|
|
18515
|
-
type: level.type,
|
|
18516
|
-
data: level.data,
|
|
18517
|
-
price: level.price,
|
|
18518
|
-
side: level.side,
|
|
18519
|
-
stopLossPrice: _sl3,
|
|
18520
|
-
takeProfitPrice: _tp3,
|
|
18521
|
-
isFullscreen: this.isFullscreen
|
|
18522
|
-
});
|
|
18523
|
-
}
|
|
18612
|
+
this.renderTradeLayer();
|
|
18524
18613
|
}
|
|
18525
18614
|
return;
|
|
18526
18615
|
} else if (this.selectedTradeLabel !== null) {
|
|
18527
18616
|
if (!this.hideLevelConfirmCancel) {
|
|
18528
18617
|
const lbl = this.selectedTradeLabel;
|
|
18529
18618
|
const pending = this.pendingLevelDrags.get(lbl);
|
|
18530
|
-
const isEditFormOpen = this._editOpenLabel === lbl;
|
|
18531
18619
|
if (!pending || pending.length === 0) {
|
|
18532
|
-
|
|
18533
|
-
|
|
18534
|
-
this._editOpenLabel = null;
|
|
18535
|
-
if (lvl) {
|
|
18536
|
-
this.emitter.emit("tradeLevelEditCancelled", {
|
|
18537
|
-
label: lbl,
|
|
18538
|
-
type: lvl.type,
|
|
18539
|
-
isFullscreen: this.isFullscreen
|
|
18540
|
-
});
|
|
18541
|
-
}
|
|
18542
|
-
}
|
|
18543
|
-
this.selectedTradeLabel = null;
|
|
18544
|
-
this.renderTradeLayer();
|
|
18545
|
-
} else if (pending.every((c) => c.isNew)) {
|
|
18620
|
+
this._cancelEditAndDeselect(lbl);
|
|
18621
|
+
} else {
|
|
18546
18622
|
this.revertPendingChanges(lbl);
|
|
18547
18623
|
}
|
|
18548
18624
|
}
|
|
@@ -18597,10 +18673,9 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18597
18673
|
this.scheduleRender();
|
|
18598
18674
|
return;
|
|
18599
18675
|
}
|
|
18600
|
-
const consumed = this.drawingManager.onMouseDown(
|
|
18601
|
-
|
|
18602
|
-
|
|
18603
|
-
});
|
|
18676
|
+
const consumed = this.drawingManager.onMouseDown(
|
|
18677
|
+
this.makeDrawingPoint(rawBarIndex, price)
|
|
18678
|
+
);
|
|
18604
18679
|
if (!consumed) {
|
|
18605
18680
|
const handleHit = this.drawingManager.getHandleAtPoint(
|
|
18606
18681
|
x,
|
|
@@ -19195,6 +19270,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19195
19270
|
this.tradePopover = new TradePopover(
|
|
19196
19271
|
this.theme,
|
|
19197
19272
|
0.01,
|
|
19273
|
+
this.symbol,
|
|
19198
19274
|
(side, price, orderType) => {
|
|
19199
19275
|
this.createDraftOrder(side, price, orderType);
|
|
19200
19276
|
},
|
|
@@ -19298,6 +19374,12 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19298
19374
|
this.drawCanvas.style.cursor = "default";
|
|
19299
19375
|
});
|
|
19300
19376
|
this.drawingManager.onDrawingsChange = () => this.emitStateChange();
|
|
19377
|
+
this.drawingManager.onPointsMutated = (drawing) => {
|
|
19378
|
+
for (const pt of drawing.points) {
|
|
19379
|
+
const t = this.barIndexToTimestamp(pt.barIndex);
|
|
19380
|
+
if (t != null) pt.timestamp = t;
|
|
19381
|
+
}
|
|
19382
|
+
};
|
|
19301
19383
|
this.drawingManager.onStartTextEdit = (drawing) => this.showTextInput(drawing);
|
|
19302
19384
|
this.panHandler = new PanHandler(
|
|
19303
19385
|
this.drawCanvas,
|
|
@@ -19307,16 +19389,19 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19307
19389
|
this.clearDuration();
|
|
19308
19390
|
this.checkNeedMoreData();
|
|
19309
19391
|
this.scheduleRender();
|
|
19392
|
+
this.scheduleViewportPersist();
|
|
19310
19393
|
},
|
|
19311
19394
|
() => this.dataStore.length,
|
|
19312
19395
|
() => this.scheduleRender(),
|
|
19313
19396
|
() => this.drawingManager.getActiveTool() !== null || this.drawingManager.isDraggingDrawing() || this.drawingManager.isResizingDrawing() || this.separatorDrag !== null || this.axisDrag !== null || (this.tradeDragHandler?.active ?? false) || this._mobileCrosshairActive,
|
|
19314
19397
|
(deltaY) => {
|
|
19398
|
+
if (this.priceScaleFactor === 1) return;
|
|
19315
19399
|
const { priceH } = this.getChartLayout();
|
|
19316
19400
|
const currentRange = this._displayedPriceRange ?? this.getScaledPriceRange(this._targetViewport);
|
|
19317
19401
|
const pricePerPixel = (currentRange.max - currentRange.min) / priceH;
|
|
19318
19402
|
this.pricePanOffset += deltaY * pricePerPixel;
|
|
19319
19403
|
this.scheduleRender();
|
|
19404
|
+
this.scheduleViewportPersist();
|
|
19320
19405
|
},
|
|
19321
19406
|
() => {
|
|
19322
19407
|
this._isPanning = true;
|
|
@@ -19339,6 +19424,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19339
19424
|
this.clearDuration();
|
|
19340
19425
|
this.checkNeedMoreData();
|
|
19341
19426
|
this.scheduleRender();
|
|
19427
|
+
this.scheduleViewportPersist();
|
|
19342
19428
|
},
|
|
19343
19429
|
() => this.dataStore.length,
|
|
19344
19430
|
() => this.scheduleRender(),
|
|
@@ -19359,6 +19445,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19359
19445
|
this.clearDuration();
|
|
19360
19446
|
this.checkNeedMoreData();
|
|
19361
19447
|
this.scheduleRender();
|
|
19448
|
+
this.scheduleViewportPersist();
|
|
19362
19449
|
}
|
|
19363
19450
|
);
|
|
19364
19451
|
this.drawCanvas.addEventListener("mousemove", this.onMouseMove);
|
|
@@ -19396,6 +19483,15 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19396
19483
|
return true;
|
|
19397
19484
|
}).map((s) => s.indicator);
|
|
19398
19485
|
}
|
|
19486
|
+
/** Schedules a stateChange emission so viewport/priceScale changes made by
|
|
19487
|
+
* pan/zoom/price-scale drag reach persistence. Coalesces rapid calls. */
|
|
19488
|
+
scheduleViewportPersist() {
|
|
19489
|
+
if (this._viewportPersistTimer) clearTimeout(this._viewportPersistTimer);
|
|
19490
|
+
this._viewportPersistTimer = setTimeout(() => {
|
|
19491
|
+
this._viewportPersistTimer = null;
|
|
19492
|
+
this.emitStateChange();
|
|
19493
|
+
}, 500);
|
|
19494
|
+
}
|
|
19399
19495
|
get isFullscreen() {
|
|
19400
19496
|
return this._isCssFullscreen || !!document.fullscreenElement;
|
|
19401
19497
|
}
|
|
@@ -19487,6 +19583,22 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19487
19583
|
}
|
|
19488
19584
|
this.recomputeIndicators();
|
|
19489
19585
|
this.reanchorDrawingsByTimestamp();
|
|
19586
|
+
if (this._pendingViewportRestore) {
|
|
19587
|
+
const pending = this._pendingViewportRestore;
|
|
19588
|
+
this._pendingViewportRestore = null;
|
|
19589
|
+
if (pending.priceScale) {
|
|
19590
|
+
this.priceScaleFactor = pending.priceScale.factor;
|
|
19591
|
+
this.pricePanOffset = pending.priceScale.panOffset;
|
|
19592
|
+
this._displayedPriceRange = null;
|
|
19593
|
+
this._lockedAutoFitBase = null;
|
|
19594
|
+
}
|
|
19595
|
+
if (pending.viewport) {
|
|
19596
|
+
this.applyViewportFromTimestamps(
|
|
19597
|
+
pending.viewport.startTime,
|
|
19598
|
+
pending.viewport.endTime
|
|
19599
|
+
);
|
|
19600
|
+
}
|
|
19601
|
+
}
|
|
19490
19602
|
this.updateCountdownState();
|
|
19491
19603
|
this.scheduleRender();
|
|
19492
19604
|
return this;
|
|
@@ -19507,6 +19619,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19507
19619
|
};
|
|
19508
19620
|
this._targetViewport = { ...this.viewport };
|
|
19509
19621
|
this.drawingManager.shiftBarIndices(n);
|
|
19622
|
+
this.reanchorDrawingsByTimestamp();
|
|
19510
19623
|
this.recomputeIndicators();
|
|
19511
19624
|
this.scheduleRender();
|
|
19512
19625
|
}
|
|
@@ -20030,15 +20143,44 @@ var _ChartEngine = class _ChartEngine {
|
|
|
20030
20143
|
...d,
|
|
20031
20144
|
points: d.points.map((p) => ({
|
|
20032
20145
|
...p,
|
|
20033
|
-
timestamp
|
|
20146
|
+
// Prefer the point's own timestamp (captured at placement / drag /
|
|
20147
|
+
// resize); only derive from current bars as a fallback for legacy
|
|
20148
|
+
// points. Re-deriving on every export would otherwise corrupt the
|
|
20149
|
+
// anchor after a timeframe change has staled the cached barIndex.
|
|
20150
|
+
timestamp: p.timestamp ?? this.barIndexToTimestamp(p.barIndex)
|
|
20034
20151
|
}))
|
|
20035
20152
|
})),
|
|
20036
20153
|
positionRenderStyle: this.positionRenderStyle,
|
|
20037
20154
|
tradeDisplayFilter: this.tradeDisplayFilter,
|
|
20038
20155
|
canvasColors: Object.keys(this.settingsCanvasColors).length > 0 ? { ...this.settingsCanvasColors } : void 0,
|
|
20039
|
-
tfcEnabled: this.tfcActive
|
|
20156
|
+
tfcEnabled: this.tfcActive,
|
|
20157
|
+
viewport: this.getViewportTimestamps(),
|
|
20158
|
+
priceScale: this.priceScaleFactor !== 1 || this.pricePanOffset !== 0 ? { factor: this.priceScaleFactor, panOffset: this.pricePanOffset } : void 0
|
|
20040
20159
|
};
|
|
20041
20160
|
}
|
|
20161
|
+
/**
|
|
20162
|
+
* Captures the current viewport as a {startTime, endTime} timestamp pair,
|
|
20163
|
+
* preserving sub-bar fractional position. Returns undefined only when no
|
|
20164
|
+
* bars are loaded (viewport has no meaningful time anchor).
|
|
20165
|
+
*/
|
|
20166
|
+
getViewportTimestamps() {
|
|
20167
|
+
if (this.dataStore.all.length === 0) return void 0;
|
|
20168
|
+
const startTime = this.barIndexToTimestamp(this.viewport.startIndex);
|
|
20169
|
+
const endTime = this.barIndexToTimestamp(this.viewport.endIndex);
|
|
20170
|
+
if (startTime == null || endTime == null) return void 0;
|
|
20171
|
+
return { startTime, endTime };
|
|
20172
|
+
}
|
|
20173
|
+
/**
|
|
20174
|
+
* Restores the viewport from a timestamp pair, converting each edge back
|
|
20175
|
+
* into a fractional bar index relative to the current dataStore. Callers
|
|
20176
|
+
* must ensure bars are loaded before invoking.
|
|
20177
|
+
*/
|
|
20178
|
+
applyViewportFromTimestamps(startTime, endTime) {
|
|
20179
|
+
const startIndex = this.timestampToBarIndex(startTime);
|
|
20180
|
+
const endIndex = this.timestampToBarIndex(endTime);
|
|
20181
|
+
this.viewport = { ...this.viewport, startIndex, endIndex };
|
|
20182
|
+
this._targetViewport = { ...this.viewport };
|
|
20183
|
+
}
|
|
20042
20184
|
/**
|
|
20043
20185
|
* Restores chart configuration from a previously captured `ChartState`.
|
|
20044
20186
|
* Call this after construction to hydrate a chart from persisted state.
|
|
@@ -20085,18 +20227,11 @@ var _ChartEngine = class _ChartEngine {
|
|
|
20085
20227
|
}
|
|
20086
20228
|
}
|
|
20087
20229
|
}
|
|
20088
|
-
const bars = this.dataStore.all;
|
|
20089
20230
|
const resolvedDrawings = state.drawings.map((d) => ({
|
|
20090
20231
|
...d,
|
|
20091
20232
|
points: d.points.map((p) => {
|
|
20092
|
-
if (
|
|
20093
|
-
|
|
20094
|
-
while (lo < hi) {
|
|
20095
|
-
const mid = lo + hi >> 1;
|
|
20096
|
-
if ((bars[mid]?.time ?? 0) < p.timestamp) lo = mid + 1;
|
|
20097
|
-
else hi = mid;
|
|
20098
|
-
}
|
|
20099
|
-
return { ...p, barIndex: lo + (p.barIndex - Math.floor(p.barIndex)) };
|
|
20233
|
+
if (p.timestamp == null || this.dataStore.all.length === 0) return p;
|
|
20234
|
+
return { ...p, barIndex: this.timestampToBarIndex(p.timestamp) };
|
|
20100
20235
|
})
|
|
20101
20236
|
}));
|
|
20102
20237
|
this.drawingManager.loadDrawings(resolvedDrawings);
|
|
@@ -20114,6 +20249,25 @@ var _ChartEngine = class _ChartEngine {
|
|
|
20114
20249
|
this.tfcActive = state.tfcEnabled;
|
|
20115
20250
|
this.topBar?.setTfcActive(state.tfcEnabled);
|
|
20116
20251
|
}
|
|
20252
|
+
if (state.priceScale) {
|
|
20253
|
+
this.priceScaleFactor = state.priceScale.factor;
|
|
20254
|
+
this.pricePanOffset = state.priceScale.panOffset;
|
|
20255
|
+
this._displayedPriceRange = null;
|
|
20256
|
+
this._lockedAutoFitBase = null;
|
|
20257
|
+
}
|
|
20258
|
+
if (state.viewport) {
|
|
20259
|
+
if (this.dataStore.all.length > 0) {
|
|
20260
|
+
this.applyViewportFromTimestamps(
|
|
20261
|
+
state.viewport.startTime,
|
|
20262
|
+
state.viewport.endTime
|
|
20263
|
+
);
|
|
20264
|
+
} else {
|
|
20265
|
+
this._pendingViewportRestore = {
|
|
20266
|
+
viewport: state.viewport,
|
|
20267
|
+
priceScale: state.priceScale
|
|
20268
|
+
};
|
|
20269
|
+
}
|
|
20270
|
+
}
|
|
20117
20271
|
} finally {
|
|
20118
20272
|
this.isRestoringState = false;
|
|
20119
20273
|
}
|
|
@@ -20122,23 +20276,120 @@ var _ChartEngine = class _ChartEngine {
|
|
|
20122
20276
|
return this;
|
|
20123
20277
|
}
|
|
20124
20278
|
/**
|
|
20125
|
-
*
|
|
20126
|
-
*
|
|
20127
|
-
*
|
|
20279
|
+
* Converts a fractional barIndex into an absolute unix-ms timestamp by
|
|
20280
|
+
* interpolating across the containing bar. Extrapolates linearly for indexes
|
|
20281
|
+
* outside [0, bars.length-1] using the adjacent bar's width.
|
|
20282
|
+
*
|
|
20283
|
+
* This is the inverse of `timestampToBarIndex` and the authoritative way to
|
|
20284
|
+
* capture "where in time" a drawing point sits at placement, drag, or resize.
|
|
20285
|
+
* Returns undefined only when no bars are loaded.
|
|
20286
|
+
*/
|
|
20287
|
+
barIndexToTimestamp(idx) {
|
|
20288
|
+
const bars = this.dataStore.all;
|
|
20289
|
+
const n = bars.length;
|
|
20290
|
+
if (n === 0) return void 0;
|
|
20291
|
+
const first = bars[0];
|
|
20292
|
+
if (!first) return void 0;
|
|
20293
|
+
if (n === 1) return first.time;
|
|
20294
|
+
const floorIdx = Math.floor(idx);
|
|
20295
|
+
const frac = idx - floorIdx;
|
|
20296
|
+
if (floorIdx < 0) {
|
|
20297
|
+
const second = bars[1];
|
|
20298
|
+
if (!second) return first.time;
|
|
20299
|
+
return first.time + idx * (second.time - first.time);
|
|
20300
|
+
}
|
|
20301
|
+
if (floorIdx >= n - 1) {
|
|
20302
|
+
const last = bars[n - 1];
|
|
20303
|
+
const prev = bars[n - 2];
|
|
20304
|
+
if (!last || !prev) return first.time;
|
|
20305
|
+
return last.time + (idx - (n - 1)) * (last.time - prev.time);
|
|
20306
|
+
}
|
|
20307
|
+
const lo = bars[floorIdx];
|
|
20308
|
+
const hi = bars[floorIdx + 1];
|
|
20309
|
+
if (!lo || !hi) return first.time;
|
|
20310
|
+
return lo.time + frac * (hi.time - lo.time);
|
|
20311
|
+
}
|
|
20312
|
+
/**
|
|
20313
|
+
* Converts a unix-ms timestamp into a fractional barIndex using floor-bar
|
|
20314
|
+
* containment: the result is `floorBar + (ts - floorBar.time) / (nextBar.time - floorBar.time)`.
|
|
20315
|
+
* Extrapolates linearly for timestamps outside the loaded range using the edge
|
|
20316
|
+
* bar width, so drawings outside the visible data still render at the correct
|
|
20317
|
+
* relative offset rather than snapping to an edge.
|
|
20318
|
+
*
|
|
20319
|
+
* Matches TradingView's floor-bar semantics and preserves sub-bar time
|
|
20320
|
+
* precision across timeframe changes.
|
|
20321
|
+
*/
|
|
20322
|
+
timestampToBarIndex(ts) {
|
|
20323
|
+
const bars = this.dataStore.all;
|
|
20324
|
+
const n = bars.length;
|
|
20325
|
+
if (n === 0) return 0;
|
|
20326
|
+
const first = bars[0];
|
|
20327
|
+
if (!first) return 0;
|
|
20328
|
+
if (n === 1) return 0;
|
|
20329
|
+
if (ts <= first.time) {
|
|
20330
|
+
const second = bars[1];
|
|
20331
|
+
if (!second) return 0;
|
|
20332
|
+
const barMs2 = second.time - first.time;
|
|
20333
|
+
return barMs2 > 0 ? (ts - first.time) / barMs2 : 0;
|
|
20334
|
+
}
|
|
20335
|
+
const lastIdx = n - 1;
|
|
20336
|
+
const lastBar = bars[lastIdx];
|
|
20337
|
+
const prevBar = bars[lastIdx - 1];
|
|
20338
|
+
if (!lastBar || !prevBar) return 0;
|
|
20339
|
+
if (ts >= lastBar.time) {
|
|
20340
|
+
const barMs2 = lastBar.time - prevBar.time;
|
|
20341
|
+
return lastIdx + (barMs2 > 0 ? (ts - lastBar.time) / barMs2 : 0);
|
|
20342
|
+
}
|
|
20343
|
+
let lo = 0, hi = lastIdx;
|
|
20344
|
+
while (lo < hi) {
|
|
20345
|
+
const mid = lo + hi + 1 >> 1;
|
|
20346
|
+
if ((bars[mid]?.time ?? 0) <= ts) lo = mid;
|
|
20347
|
+
else hi = mid - 1;
|
|
20348
|
+
}
|
|
20349
|
+
const loBar = bars[lo];
|
|
20350
|
+
const hiBar = bars[lo + 1];
|
|
20351
|
+
if (!loBar || !hiBar) return lo;
|
|
20352
|
+
const barMs = hiBar.time - loBar.time;
|
|
20353
|
+
return lo + (barMs > 0 ? (ts - loBar.time) / barMs : 0);
|
|
20354
|
+
}
|
|
20355
|
+
/**
|
|
20356
|
+
* Re-anchors all loaded drawings to the current dataset. For points with a
|
|
20357
|
+
* stored timestamp, barIndex is recomputed via `timestampToBarIndex`; for
|
|
20358
|
+
* points missing a timestamp (legacy saves or freshly placed points whose
|
|
20359
|
+
* timestamp capture was skipped), the timestamp is backfilled from the
|
|
20360
|
+
* current barIndex so subsequent data reloads remain accurate.
|
|
20361
|
+
*
|
|
20362
|
+
* Called after loadData(), prependData(), and whenever the underlying bars
|
|
20363
|
+
* change such that cached barIndex values would otherwise drift.
|
|
20128
20364
|
*/
|
|
20129
20365
|
reanchorDrawingsByTimestamp() {
|
|
20130
20366
|
const bars = this.dataStore.all;
|
|
20131
20367
|
if (bars.length === 0) return;
|
|
20132
20368
|
for (const drawing of this.drawingManager.getDrawings()) {
|
|
20133
20369
|
for (const pt of drawing.points) {
|
|
20134
|
-
if (
|
|
20135
|
-
|
|
20136
|
-
|
|
20137
|
-
|
|
20138
|
-
if ((bars[mid]?.time ?? 0) < pt.timestamp) lo = mid + 1;
|
|
20139
|
-
else hi = mid;
|
|
20370
|
+
if (pt.timestamp == null) {
|
|
20371
|
+
const t = this.barIndexToTimestamp(pt.barIndex);
|
|
20372
|
+
if (t != null) pt.timestamp = t;
|
|
20373
|
+
continue;
|
|
20140
20374
|
}
|
|
20141
|
-
pt.barIndex =
|
|
20375
|
+
pt.barIndex = this.timestampToBarIndex(pt.timestamp);
|
|
20376
|
+
}
|
|
20377
|
+
}
|
|
20378
|
+
}
|
|
20379
|
+
/**
|
|
20380
|
+
* Captures a timestamp on any drawing point that is missing one, using the
|
|
20381
|
+
* current bars. Call this BEFORE mutating `dataStore.all` (insert/splice)
|
|
20382
|
+
* so that points still pointing at a stale barIndex get frozen to the
|
|
20383
|
+
* correct time before the mutation invalidates the index. Safe no-op for
|
|
20384
|
+
* points that already have a timestamp.
|
|
20385
|
+
*/
|
|
20386
|
+
backfillDrawingTimestamps() {
|
|
20387
|
+
if (this.dataStore.all.length === 0) return;
|
|
20388
|
+
for (const drawing of this.drawingManager.getDrawings()) {
|
|
20389
|
+
for (const pt of drawing.points) {
|
|
20390
|
+
if (pt.timestamp != null) continue;
|
|
20391
|
+
const t = this.barIndexToTimestamp(pt.barIndex);
|
|
20392
|
+
if (t != null) pt.timestamp = t;
|
|
20142
20393
|
}
|
|
20143
20394
|
}
|
|
20144
20395
|
}
|
|
@@ -20168,6 +20419,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
20168
20419
|
}
|
|
20169
20420
|
setSymbol(symbol) {
|
|
20170
20421
|
this.symbol = symbol;
|
|
20422
|
+
this.tradePopover?.setSymbol(symbol);
|
|
20171
20423
|
if (this.siSymbolSpan) {
|
|
20172
20424
|
this.siSymbolSpan.textContent = symbol;
|
|
20173
20425
|
this.siSymbolSpan.style.display = symbol ? "inline" : "none";
|
|
@@ -21175,7 +21427,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
21175
21427
|
return;
|
|
21176
21428
|
}
|
|
21177
21429
|
const { chartW, priceH } = this.getChartLayout();
|
|
21178
|
-
const priceRange = this.getScaledPriceRange(viewport);
|
|
21430
|
+
const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(viewport);
|
|
21179
21431
|
const bracketOrders = this.levels.filter(
|
|
21180
21432
|
(l) => l.type === "pending" && l.ToClose != null
|
|
21181
21433
|
);
|
|
@@ -21240,7 +21492,12 @@ var _ChartEngine = class _ChartEngine {
|
|
|
21240
21492
|
this.tradeDragHandler?.dragLabel ?? null,
|
|
21241
21493
|
this.tradeDragNewPrice,
|
|
21242
21494
|
this.selectedTradeLabel,
|
|
21243
|
-
|
|
21495
|
+
// Include _editOpenLabel so confirm/cancel buttons appear while the edit form is open
|
|
21496
|
+
// even before any change is staged — e.g. editing a position with no SL/TP.
|
|
21497
|
+
/* @__PURE__ */ new Set([
|
|
21498
|
+
...this.pendingLevelDrags.keys(),
|
|
21499
|
+
...this._editOpenLabel !== null ? [this._editOpenLabel] : []
|
|
21500
|
+
]),
|
|
21244
21501
|
this.positionRenderStyle,
|
|
21245
21502
|
this.hideLevelConfirmCancel,
|
|
21246
21503
|
this.draftOrderLabel,
|
|
@@ -21443,6 +21700,11 @@ var _ChartEngine = class _ChartEngine {
|
|
|
21443
21700
|
}
|
|
21444
21701
|
}
|
|
21445
21702
|
}
|
|
21703
|
+
if (result.newPrice === oldPrice) {
|
|
21704
|
+
this.tradeDragNewPrice = null;
|
|
21705
|
+
this.renderTradeLayer();
|
|
21706
|
+
return;
|
|
21707
|
+
}
|
|
21446
21708
|
const field = result.bracketType ?? "main";
|
|
21447
21709
|
const isNew = this._isBracketNew;
|
|
21448
21710
|
this._isBracketNew = false;
|
|
@@ -21548,7 +21810,22 @@ var _ChartEngine = class _ChartEngine {
|
|
|
21548
21810
|
}
|
|
21549
21811
|
applyPendingChanges(label) {
|
|
21550
21812
|
const changes = this.pendingLevelDrags.get(label);
|
|
21551
|
-
if (!changes)
|
|
21813
|
+
if (!changes) {
|
|
21814
|
+
if (this._editOpenLabel === label) {
|
|
21815
|
+
const level2 = this.levels.find((l) => l.label === label);
|
|
21816
|
+
this._editOpenLabel = null;
|
|
21817
|
+
this.selectedTradeLabel = null;
|
|
21818
|
+
this.renderTradeLayer();
|
|
21819
|
+
if (level2) {
|
|
21820
|
+
this.emitter.emit("tradeLevelConfirmed", {
|
|
21821
|
+
label,
|
|
21822
|
+
type: level2.type,
|
|
21823
|
+
isFullscreen: this.isFullscreen
|
|
21824
|
+
});
|
|
21825
|
+
}
|
|
21826
|
+
}
|
|
21827
|
+
return;
|
|
21828
|
+
}
|
|
21552
21829
|
const level = this.levels.find((l) => l.label === label);
|
|
21553
21830
|
if (!level) return;
|
|
21554
21831
|
if (this._editOpenLabel === label) this._editOpenLabel = null;
|
|
@@ -21627,6 +21904,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
21627
21904
|
if (!label && this.draftOrderLabel) {
|
|
21628
21905
|
this.removeDraftOrder();
|
|
21629
21906
|
}
|
|
21907
|
+
if (label && !this.pendingLevelDrags.has(label) && this._editOpenLabel === label) {
|
|
21908
|
+
this._cancelEditAndDeselect(label);
|
|
21909
|
+
return;
|
|
21910
|
+
}
|
|
21630
21911
|
const toRevert = label ? this.pendingLevelDrags.has(label) ? [[label, this.pendingLevelDrags.get(label) ?? []]] : [] : [...this.pendingLevelDrags.entries()];
|
|
21631
21912
|
for (const [lbl] of toRevert) {
|
|
21632
21913
|
this.undoIsNewBracketMutations(lbl);
|
|
@@ -22199,6 +22480,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
22199
22480
|
this.leftBar?.setMobileMode(isMobile);
|
|
22200
22481
|
this.topBar?.setMobileLayout(isMobile);
|
|
22201
22482
|
this.tradeButton?.setMobileMode(isMobile);
|
|
22483
|
+
this.tradePopover?.setCompact(isMobile);
|
|
22202
22484
|
if (this.siEl) {
|
|
22203
22485
|
this.siEl.style.flexDirection = isMobile ? "column" : "row";
|
|
22204
22486
|
this.siEl.style.alignItems = isMobile ? "flex-start" : "center";
|
|
@@ -22289,10 +22571,21 @@ var _ChartEngine = class _ChartEngine {
|
|
|
22289
22571
|
const y = touch.clientY - rect.top;
|
|
22290
22572
|
const { chartW, priceH } = this.getChartLayout();
|
|
22291
22573
|
const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(this.viewport);
|
|
22292
|
-
return
|
|
22293
|
-
|
|
22294
|
-
|
|
22295
|
-
|
|
22574
|
+
return this.makeDrawingPoint(
|
|
22575
|
+
this.scaleManager.pixelToBarIndex(x, this.viewport, chartW),
|
|
22576
|
+
this.scaleManager.pixelToPrice(y, priceRange, priceH)
|
|
22577
|
+
);
|
|
22578
|
+
}
|
|
22579
|
+
/**
|
|
22580
|
+
* Builds a fully-populated DrawingPoint from raw float coordinates. Captures
|
|
22581
|
+
* the absolute timestamp at this fractional barIndex so the anchor survives
|
|
22582
|
+
* timeframe changes, data reloads, and serialization round-trips.
|
|
22583
|
+
*/
|
|
22584
|
+
makeDrawingPoint(barIndex, price) {
|
|
22585
|
+
const point = { barIndex, price };
|
|
22586
|
+
const t = this.barIndexToTimestamp(barIndex);
|
|
22587
|
+
if (t != null) point.timestamp = t;
|
|
22588
|
+
return point;
|
|
22296
22589
|
}
|
|
22297
22590
|
/** Shared close-button handler used by both mouse click and touch tap. */
|
|
22298
22591
|
_handleTradeLevelClose(closedLevel) {
|
|
@@ -22402,6 +22695,49 @@ var _ChartEngine = class _ChartEngine {
|
|
|
22402
22695
|
}
|
|
22403
22696
|
this.scheduleRender();
|
|
22404
22697
|
}
|
|
22698
|
+
/**
|
|
22699
|
+
* Closes the edit form, clears selection, and emits `tradeLevelEditCancelled` when
|
|
22700
|
+
* the edit form was actually open for this label. Shared between click-outside,
|
|
22701
|
+
* repeat-click toggle, and the ✗ button so all exit paths produce the same payload.
|
|
22702
|
+
*/
|
|
22703
|
+
_cancelEditAndDeselect(label) {
|
|
22704
|
+
const wasEditFormOpen = this._editOpenLabel === label;
|
|
22705
|
+
const level = this.levels.find((l) => l.label === label);
|
|
22706
|
+
this._editOpenLabel = null;
|
|
22707
|
+
this.selectedTradeLabel = null;
|
|
22708
|
+
this.draftBracketPnl = {};
|
|
22709
|
+
this.renderTradeLayer();
|
|
22710
|
+
if (wasEditFormOpen && level) {
|
|
22711
|
+
this.emitter.emit("tradeLevelEditCancelled", {
|
|
22712
|
+
label,
|
|
22713
|
+
type: level.type,
|
|
22714
|
+
isFullscreen: this.isFullscreen
|
|
22715
|
+
});
|
|
22716
|
+
}
|
|
22717
|
+
}
|
|
22718
|
+
/**
|
|
22719
|
+
* Opens the external edit form for a trade level by emitting `tradeLevelEditOpen`.
|
|
22720
|
+
* Shared between pencil click, bare box click, and drag start so every entry
|
|
22721
|
+
* point fires the same payload. No-op when the form is already open for this
|
|
22722
|
+
* label, so repeat opens (e.g. pencil click followed by drag) don't spam the event.
|
|
22723
|
+
*/
|
|
22724
|
+
_openTradeLevelEditForm(level) {
|
|
22725
|
+
if (this._editOpenLabel === level.label) return;
|
|
22726
|
+
this.selectedTradeLabel = level.label;
|
|
22727
|
+
this._editOpenLabel = level.label;
|
|
22728
|
+
const { slPrice, tpPrice } = this._resolveBracketPrices(level);
|
|
22729
|
+
this.renderTradeLayer();
|
|
22730
|
+
this.emitter.emit("tradeLevelEditOpen", {
|
|
22731
|
+
label: level.label,
|
|
22732
|
+
type: level.type,
|
|
22733
|
+
data: level.data,
|
|
22734
|
+
price: level.price,
|
|
22735
|
+
side: level.side,
|
|
22736
|
+
stopLossPrice: slPrice,
|
|
22737
|
+
takeProfitPrice: tpPrice,
|
|
22738
|
+
isFullscreen: this.isFullscreen
|
|
22739
|
+
});
|
|
22740
|
+
}
|
|
22405
22741
|
/** Shared helper: initiate a trade level main-line drag from a hit area. */
|
|
22406
22742
|
_startTradeDragFromHit(dragHit, level, hitY) {
|
|
22407
22743
|
if (!this.tradeDragHandler) return;
|
|
@@ -22477,6 +22813,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
22477
22813
|
);
|
|
22478
22814
|
}
|
|
22479
22815
|
}
|
|
22816
|
+
this._openTradeLevelEditForm(level);
|
|
22480
22817
|
}
|
|
22481
22818
|
/** Shared helper: initiate a +SL/+TP add-bracket drag from a hit area. */
|
|
22482
22819
|
_startAddBracketFromHit(addHit, hitY) {
|
|
@@ -22590,6 +22927,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
22590
22927
|
this.dataLoader({ start: new Date(fromTime), end: new Date(toTime), interval }).then((rawBars) => {
|
|
22591
22928
|
const bars = this.maybeAggregate(rawBars);
|
|
22592
22929
|
if (bars.length === 0) return;
|
|
22930
|
+
this.backfillDrawingTimestamps();
|
|
22593
22931
|
const all = this.dataStore.all;
|
|
22594
22932
|
const liveBar = all[all.length - 1];
|
|
22595
22933
|
let insertCount = 0;
|
|
@@ -22610,6 +22948,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
22610
22948
|
startIndex: this.viewport.startIndex + insertCount,
|
|
22611
22949
|
endIndex: this.viewport.endIndex + insertCount
|
|
22612
22950
|
};
|
|
22951
|
+
this.reanchorDrawingsByTimestamp();
|
|
22613
22952
|
this.recomputeIndicators();
|
|
22614
22953
|
this.scheduleRender();
|
|
22615
22954
|
}
|
|
@@ -22804,6 +23143,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
22804
23143
|
}
|
|
22805
23144
|
};
|
|
22806
23145
|
_ChartEngine.DRAW_GESTURE_THRESHOLD = 4;
|
|
23146
|
+
_ChartEngine.TRADE_DRAG_PIXEL_THRESHOLD = 3;
|
|
22807
23147
|
var ChartEngine = _ChartEngine;
|
|
22808
23148
|
|
|
22809
23149
|
// src/data/WebSocketAdapter.ts
|