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.js
CHANGED
|
@@ -265,6 +265,8 @@ function resolveCssVarsInTheme(theme, container) {
|
|
|
265
265
|
dropdownHoverBg: resolve(theme.indicatorOverlay.dropdownHoverBg)
|
|
266
266
|
},
|
|
267
267
|
tradeLevels: {
|
|
268
|
+
buySide: resolve(theme.tradeLevels.buySide),
|
|
269
|
+
sellSide: resolve(theme.tradeLevels.sellSide),
|
|
268
270
|
tradeLine: resolve(theme.tradeLevels.tradeLine),
|
|
269
271
|
positionLine: resolve(theme.tradeLevels.positionLine),
|
|
270
272
|
pendingBuyLine: resolve(theme.tradeLevels.pendingBuyLine),
|
|
@@ -341,6 +343,8 @@ var DARK_THEME = {
|
|
|
341
343
|
dropdownHoverBg: "#1c2a35"
|
|
342
344
|
},
|
|
343
345
|
tradeLevels: {
|
|
346
|
+
buySide: "#26a69a",
|
|
347
|
+
sellSide: "#ef5350",
|
|
344
348
|
tradeLine: "#5caf79",
|
|
345
349
|
positionLine: "#7b61ff",
|
|
346
350
|
pendingBuyLine: "rgba(92,175,121,0.75)",
|
|
@@ -413,6 +417,8 @@ var LIGHT_THEME = {
|
|
|
413
417
|
dropdownHoverBg: "#f0f0f0"
|
|
414
418
|
},
|
|
415
419
|
tradeLevels: {
|
|
420
|
+
buySide: "#26a69a",
|
|
421
|
+
sellSide: "#ef5350",
|
|
416
422
|
tradeLine: "#5caf79",
|
|
417
423
|
positionLine: "#6d28d9",
|
|
418
424
|
pendingBuyLine: "rgba(92,175,121,0.75)",
|
|
@@ -698,6 +704,7 @@ var DEFAULT_LABELS = {
|
|
|
698
704
|
limit: "Limit",
|
|
699
705
|
stop: "Stop",
|
|
700
706
|
market: "Market",
|
|
707
|
+
at: "at",
|
|
701
708
|
qty: "Qty",
|
|
702
709
|
placeOrderTitle: "Place order at this price"
|
|
703
710
|
},
|
|
@@ -1673,16 +1680,18 @@ function computeFanOutLayout(cluster, boxH, gapPx = 4) {
|
|
|
1673
1680
|
return result;
|
|
1674
1681
|
}
|
|
1675
1682
|
function buildClusterBadgeLayout(cluster, chartW, ctx) {
|
|
1676
|
-
const
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1683
|
+
const posCount = cluster.members.filter(
|
|
1684
|
+
(m) => m.level.type === "position" && !m.level.entryPriceEditable
|
|
1685
|
+
).length;
|
|
1686
|
+
const orderCount = cluster.members.filter(
|
|
1687
|
+
(m) => m.level.type === "pending" || m.level.entryPriceEditable === true
|
|
1688
|
+
).length;
|
|
1689
|
+
const tradeCount = cluster.members.filter((m) => m.level.type === "trade").length;
|
|
1690
|
+
const parts = [];
|
|
1691
|
+
if (posCount > 0) parts.push(`${posCount} Position${posCount > 1 ? "s" : ""}`);
|
|
1692
|
+
if (orderCount > 0) parts.push(`${orderCount} Order${orderCount > 1 ? "s" : ""}`);
|
|
1693
|
+
if (tradeCount > 0) parts.push(`${tradeCount} Trade${tradeCount > 1 ? "s" : ""}`);
|
|
1694
|
+
const text = parts.join(" / ");
|
|
1686
1695
|
const PAD_X = 12;
|
|
1687
1696
|
const textW = ctx.measureText(text).width;
|
|
1688
1697
|
const EXPAND_ICON_W = 16;
|
|
@@ -2090,6 +2099,22 @@ function renderTradeLevels(ctx, levels, scale, priceRange, theme, chartW, priceH
|
|
|
2090
2099
|
const offMainDragActive = draggingLabel === offLevel.label;
|
|
2091
2100
|
const offSlPrice = offMainDragActive && dragNewSLPrice !== null ? dragNewSLPrice : bh.stopLossPrice;
|
|
2092
2101
|
const offTpPrice = offMainDragActive && dragNewTPPrice !== null ? dragNewTPPrice : bh.takeProfitPrice;
|
|
2102
|
+
if (offSlPrice !== void 0 && offTpPrice !== void 0) {
|
|
2103
|
+
const slY = scale.yToPixel(offSlPrice, priceRange, priceH);
|
|
2104
|
+
const tpY = scale.yToPixel(offTpPrice, priceRange, priceH);
|
|
2105
|
+
const bracketBH = Math.max(BRACKET_BOX_H, CLOSE_R * 2 + BRACKET_BOX_PAD_Y * 2);
|
|
2106
|
+
ctx.save();
|
|
2107
|
+
ctx.font = FONT_BRACKET;
|
|
2108
|
+
const closeSlot = CLOSE_R * 2 + BRACKET_BOX_PAD_X / 2;
|
|
2109
|
+
const slBW = BRACKET_BOX_PAD_X + ctx.measureText(`SL ${offSlPrice.toFixed(precision)}`).width + BRACKET_BOX_PAD_X + closeSlot;
|
|
2110
|
+
const tpBW = BRACKET_BOX_PAD_X + ctx.measureText(`TP ${offTpPrice.toFixed(precision)}`).width + BRACKET_BOX_PAD_X + closeSlot;
|
|
2111
|
+
ctx.restore();
|
|
2112
|
+
const connX = ((bracketLineXMap.get(slDragKey) ?? fallbackX) - slBW / 2 - 2 + ((bracketLineXMap.get(tpDragKey) ?? fallbackX) - tpBW / 2 - 2)) / 2;
|
|
2113
|
+
const slPillY = bracketBoxYMap.get(slDragKey) ?? slY - bracketBH / 2;
|
|
2114
|
+
const tpPillY = bracketBoxYMap.get(tpDragKey) ?? tpY - bracketBH / 2;
|
|
2115
|
+
const [upperY, lowerY] = tpPillY < slPillY ? [tpPillY, slPillY] : [slPillY, tpPillY];
|
|
2116
|
+
drawSLTPConnector(ctx, connX, upperY + bracketBH, lowerY, theme);
|
|
2117
|
+
}
|
|
2093
2118
|
if (offSlPrice !== void 0) {
|
|
2094
2119
|
drawBracketLine(
|
|
2095
2120
|
ctx,
|
|
@@ -2223,6 +2248,20 @@ function drawFanConnector(ctx, fanCenterY, realY, chartW, theme) {
|
|
|
2223
2248
|
ctx.stroke();
|
|
2224
2249
|
ctx.restore();
|
|
2225
2250
|
}
|
|
2251
|
+
function drawSLTPConnector(ctx, x, yTop, yBottom, theme) {
|
|
2252
|
+
if (yBottom - yTop < 2) return;
|
|
2253
|
+
ctx.save();
|
|
2254
|
+
ctx.strokeStyle = monoColor(theme);
|
|
2255
|
+
ctx.globalAlpha = 0.5;
|
|
2256
|
+
ctx.lineWidth = 1;
|
|
2257
|
+
ctx.setLineDash([3, 4]);
|
|
2258
|
+
ctx.beginPath();
|
|
2259
|
+
ctx.moveTo(x, yTop);
|
|
2260
|
+
ctx.lineTo(x, yBottom);
|
|
2261
|
+
ctx.stroke();
|
|
2262
|
+
ctx.setLineDash([]);
|
|
2263
|
+
ctx.restore();
|
|
2264
|
+
}
|
|
2226
2265
|
function drawDotHoverBox(ctx, level, y, dotX, dotR, theme, precision) {
|
|
2227
2266
|
const pl = level;
|
|
2228
2267
|
const rows = [];
|
|
@@ -2334,6 +2373,24 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2334
2373
|
const mainDragActive = draggingLabel === level.label;
|
|
2335
2374
|
const slRenderPrice = mainDragActive && dragNewSLPrice !== null ? dragNewSLPrice : bracketHost.stopLossPrice;
|
|
2336
2375
|
const tpRenderPrice = mainDragActive && dragNewTPPrice !== null ? dragNewTPPrice : bracketHost.takeProfitPrice;
|
|
2376
|
+
if (selected && slRenderPrice !== void 0 && tpRenderPrice !== void 0) {
|
|
2377
|
+
const slY = scale.yToPixel(slRenderPrice, priceRange, priceH);
|
|
2378
|
+
const tpY = scale.yToPixel(tpRenderPrice, priceRange, priceH);
|
|
2379
|
+
const bracketBH = hideConfirmCancel ? BRACKET_BOX_H : Math.max(BRACKET_BOX_H, closeR * 2 + BRACKET_BOX_PAD_Y * 2);
|
|
2380
|
+
ctx.save();
|
|
2381
|
+
ctx.font = FONT_BRACKET;
|
|
2382
|
+
const closeSlot = hideConfirmCancel ? 0 : closeR * 2 + BRACKET_BOX_PAD_X / 2;
|
|
2383
|
+
const slBW = BRACKET_BOX_PAD_X + ctx.measureText(`SL ${slRenderPrice.toFixed(precision)}`).width + BRACKET_BOX_PAD_X + closeSlot;
|
|
2384
|
+
const tpBW = BRACKET_BOX_PAD_X + ctx.measureText(`TP ${tpRenderPrice.toFixed(precision)}`).width + BRACKET_BOX_PAD_X + closeSlot;
|
|
2385
|
+
ctx.restore();
|
|
2386
|
+
const slBLineX = bracketLineXMap.get(slDragKey) ?? chartW * BRACKET_BASE_RATIO;
|
|
2387
|
+
const tpBLineX = bracketLineXMap.get(tpDragKey) ?? chartW * BRACKET_BASE_RATIO;
|
|
2388
|
+
const connX = (slBLineX - slBW / 2 - 2 + (tpBLineX - tpBW / 2 - 2)) / 2;
|
|
2389
|
+
const slPillY = bracketBoxYMap?.get(slDragKey) ?? slY - bracketBH / 2;
|
|
2390
|
+
const tpPillY = bracketBoxYMap?.get(tpDragKey) ?? tpY - bracketBH / 2;
|
|
2391
|
+
const [upperY, lowerY] = tpPillY < slPillY ? [tpPillY, slPillY] : [slPillY, tpPillY];
|
|
2392
|
+
drawSLTPConnector(ctx, connX, upperY + bracketBH, lowerY, theme);
|
|
2393
|
+
}
|
|
2337
2394
|
if (slRenderPrice !== void 0) {
|
|
2338
2395
|
const bLineX = bracketLineXMap.get(slDragKey) ?? chartW * BRACKET_BASE_RATIO;
|
|
2339
2396
|
drawBracketLine(
|
|
@@ -11332,6 +11389,14 @@ var DrawingManager = class {
|
|
|
11332
11389
|
this.onDrawingsChange = null;
|
|
11333
11390
|
/** Called when a text-type drawing is first completed — ChartEngine shows the text input. */
|
|
11334
11391
|
this.onStartTextEdit = null;
|
|
11392
|
+
/**
|
|
11393
|
+
* Called after drag/resize mutates a drawing's points so ChartEngine can
|
|
11394
|
+
* refresh each point's `timestamp` from the current dataStore. Required
|
|
11395
|
+
* because DrawingManager has no access to the data layer, and drag
|
|
11396
|
+
* reconstructs points from raw (barIndex, price) deltas that strip the
|
|
11397
|
+
* original point's timestamp.
|
|
11398
|
+
*/
|
|
11399
|
+
this.onPointsMutated = null;
|
|
11335
11400
|
/** Set to true by ChartEngine while the text input is focused — prevents Delete/Backspace from deleting the drawing. */
|
|
11336
11401
|
this.editingText = false;
|
|
11337
11402
|
// Drag state (move whole drawing)
|
|
@@ -11426,14 +11491,18 @@ var DrawingManager = class {
|
|
|
11426
11491
|
const dBar = point.barIndex - this.dragStartMouse.barIndex;
|
|
11427
11492
|
const dPrice = point.price - this.dragStartMouse.price;
|
|
11428
11493
|
for (let i = 0; i < this.dragDrawing.points.length; i++) {
|
|
11494
|
+
const start = this.dragStartPoints[i];
|
|
11495
|
+
if (!start) continue;
|
|
11429
11496
|
this.dragDrawing.points[i] = {
|
|
11430
|
-
barIndex:
|
|
11431
|
-
price:
|
|
11497
|
+
barIndex: start.barIndex + dBar,
|
|
11498
|
+
price: start.price + dPrice
|
|
11432
11499
|
};
|
|
11433
11500
|
}
|
|
11501
|
+
this.onPointsMutated?.(this.dragDrawing);
|
|
11434
11502
|
}
|
|
11435
11503
|
if (this.resizeDrawing && this.resizePointIndex >= 0) {
|
|
11436
11504
|
this.resizeDrawing.points[this.resizePointIndex] = { ...point };
|
|
11505
|
+
this.onPointsMutated?.(this.resizeDrawing);
|
|
11437
11506
|
}
|
|
11438
11507
|
}
|
|
11439
11508
|
/**
|
|
@@ -13190,8 +13259,8 @@ var TradeButton = class {
|
|
|
13190
13259
|
this.el.style.opacity = "1";
|
|
13191
13260
|
Object.assign(this.el.style, {
|
|
13192
13261
|
background: theme.tooltip.background,
|
|
13193
|
-
color:
|
|
13194
|
-
border:
|
|
13262
|
+
color: theme.tradeLevels.buySide,
|
|
13263
|
+
border: `1px solid ${theme.tradeLevels.buySide}`
|
|
13195
13264
|
});
|
|
13196
13265
|
});
|
|
13197
13266
|
this.el.addEventListener("mouseleave", (e) => {
|
|
@@ -13254,11 +13323,12 @@ var TradeButton = class {
|
|
|
13254
13323
|
|
|
13255
13324
|
// src/ui/TradePopover.ts
|
|
13256
13325
|
var TradePopover = class {
|
|
13257
|
-
constructor(theme, lots, onDraftCreate, labels = DEFAULT_LABELS.trade, onVisibilityChange) {
|
|
13326
|
+
constructor(theme, lots, symbol, onDraftCreate, labels = DEFAULT_LABELS.trade, onVisibilityChange) {
|
|
13258
13327
|
this._price = 0;
|
|
13259
13328
|
this._pricePrecision = 2;
|
|
13260
13329
|
this._buyOrderType = "limit";
|
|
13261
13330
|
this._sellOrderType = "limit";
|
|
13331
|
+
this._compact = false;
|
|
13262
13332
|
this.onDocMouseDown = (e) => {
|
|
13263
13333
|
if (this.isVisible && !this.el.contains(e.target)) {
|
|
13264
13334
|
this.hide();
|
|
@@ -13266,6 +13336,7 @@ var TradePopover = class {
|
|
|
13266
13336
|
};
|
|
13267
13337
|
this.theme = theme;
|
|
13268
13338
|
this.lots = lots;
|
|
13339
|
+
this.symbol = symbol;
|
|
13269
13340
|
this.onDraftCreate = onDraftCreate;
|
|
13270
13341
|
this.labels = labels;
|
|
13271
13342
|
this.onVisibilityChange = onVisibilityChange;
|
|
@@ -13274,7 +13345,6 @@ var TradePopover = class {
|
|
|
13274
13345
|
position: "absolute",
|
|
13275
13346
|
display: "none",
|
|
13276
13347
|
flexDirection: "column",
|
|
13277
|
-
gap: "1px",
|
|
13278
13348
|
background: theme.tooltip.background,
|
|
13279
13349
|
border: `1px solid ${theme.axisBorder}`,
|
|
13280
13350
|
borderRadius: "5px",
|
|
@@ -13283,7 +13353,7 @@ var TradePopover = class {
|
|
|
13283
13353
|
boxShadow: "0 4px 16px rgba(0,0,0,0.25)",
|
|
13284
13354
|
fontFamily: "'Inter', system-ui, sans-serif",
|
|
13285
13355
|
fontSize: "12px",
|
|
13286
|
-
|
|
13356
|
+
width: "max-content",
|
|
13287
13357
|
pointerEvents: "auto"
|
|
13288
13358
|
});
|
|
13289
13359
|
document.addEventListener("mousedown", this.onDocMouseDown);
|
|
@@ -13297,10 +13367,8 @@ var TradePopover = class {
|
|
|
13297
13367
|
this._sellOrderType = sellOrderType;
|
|
13298
13368
|
this.rebuild();
|
|
13299
13369
|
this.el.style.display = "flex";
|
|
13300
|
-
const popW = 145;
|
|
13301
13370
|
this.el.style.right = `${buttonRight}px`;
|
|
13302
13371
|
this.el.style.top = `${buttonY - 16}px`;
|
|
13303
|
-
this.el.style.width = `${popW}px`;
|
|
13304
13372
|
if (!wasVisible) this.onVisibilityChange?.();
|
|
13305
13373
|
}
|
|
13306
13374
|
hide() {
|
|
@@ -13314,6 +13382,12 @@ var TradePopover = class {
|
|
|
13314
13382
|
setLots(lots) {
|
|
13315
13383
|
this.lots = lots;
|
|
13316
13384
|
}
|
|
13385
|
+
setSymbol(symbol) {
|
|
13386
|
+
this.symbol = symbol;
|
|
13387
|
+
}
|
|
13388
|
+
setCompact(compact) {
|
|
13389
|
+
this._compact = compact;
|
|
13390
|
+
}
|
|
13317
13391
|
setTheme(theme) {
|
|
13318
13392
|
this.theme = theme;
|
|
13319
13393
|
Object.assign(this.el.style, {
|
|
@@ -13327,31 +13401,46 @@ var TradePopover = class {
|
|
|
13327
13401
|
// ── Private ───────────────────────────────────────────────────────────────
|
|
13328
13402
|
rebuild() {
|
|
13329
13403
|
this.el.innerHTML = "";
|
|
13404
|
+
const divider = document.createElement("div");
|
|
13405
|
+
Object.assign(divider.style, {
|
|
13406
|
+
height: "1px",
|
|
13407
|
+
background: this.theme.axisBorder,
|
|
13408
|
+
margin: "0 8px"
|
|
13409
|
+
});
|
|
13330
13410
|
this.el.append(
|
|
13331
|
-
this.makeRow(this.
|
|
13332
|
-
|
|
13411
|
+
this.makeRow(this.theme.tradeLevels.sellSide, "sell", this._sellOrderType),
|
|
13412
|
+
divider,
|
|
13413
|
+
this.makeRow(this.theme.tradeLevels.buySide, "buy", this._buyOrderType)
|
|
13333
13414
|
);
|
|
13334
13415
|
}
|
|
13335
|
-
makeRow(
|
|
13416
|
+
makeRow(color, side, orderType) {
|
|
13336
13417
|
const btn = document.createElement("button");
|
|
13337
|
-
const priceStr = this._price.toFixed(this._pricePrecision);
|
|
13338
|
-
btn.textContent = `${label} ${orderType === "limit" ? this.labels.limit : this.labels.stop} ${priceStr}`;
|
|
13339
13418
|
Object.assign(btn.style, {
|
|
13340
|
-
display: "
|
|
13419
|
+
display: "flex",
|
|
13420
|
+
alignItems: "center",
|
|
13421
|
+
gap: "8px",
|
|
13341
13422
|
width: "100%",
|
|
13342
13423
|
background: "transparent",
|
|
13343
|
-
color,
|
|
13344
13424
|
border: "none",
|
|
13345
13425
|
borderRadius: "3px",
|
|
13346
|
-
padding: "
|
|
13347
|
-
textAlign: "left",
|
|
13426
|
+
padding: this._compact ? "5px 8px" : "8px 10px",
|
|
13348
13427
|
cursor: "pointer",
|
|
13349
|
-
fontSize: "12px",
|
|
13350
13428
|
fontFamily: "'Inter', system-ui, sans-serif",
|
|
13351
|
-
|
|
13429
|
+
fontSize: this._compact ? "11px" : "12px"
|
|
13352
13430
|
});
|
|
13431
|
+
btn.appendChild(this.makeChevron(color, side));
|
|
13432
|
+
const sideLabel = side === "sell" ? this.labels.sell : this.labels.buy;
|
|
13433
|
+
const orderTypeLabel = orderType === "limit" ? this.labels.limit : this.labels.stop;
|
|
13434
|
+
const priceStr = this._price.toFixed(this._pricePrecision);
|
|
13435
|
+
const colored = document.createElement("span");
|
|
13436
|
+
colored.textContent = `${sideLabel} ${orderTypeLabel}`;
|
|
13437
|
+
Object.assign(colored.style, { color, fontWeight: "600" });
|
|
13438
|
+
const plain = document.createElement("span");
|
|
13439
|
+
plain.textContent = ` ${this.lots} ${this.symbol} ${this.labels.at} ${priceStr}`;
|
|
13440
|
+
Object.assign(plain.style, { color: "#ffffff", fontWeight: "400" });
|
|
13441
|
+
btn.append(colored, plain);
|
|
13353
13442
|
btn.addEventListener("mouseenter", () => {
|
|
13354
|
-
btn.style.background =
|
|
13443
|
+
btn.style.background = "rgba(255,255,255,0.06)";
|
|
13355
13444
|
});
|
|
13356
13445
|
btn.addEventListener("mouseleave", () => {
|
|
13357
13446
|
btn.style.background = "transparent";
|
|
@@ -13363,6 +13452,23 @@ var TradePopover = class {
|
|
|
13363
13452
|
});
|
|
13364
13453
|
return btn;
|
|
13365
13454
|
}
|
|
13455
|
+
makeChevron(color, side) {
|
|
13456
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
13457
|
+
const svg = document.createElementNS(ns, "svg");
|
|
13458
|
+
svg.setAttribute("width", "10");
|
|
13459
|
+
svg.setAttribute("height", "8");
|
|
13460
|
+
svg.setAttribute("viewBox", "0 0 10 8");
|
|
13461
|
+
svg.style.flexShrink = "0";
|
|
13462
|
+
const path = document.createElementNS(ns, "path");
|
|
13463
|
+
path.setAttribute("d", side === "sell" ? "M1 1 L5 7 L9 1" : "M1 7 L5 1 L9 7");
|
|
13464
|
+
path.setAttribute("stroke", color);
|
|
13465
|
+
path.setAttribute("fill", "none");
|
|
13466
|
+
path.setAttribute("stroke-width", "1.5");
|
|
13467
|
+
path.setAttribute("stroke-linecap", "round");
|
|
13468
|
+
path.setAttribute("stroke-linejoin", "round");
|
|
13469
|
+
svg.appendChild(path);
|
|
13470
|
+
return svg;
|
|
13471
|
+
}
|
|
13366
13472
|
};
|
|
13367
13473
|
|
|
13368
13474
|
// src/ui/DraftQtyFlyout.ts
|
|
@@ -15064,6 +15170,13 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15064
15170
|
/** Pending resize — committed only after mouse moves past DRAW_GESTURE_THRESHOLD. */
|
|
15065
15171
|
this._pendingDrawResize = null;
|
|
15066
15172
|
// px
|
|
15173
|
+
/**
|
|
15174
|
+
* Pending trade-level drag — committed only after mouse moves past TRADE_DRAG_PIXEL_THRESHOLD.
|
|
15175
|
+
* A mouseup before the threshold means it was a click: route to _openTradeLevelEditForm instead
|
|
15176
|
+
* of finishing a zero-delta drag (which would otherwise stage a spurious pending change).
|
|
15177
|
+
*/
|
|
15178
|
+
this._pendingTradeDrag = null;
|
|
15179
|
+
// px
|
|
15067
15180
|
// Pane resize
|
|
15068
15181
|
this.paneHeightOverrides = /* @__PURE__ */ new Map();
|
|
15069
15182
|
this.separatorDrag = null;
|
|
@@ -15079,6 +15192,21 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15079
15192
|
// lerp target; null = snap on next render
|
|
15080
15193
|
this._lockedAutoFitBase = null;
|
|
15081
15194
|
// non-null = user has manually adjusted Y-axis
|
|
15195
|
+
/**
|
|
15196
|
+
* When setState() restores a persisted viewport but data isn't loaded yet,
|
|
15197
|
+
* we stash the desired time window here and apply it from loadData() once
|
|
15198
|
+
* bars are available. Cleared after one application.
|
|
15199
|
+
*/
|
|
15200
|
+
this._pendingViewportRestore = null;
|
|
15201
|
+
/**
|
|
15202
|
+
* Debounces viewport/priceScale stateChange emissions during continuous
|
|
15203
|
+
* gestures (pan, wheel-zoom, price-scale drag). Pan/zoom don't emit
|
|
15204
|
+
* stateChange directly because doing so per-frame would be wasteful; this
|
|
15205
|
+
* timer coalesces bursts so the persisted viewport catches up a few ms
|
|
15206
|
+
* after the user stops interacting. Paired with the consumer's own
|
|
15207
|
+
* debounce (e.g. CRM's 800ms save) for minimal traffic.
|
|
15208
|
+
*/
|
|
15209
|
+
this._viewportPersistTimer = null;
|
|
15082
15210
|
this.axisDrag = null;
|
|
15083
15211
|
this.subPaneScaleFactors = /* @__PURE__ */ new Map();
|
|
15084
15212
|
this._mobileCrosshairActive = false;
|
|
@@ -15239,14 +15367,14 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15239
15367
|
const dx = x - this._pendingDrawDrag.startX;
|
|
15240
15368
|
const dy = y - this._pendingDrawDrag.startY;
|
|
15241
15369
|
if (Math.sqrt(dx * dx + dy * dy) > _ChartEngine.DRAW_GESTURE_THRESHOLD) {
|
|
15242
|
-
this.drawingManager.startDrag(
|
|
15243
|
-
|
|
15244
|
-
price
|
|
15245
|
-
|
|
15370
|
+
this.drawingManager.startDrag(
|
|
15371
|
+
this._pendingDrawDrag.drawing,
|
|
15372
|
+
this.makeDrawingPoint(rawBarIndex, price)
|
|
15373
|
+
);
|
|
15246
15374
|
this._pendingDrawDrag = null;
|
|
15247
15375
|
}
|
|
15248
15376
|
}
|
|
15249
|
-
this.drawingManager.onMouseMove(
|
|
15377
|
+
this.drawingManager.onMouseMove(this.makeDrawingPoint(rawBarIndex, price));
|
|
15250
15378
|
if (this.axisDrag) {
|
|
15251
15379
|
if (this.axisDrag.axis === "price") {
|
|
15252
15380
|
const delta = y - this.axisDrag.startPos;
|
|
@@ -15288,6 +15416,18 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15288
15416
|
this.scheduleRender();
|
|
15289
15417
|
return;
|
|
15290
15418
|
}
|
|
15419
|
+
if (this._pendingTradeDrag) {
|
|
15420
|
+
const dx = x - this._pendingTradeDrag.startX;
|
|
15421
|
+
const dy = y - this._pendingTradeDrag.startY;
|
|
15422
|
+
if (Math.sqrt(dx * dx + dy * dy) > _ChartEngine.TRADE_DRAG_PIXEL_THRESHOLD) {
|
|
15423
|
+
const { dragHit, level, hitY } = this._pendingTradeDrag;
|
|
15424
|
+
this._pendingTradeDrag = null;
|
|
15425
|
+
this._startTradeDragFromHit(dragHit, level, hitY);
|
|
15426
|
+
this.drawCanvas.style.cursor = "ns-resize";
|
|
15427
|
+
} else {
|
|
15428
|
+
return;
|
|
15429
|
+
}
|
|
15430
|
+
}
|
|
15291
15431
|
if (this.tradeDragHandler?.active) {
|
|
15292
15432
|
const moveResult = this.tradeDragHandler.onMouseMove(
|
|
15293
15433
|
y,
|
|
@@ -15328,6 +15468,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15328
15468
|
const dragHover = hitTestTradeLevelDrag(x, y, this.tradeLevelDragAreas);
|
|
15329
15469
|
if (dragHover) {
|
|
15330
15470
|
this.drawCanvas.style.cursor = "ns-resize";
|
|
15471
|
+
if (dragHover.label !== this.hoveredTradeLabel) {
|
|
15472
|
+
this.hoveredTradeLabel = dragHover.label;
|
|
15473
|
+
this.renderTradeLayer();
|
|
15474
|
+
}
|
|
15331
15475
|
this.scheduleRender();
|
|
15332
15476
|
return;
|
|
15333
15477
|
}
|
|
@@ -15397,6 +15541,16 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15397
15541
|
this.scheduleRender();
|
|
15398
15542
|
};
|
|
15399
15543
|
this.onMouseUp = () => {
|
|
15544
|
+
if (this._pendingTradeDrag) {
|
|
15545
|
+
const { level } = this._pendingTradeDrag;
|
|
15546
|
+
this._pendingTradeDrag = null;
|
|
15547
|
+
if (this._editOpenLabel === level.label && !this.pendingLevelDrags.has(level.label)) {
|
|
15548
|
+
this._cancelEditAndDeselect(level.label);
|
|
15549
|
+
return;
|
|
15550
|
+
}
|
|
15551
|
+
this._openTradeLevelEditForm(level);
|
|
15552
|
+
return;
|
|
15553
|
+
}
|
|
15400
15554
|
if (this.tradeDragHandler?.active) {
|
|
15401
15555
|
const result = this.tradeDragHandler.endDrag();
|
|
15402
15556
|
if (result) {
|
|
@@ -15413,9 +15567,11 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15413
15567
|
return;
|
|
15414
15568
|
}
|
|
15415
15569
|
if (this.axisDrag) {
|
|
15570
|
+
const wasPriceAxis = this.axisDrag.axis === "price" || this.axisDrag.axis === "time";
|
|
15416
15571
|
this.axisDrag = null;
|
|
15417
15572
|
this.drawCanvas.style.cursor = "default";
|
|
15418
15573
|
this.scheduleRender();
|
|
15574
|
+
if (wasPriceAxis) this.scheduleViewportPersist();
|
|
15419
15575
|
}
|
|
15420
15576
|
if (this.separatorDrag) {
|
|
15421
15577
|
this.separatorDrag = null;
|
|
@@ -15577,22 +15733,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15577
15733
|
if (editHit) {
|
|
15578
15734
|
e.preventDefault();
|
|
15579
15735
|
const level = this.levels.find((l) => l.label === editHit.label);
|
|
15580
|
-
if (level)
|
|
15581
|
-
this.selectedTradeLabel = level.label;
|
|
15582
|
-
this.renderTradeLayer();
|
|
15583
|
-
const { slPrice: _slE, tpPrice: _tpE } = this._resolveBracketPrices(level);
|
|
15584
|
-
this._editOpenLabel = level.label;
|
|
15585
|
-
this.emitter.emit("tradeLevelEditOpen", {
|
|
15586
|
-
label: level.label,
|
|
15587
|
-
type: level.type,
|
|
15588
|
-
data: level.data,
|
|
15589
|
-
price: level.price,
|
|
15590
|
-
side: level.side,
|
|
15591
|
-
stopLossPrice: _slE,
|
|
15592
|
-
takeProfitPrice: _tpE,
|
|
15593
|
-
isFullscreen: this.isFullscreen
|
|
15594
|
-
});
|
|
15595
|
-
}
|
|
15736
|
+
if (level) this._openTradeLevelEditForm(level);
|
|
15596
15737
|
return;
|
|
15597
15738
|
}
|
|
15598
15739
|
}
|
|
@@ -15607,23 +15748,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15607
15748
|
if (this.hideLevelConfirmCancel && !isMobileDraggable) {
|
|
15608
15749
|
const hasPending = this.pendingLevelDrags.has(level.label);
|
|
15609
15750
|
if (this.selectedTradeLabel === level.label && !hasPending) {
|
|
15610
|
-
this.
|
|
15611
|
-
|
|
15612
|
-
this.selectedTradeLabel = level.label;
|
|
15751
|
+
this._cancelEditAndDeselect(level.label);
|
|
15752
|
+
return;
|
|
15613
15753
|
}
|
|
15614
|
-
this.
|
|
15615
|
-
const { slPrice: _sl1, tpPrice: _tp1 } = this._resolveBracketPrices(level);
|
|
15616
|
-
this._editOpenLabel = level.label;
|
|
15617
|
-
this.emitter.emit("tradeLevelEditOpen", {
|
|
15618
|
-
label: level.label,
|
|
15619
|
-
type: level.type,
|
|
15620
|
-
data: level.data,
|
|
15621
|
-
price: level.price,
|
|
15622
|
-
side: level.side,
|
|
15623
|
-
stopLossPrice: _sl1,
|
|
15624
|
-
takeProfitPrice: _tp1,
|
|
15625
|
-
isFullscreen: this.isFullscreen
|
|
15626
|
-
});
|
|
15754
|
+
this._openTradeLevelEditForm(level);
|
|
15627
15755
|
return;
|
|
15628
15756
|
}
|
|
15629
15757
|
this._startTradeDragFromHit(dragHit, level, y);
|
|
@@ -15647,23 +15775,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15647
15775
|
if (level) {
|
|
15648
15776
|
const hasPending = this.pendingLevelDrags.has(hoverHit.label);
|
|
15649
15777
|
if (this.selectedTradeLabel === hoverHit.label && !hasPending) {
|
|
15650
|
-
this.
|
|
15651
|
-
|
|
15652
|
-
this.selectedTradeLabel = hoverHit.label;
|
|
15778
|
+
this._cancelEditAndDeselect(hoverHit.label);
|
|
15779
|
+
return;
|
|
15653
15780
|
}
|
|
15654
|
-
|
|
15655
|
-
this._editOpenLabel = level.label;
|
|
15656
|
-
this.renderTradeLayer();
|
|
15657
|
-
this.emitter.emit("tradeLevelEditOpen", {
|
|
15658
|
-
label: level.label,
|
|
15659
|
-
type: level.type,
|
|
15660
|
-
data: level.data,
|
|
15661
|
-
price: level.price,
|
|
15662
|
-
side: level.side,
|
|
15663
|
-
stopLossPrice: _slH,
|
|
15664
|
-
takeProfitPrice: _tpH,
|
|
15665
|
-
isFullscreen: this.isFullscreen
|
|
15666
|
-
});
|
|
15781
|
+
this._openTradeLevelEditForm(level);
|
|
15667
15782
|
return;
|
|
15668
15783
|
}
|
|
15669
15784
|
}
|
|
@@ -15862,8 +15977,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15862
15977
|
return;
|
|
15863
15978
|
}
|
|
15864
15979
|
if (this.axisDrag) {
|
|
15980
|
+
const wasPriceAxis = this.axisDrag.axis === "price" || this.axisDrag.axis === "time";
|
|
15865
15981
|
this.axisDrag = null;
|
|
15866
15982
|
this.scheduleRender();
|
|
15983
|
+
if (wasPriceAxis) this.scheduleViewportPersist();
|
|
15867
15984
|
return;
|
|
15868
15985
|
}
|
|
15869
15986
|
const wasAnchorTouch = this._drawTouchAnchorActive;
|
|
@@ -15948,22 +16065,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15948
16065
|
const editHit = hitTestTradeLevelEdit(x, y, this.tradeLevelEditAreas);
|
|
15949
16066
|
if (editHit) {
|
|
15950
16067
|
const level = this.levels.find((l) => l.label === editHit.label);
|
|
15951
|
-
if (level)
|
|
15952
|
-
this.selectedTradeLabel = level.label;
|
|
15953
|
-
this.renderTradeLayer();
|
|
15954
|
-
const { slPrice: _sl2, tpPrice: _tp2 } = this._resolveBracketPrices(level);
|
|
15955
|
-
this._editOpenLabel = level.label;
|
|
15956
|
-
this.emitter.emit("tradeLevelEditOpen", {
|
|
15957
|
-
label: level.label,
|
|
15958
|
-
type: level.type,
|
|
15959
|
-
data: level.data,
|
|
15960
|
-
price: level.price,
|
|
15961
|
-
side: level.side,
|
|
15962
|
-
stopLossPrice: _sl2,
|
|
15963
|
-
takeProfitPrice: _tp2,
|
|
15964
|
-
isFullscreen: this.isFullscreen
|
|
15965
|
-
});
|
|
15966
|
-
}
|
|
16068
|
+
if (level) this._openTradeLevelEditForm(level);
|
|
15967
16069
|
return;
|
|
15968
16070
|
}
|
|
15969
16071
|
}
|
|
@@ -15981,8 +16083,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15981
16083
|
const level = this.levels.find((l) => l.label === dragHit.label);
|
|
15982
16084
|
if (level) {
|
|
15983
16085
|
if (!(this.hideLevelConfirmCancel && !dragHit.isBracket)) {
|
|
15984
|
-
this.
|
|
15985
|
-
this.drawCanvas.style.cursor = "ns-resize";
|
|
16086
|
+
this._pendingTradeDrag = { dragHit, level, hitY: y, startX: x, startY: y };
|
|
15986
16087
|
return;
|
|
15987
16088
|
}
|
|
15988
16089
|
}
|
|
@@ -15992,49 +16093,24 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15992
16093
|
if (hoverHit) {
|
|
15993
16094
|
const hasPending = this.pendingLevelDrags.has(hoverHit.label);
|
|
15994
16095
|
if (this.selectedTradeLabel === hoverHit.label && !hasPending) {
|
|
15995
|
-
this.
|
|
16096
|
+
this._cancelEditAndDeselect(hoverHit.label);
|
|
16097
|
+
return;
|
|
16098
|
+
}
|
|
16099
|
+
const level = this.levels.find((l) => l.label === hoverHit.label);
|
|
16100
|
+
if (level) {
|
|
16101
|
+
this._openTradeLevelEditForm(level);
|
|
15996
16102
|
} else {
|
|
15997
16103
|
this.selectedTradeLabel = hoverHit.label;
|
|
15998
|
-
|
|
15999
|
-
this.renderTradeLayer();
|
|
16000
|
-
if (this.hideLevelConfirmCancel) {
|
|
16001
|
-
const level = this.levels.find((l) => l.label === hoverHit.label);
|
|
16002
|
-
if (level) {
|
|
16003
|
-
const { slPrice: _sl3, tpPrice: _tp3 } = this._resolveBracketPrices(level);
|
|
16004
|
-
this._editOpenLabel = level.label;
|
|
16005
|
-
this.emitter.emit("tradeLevelEditOpen", {
|
|
16006
|
-
label: level.label,
|
|
16007
|
-
type: level.type,
|
|
16008
|
-
data: level.data,
|
|
16009
|
-
price: level.price,
|
|
16010
|
-
side: level.side,
|
|
16011
|
-
stopLossPrice: _sl3,
|
|
16012
|
-
takeProfitPrice: _tp3,
|
|
16013
|
-
isFullscreen: this.isFullscreen
|
|
16014
|
-
});
|
|
16015
|
-
}
|
|
16104
|
+
this.renderTradeLayer();
|
|
16016
16105
|
}
|
|
16017
16106
|
return;
|
|
16018
16107
|
} else if (this.selectedTradeLabel !== null) {
|
|
16019
16108
|
if (!this.hideLevelConfirmCancel) {
|
|
16020
16109
|
const lbl = this.selectedTradeLabel;
|
|
16021
16110
|
const pending = this.pendingLevelDrags.get(lbl);
|
|
16022
|
-
const isEditFormOpen = this._editOpenLabel === lbl;
|
|
16023
16111
|
if (!pending || pending.length === 0) {
|
|
16024
|
-
|
|
16025
|
-
|
|
16026
|
-
this._editOpenLabel = null;
|
|
16027
|
-
if (lvl) {
|
|
16028
|
-
this.emitter.emit("tradeLevelEditCancelled", {
|
|
16029
|
-
label: lbl,
|
|
16030
|
-
type: lvl.type,
|
|
16031
|
-
isFullscreen: this.isFullscreen
|
|
16032
|
-
});
|
|
16033
|
-
}
|
|
16034
|
-
}
|
|
16035
|
-
this.selectedTradeLabel = null;
|
|
16036
|
-
this.renderTradeLayer();
|
|
16037
|
-
} else if (pending.every((c) => c.isNew)) {
|
|
16112
|
+
this._cancelEditAndDeselect(lbl);
|
|
16113
|
+
} else {
|
|
16038
16114
|
this.revertPendingChanges(lbl);
|
|
16039
16115
|
}
|
|
16040
16116
|
}
|
|
@@ -16089,10 +16165,9 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16089
16165
|
this.scheduleRender();
|
|
16090
16166
|
return;
|
|
16091
16167
|
}
|
|
16092
|
-
const consumed = this.drawingManager.onMouseDown(
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
});
|
|
16168
|
+
const consumed = this.drawingManager.onMouseDown(
|
|
16169
|
+
this.makeDrawingPoint(rawBarIndex, price)
|
|
16170
|
+
);
|
|
16096
16171
|
if (!consumed) {
|
|
16097
16172
|
const handleHit = this.drawingManager.getHandleAtPoint(
|
|
16098
16173
|
x,
|
|
@@ -16687,6 +16762,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16687
16762
|
this.tradePopover = new TradePopover(
|
|
16688
16763
|
this.theme,
|
|
16689
16764
|
0.01,
|
|
16765
|
+
this.symbol,
|
|
16690
16766
|
(side, price, orderType) => {
|
|
16691
16767
|
this.createDraftOrder(side, price, orderType);
|
|
16692
16768
|
},
|
|
@@ -16790,6 +16866,12 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16790
16866
|
this.drawCanvas.style.cursor = "default";
|
|
16791
16867
|
});
|
|
16792
16868
|
this.drawingManager.onDrawingsChange = () => this.emitStateChange();
|
|
16869
|
+
this.drawingManager.onPointsMutated = (drawing) => {
|
|
16870
|
+
for (const pt of drawing.points) {
|
|
16871
|
+
const t = this.barIndexToTimestamp(pt.barIndex);
|
|
16872
|
+
if (t != null) pt.timestamp = t;
|
|
16873
|
+
}
|
|
16874
|
+
};
|
|
16793
16875
|
this.drawingManager.onStartTextEdit = (drawing) => this.showTextInput(drawing);
|
|
16794
16876
|
this.panHandler = new PanHandler(
|
|
16795
16877
|
this.drawCanvas,
|
|
@@ -16799,16 +16881,19 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16799
16881
|
this.clearDuration();
|
|
16800
16882
|
this.checkNeedMoreData();
|
|
16801
16883
|
this.scheduleRender();
|
|
16884
|
+
this.scheduleViewportPersist();
|
|
16802
16885
|
},
|
|
16803
16886
|
() => this.dataStore.length,
|
|
16804
16887
|
() => this.scheduleRender(),
|
|
16805
16888
|
() => this.drawingManager.getActiveTool() !== null || this.drawingManager.isDraggingDrawing() || this.drawingManager.isResizingDrawing() || this.separatorDrag !== null || this.axisDrag !== null || (this.tradeDragHandler?.active ?? false) || this._mobileCrosshairActive,
|
|
16806
16889
|
(deltaY) => {
|
|
16890
|
+
if (this.priceScaleFactor === 1) return;
|
|
16807
16891
|
const { priceH } = this.getChartLayout();
|
|
16808
16892
|
const currentRange = this._displayedPriceRange ?? this.getScaledPriceRange(this._targetViewport);
|
|
16809
16893
|
const pricePerPixel = (currentRange.max - currentRange.min) / priceH;
|
|
16810
16894
|
this.pricePanOffset += deltaY * pricePerPixel;
|
|
16811
16895
|
this.scheduleRender();
|
|
16896
|
+
this.scheduleViewportPersist();
|
|
16812
16897
|
},
|
|
16813
16898
|
() => {
|
|
16814
16899
|
this._isPanning = true;
|
|
@@ -16831,6 +16916,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16831
16916
|
this.clearDuration();
|
|
16832
16917
|
this.checkNeedMoreData();
|
|
16833
16918
|
this.scheduleRender();
|
|
16919
|
+
this.scheduleViewportPersist();
|
|
16834
16920
|
},
|
|
16835
16921
|
() => this.dataStore.length,
|
|
16836
16922
|
() => this.scheduleRender(),
|
|
@@ -16851,6 +16937,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16851
16937
|
this.clearDuration();
|
|
16852
16938
|
this.checkNeedMoreData();
|
|
16853
16939
|
this.scheduleRender();
|
|
16940
|
+
this.scheduleViewportPersist();
|
|
16854
16941
|
}
|
|
16855
16942
|
);
|
|
16856
16943
|
this.drawCanvas.addEventListener("mousemove", this.onMouseMove);
|
|
@@ -16888,6 +16975,15 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16888
16975
|
return true;
|
|
16889
16976
|
}).map((s) => s.indicator);
|
|
16890
16977
|
}
|
|
16978
|
+
/** Schedules a stateChange emission so viewport/priceScale changes made by
|
|
16979
|
+
* pan/zoom/price-scale drag reach persistence. Coalesces rapid calls. */
|
|
16980
|
+
scheduleViewportPersist() {
|
|
16981
|
+
if (this._viewportPersistTimer) clearTimeout(this._viewportPersistTimer);
|
|
16982
|
+
this._viewportPersistTimer = setTimeout(() => {
|
|
16983
|
+
this._viewportPersistTimer = null;
|
|
16984
|
+
this.emitStateChange();
|
|
16985
|
+
}, 500);
|
|
16986
|
+
}
|
|
16891
16987
|
get isFullscreen() {
|
|
16892
16988
|
return this._isCssFullscreen || !!document.fullscreenElement;
|
|
16893
16989
|
}
|
|
@@ -16979,6 +17075,22 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16979
17075
|
}
|
|
16980
17076
|
this.recomputeIndicators();
|
|
16981
17077
|
this.reanchorDrawingsByTimestamp();
|
|
17078
|
+
if (this._pendingViewportRestore) {
|
|
17079
|
+
const pending = this._pendingViewportRestore;
|
|
17080
|
+
this._pendingViewportRestore = null;
|
|
17081
|
+
if (pending.priceScale) {
|
|
17082
|
+
this.priceScaleFactor = pending.priceScale.factor;
|
|
17083
|
+
this.pricePanOffset = pending.priceScale.panOffset;
|
|
17084
|
+
this._displayedPriceRange = null;
|
|
17085
|
+
this._lockedAutoFitBase = null;
|
|
17086
|
+
}
|
|
17087
|
+
if (pending.viewport) {
|
|
17088
|
+
this.applyViewportFromTimestamps(
|
|
17089
|
+
pending.viewport.startTime,
|
|
17090
|
+
pending.viewport.endTime
|
|
17091
|
+
);
|
|
17092
|
+
}
|
|
17093
|
+
}
|
|
16982
17094
|
this.updateCountdownState();
|
|
16983
17095
|
this.scheduleRender();
|
|
16984
17096
|
return this;
|
|
@@ -16999,6 +17111,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16999
17111
|
};
|
|
17000
17112
|
this._targetViewport = { ...this.viewport };
|
|
17001
17113
|
this.drawingManager.shiftBarIndices(n);
|
|
17114
|
+
this.reanchorDrawingsByTimestamp();
|
|
17002
17115
|
this.recomputeIndicators();
|
|
17003
17116
|
this.scheduleRender();
|
|
17004
17117
|
}
|
|
@@ -17522,15 +17635,44 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17522
17635
|
...d,
|
|
17523
17636
|
points: d.points.map((p) => ({
|
|
17524
17637
|
...p,
|
|
17525
|
-
timestamp
|
|
17638
|
+
// Prefer the point's own timestamp (captured at placement / drag /
|
|
17639
|
+
// resize); only derive from current bars as a fallback for legacy
|
|
17640
|
+
// points. Re-deriving on every export would otherwise corrupt the
|
|
17641
|
+
// anchor after a timeframe change has staled the cached barIndex.
|
|
17642
|
+
timestamp: p.timestamp ?? this.barIndexToTimestamp(p.barIndex)
|
|
17526
17643
|
}))
|
|
17527
17644
|
})),
|
|
17528
17645
|
positionRenderStyle: this.positionRenderStyle,
|
|
17529
17646
|
tradeDisplayFilter: this.tradeDisplayFilter,
|
|
17530
17647
|
canvasColors: Object.keys(this.settingsCanvasColors).length > 0 ? { ...this.settingsCanvasColors } : void 0,
|
|
17531
|
-
tfcEnabled: this.tfcActive
|
|
17648
|
+
tfcEnabled: this.tfcActive,
|
|
17649
|
+
viewport: this.getViewportTimestamps(),
|
|
17650
|
+
priceScale: this.priceScaleFactor !== 1 || this.pricePanOffset !== 0 ? { factor: this.priceScaleFactor, panOffset: this.pricePanOffset } : void 0
|
|
17532
17651
|
};
|
|
17533
17652
|
}
|
|
17653
|
+
/**
|
|
17654
|
+
* Captures the current viewport as a {startTime, endTime} timestamp pair,
|
|
17655
|
+
* preserving sub-bar fractional position. Returns undefined only when no
|
|
17656
|
+
* bars are loaded (viewport has no meaningful time anchor).
|
|
17657
|
+
*/
|
|
17658
|
+
getViewportTimestamps() {
|
|
17659
|
+
if (this.dataStore.all.length === 0) return void 0;
|
|
17660
|
+
const startTime = this.barIndexToTimestamp(this.viewport.startIndex);
|
|
17661
|
+
const endTime = this.barIndexToTimestamp(this.viewport.endIndex);
|
|
17662
|
+
if (startTime == null || endTime == null) return void 0;
|
|
17663
|
+
return { startTime, endTime };
|
|
17664
|
+
}
|
|
17665
|
+
/**
|
|
17666
|
+
* Restores the viewport from a timestamp pair, converting each edge back
|
|
17667
|
+
* into a fractional bar index relative to the current dataStore. Callers
|
|
17668
|
+
* must ensure bars are loaded before invoking.
|
|
17669
|
+
*/
|
|
17670
|
+
applyViewportFromTimestamps(startTime, endTime) {
|
|
17671
|
+
const startIndex = this.timestampToBarIndex(startTime);
|
|
17672
|
+
const endIndex = this.timestampToBarIndex(endTime);
|
|
17673
|
+
this.viewport = { ...this.viewport, startIndex, endIndex };
|
|
17674
|
+
this._targetViewport = { ...this.viewport };
|
|
17675
|
+
}
|
|
17534
17676
|
/**
|
|
17535
17677
|
* Restores chart configuration from a previously captured `ChartState`.
|
|
17536
17678
|
* Call this after construction to hydrate a chart from persisted state.
|
|
@@ -17577,18 +17719,11 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17577
17719
|
}
|
|
17578
17720
|
}
|
|
17579
17721
|
}
|
|
17580
|
-
const bars = this.dataStore.all;
|
|
17581
17722
|
const resolvedDrawings = state.drawings.map((d) => ({
|
|
17582
17723
|
...d,
|
|
17583
17724
|
points: d.points.map((p) => {
|
|
17584
|
-
if (
|
|
17585
|
-
|
|
17586
|
-
while (lo < hi) {
|
|
17587
|
-
const mid = lo + hi >> 1;
|
|
17588
|
-
if ((bars[mid]?.time ?? 0) < p.timestamp) lo = mid + 1;
|
|
17589
|
-
else hi = mid;
|
|
17590
|
-
}
|
|
17591
|
-
return { ...p, barIndex: lo + (p.barIndex - Math.floor(p.barIndex)) };
|
|
17725
|
+
if (p.timestamp == null || this.dataStore.all.length === 0) return p;
|
|
17726
|
+
return { ...p, barIndex: this.timestampToBarIndex(p.timestamp) };
|
|
17592
17727
|
})
|
|
17593
17728
|
}));
|
|
17594
17729
|
this.drawingManager.loadDrawings(resolvedDrawings);
|
|
@@ -17606,6 +17741,25 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17606
17741
|
this.tfcActive = state.tfcEnabled;
|
|
17607
17742
|
this.topBar?.setTfcActive(state.tfcEnabled);
|
|
17608
17743
|
}
|
|
17744
|
+
if (state.priceScale) {
|
|
17745
|
+
this.priceScaleFactor = state.priceScale.factor;
|
|
17746
|
+
this.pricePanOffset = state.priceScale.panOffset;
|
|
17747
|
+
this._displayedPriceRange = null;
|
|
17748
|
+
this._lockedAutoFitBase = null;
|
|
17749
|
+
}
|
|
17750
|
+
if (state.viewport) {
|
|
17751
|
+
if (this.dataStore.all.length > 0) {
|
|
17752
|
+
this.applyViewportFromTimestamps(
|
|
17753
|
+
state.viewport.startTime,
|
|
17754
|
+
state.viewport.endTime
|
|
17755
|
+
);
|
|
17756
|
+
} else {
|
|
17757
|
+
this._pendingViewportRestore = {
|
|
17758
|
+
viewport: state.viewport,
|
|
17759
|
+
priceScale: state.priceScale
|
|
17760
|
+
};
|
|
17761
|
+
}
|
|
17762
|
+
}
|
|
17609
17763
|
} finally {
|
|
17610
17764
|
this.isRestoringState = false;
|
|
17611
17765
|
}
|
|
@@ -17614,23 +17768,120 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17614
17768
|
return this;
|
|
17615
17769
|
}
|
|
17616
17770
|
/**
|
|
17617
|
-
*
|
|
17618
|
-
*
|
|
17619
|
-
*
|
|
17771
|
+
* Converts a fractional barIndex into an absolute unix-ms timestamp by
|
|
17772
|
+
* interpolating across the containing bar. Extrapolates linearly for indexes
|
|
17773
|
+
* outside [0, bars.length-1] using the adjacent bar's width.
|
|
17774
|
+
*
|
|
17775
|
+
* This is the inverse of `timestampToBarIndex` and the authoritative way to
|
|
17776
|
+
* capture "where in time" a drawing point sits at placement, drag, or resize.
|
|
17777
|
+
* Returns undefined only when no bars are loaded.
|
|
17778
|
+
*/
|
|
17779
|
+
barIndexToTimestamp(idx) {
|
|
17780
|
+
const bars = this.dataStore.all;
|
|
17781
|
+
const n = bars.length;
|
|
17782
|
+
if (n === 0) return void 0;
|
|
17783
|
+
const first = bars[0];
|
|
17784
|
+
if (!first) return void 0;
|
|
17785
|
+
if (n === 1) return first.time;
|
|
17786
|
+
const floorIdx = Math.floor(idx);
|
|
17787
|
+
const frac = idx - floorIdx;
|
|
17788
|
+
if (floorIdx < 0) {
|
|
17789
|
+
const second = bars[1];
|
|
17790
|
+
if (!second) return first.time;
|
|
17791
|
+
return first.time + idx * (second.time - first.time);
|
|
17792
|
+
}
|
|
17793
|
+
if (floorIdx >= n - 1) {
|
|
17794
|
+
const last = bars[n - 1];
|
|
17795
|
+
const prev = bars[n - 2];
|
|
17796
|
+
if (!last || !prev) return first.time;
|
|
17797
|
+
return last.time + (idx - (n - 1)) * (last.time - prev.time);
|
|
17798
|
+
}
|
|
17799
|
+
const lo = bars[floorIdx];
|
|
17800
|
+
const hi = bars[floorIdx + 1];
|
|
17801
|
+
if (!lo || !hi) return first.time;
|
|
17802
|
+
return lo.time + frac * (hi.time - lo.time);
|
|
17803
|
+
}
|
|
17804
|
+
/**
|
|
17805
|
+
* Converts a unix-ms timestamp into a fractional barIndex using floor-bar
|
|
17806
|
+
* containment: the result is `floorBar + (ts - floorBar.time) / (nextBar.time - floorBar.time)`.
|
|
17807
|
+
* Extrapolates linearly for timestamps outside the loaded range using the edge
|
|
17808
|
+
* bar width, so drawings outside the visible data still render at the correct
|
|
17809
|
+
* relative offset rather than snapping to an edge.
|
|
17810
|
+
*
|
|
17811
|
+
* Matches TradingView's floor-bar semantics and preserves sub-bar time
|
|
17812
|
+
* precision across timeframe changes.
|
|
17813
|
+
*/
|
|
17814
|
+
timestampToBarIndex(ts) {
|
|
17815
|
+
const bars = this.dataStore.all;
|
|
17816
|
+
const n = bars.length;
|
|
17817
|
+
if (n === 0) return 0;
|
|
17818
|
+
const first = bars[0];
|
|
17819
|
+
if (!first) return 0;
|
|
17820
|
+
if (n === 1) return 0;
|
|
17821
|
+
if (ts <= first.time) {
|
|
17822
|
+
const second = bars[1];
|
|
17823
|
+
if (!second) return 0;
|
|
17824
|
+
const barMs2 = second.time - first.time;
|
|
17825
|
+
return barMs2 > 0 ? (ts - first.time) / barMs2 : 0;
|
|
17826
|
+
}
|
|
17827
|
+
const lastIdx = n - 1;
|
|
17828
|
+
const lastBar = bars[lastIdx];
|
|
17829
|
+
const prevBar = bars[lastIdx - 1];
|
|
17830
|
+
if (!lastBar || !prevBar) return 0;
|
|
17831
|
+
if (ts >= lastBar.time) {
|
|
17832
|
+
const barMs2 = lastBar.time - prevBar.time;
|
|
17833
|
+
return lastIdx + (barMs2 > 0 ? (ts - lastBar.time) / barMs2 : 0);
|
|
17834
|
+
}
|
|
17835
|
+
let lo = 0, hi = lastIdx;
|
|
17836
|
+
while (lo < hi) {
|
|
17837
|
+
const mid = lo + hi + 1 >> 1;
|
|
17838
|
+
if ((bars[mid]?.time ?? 0) <= ts) lo = mid;
|
|
17839
|
+
else hi = mid - 1;
|
|
17840
|
+
}
|
|
17841
|
+
const loBar = bars[lo];
|
|
17842
|
+
const hiBar = bars[lo + 1];
|
|
17843
|
+
if (!loBar || !hiBar) return lo;
|
|
17844
|
+
const barMs = hiBar.time - loBar.time;
|
|
17845
|
+
return lo + (barMs > 0 ? (ts - loBar.time) / barMs : 0);
|
|
17846
|
+
}
|
|
17847
|
+
/**
|
|
17848
|
+
* Re-anchors all loaded drawings to the current dataset. For points with a
|
|
17849
|
+
* stored timestamp, barIndex is recomputed via `timestampToBarIndex`; for
|
|
17850
|
+
* points missing a timestamp (legacy saves or freshly placed points whose
|
|
17851
|
+
* timestamp capture was skipped), the timestamp is backfilled from the
|
|
17852
|
+
* current barIndex so subsequent data reloads remain accurate.
|
|
17853
|
+
*
|
|
17854
|
+
* Called after loadData(), prependData(), and whenever the underlying bars
|
|
17855
|
+
* change such that cached barIndex values would otherwise drift.
|
|
17620
17856
|
*/
|
|
17621
17857
|
reanchorDrawingsByTimestamp() {
|
|
17622
17858
|
const bars = this.dataStore.all;
|
|
17623
17859
|
if (bars.length === 0) return;
|
|
17624
17860
|
for (const drawing of this.drawingManager.getDrawings()) {
|
|
17625
17861
|
for (const pt of drawing.points) {
|
|
17626
|
-
if (
|
|
17627
|
-
|
|
17628
|
-
|
|
17629
|
-
|
|
17630
|
-
if ((bars[mid]?.time ?? 0) < pt.timestamp) lo = mid + 1;
|
|
17631
|
-
else hi = mid;
|
|
17862
|
+
if (pt.timestamp == null) {
|
|
17863
|
+
const t = this.barIndexToTimestamp(pt.barIndex);
|
|
17864
|
+
if (t != null) pt.timestamp = t;
|
|
17865
|
+
continue;
|
|
17632
17866
|
}
|
|
17633
|
-
pt.barIndex =
|
|
17867
|
+
pt.barIndex = this.timestampToBarIndex(pt.timestamp);
|
|
17868
|
+
}
|
|
17869
|
+
}
|
|
17870
|
+
}
|
|
17871
|
+
/**
|
|
17872
|
+
* Captures a timestamp on any drawing point that is missing one, using the
|
|
17873
|
+
* current bars. Call this BEFORE mutating `dataStore.all` (insert/splice)
|
|
17874
|
+
* so that points still pointing at a stale barIndex get frozen to the
|
|
17875
|
+
* correct time before the mutation invalidates the index. Safe no-op for
|
|
17876
|
+
* points that already have a timestamp.
|
|
17877
|
+
*/
|
|
17878
|
+
backfillDrawingTimestamps() {
|
|
17879
|
+
if (this.dataStore.all.length === 0) return;
|
|
17880
|
+
for (const drawing of this.drawingManager.getDrawings()) {
|
|
17881
|
+
for (const pt of drawing.points) {
|
|
17882
|
+
if (pt.timestamp != null) continue;
|
|
17883
|
+
const t = this.barIndexToTimestamp(pt.barIndex);
|
|
17884
|
+
if (t != null) pt.timestamp = t;
|
|
17634
17885
|
}
|
|
17635
17886
|
}
|
|
17636
17887
|
}
|
|
@@ -17660,6 +17911,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17660
17911
|
}
|
|
17661
17912
|
setSymbol(symbol) {
|
|
17662
17913
|
this.symbol = symbol;
|
|
17914
|
+
this.tradePopover?.setSymbol(symbol);
|
|
17663
17915
|
if (this.siSymbolSpan) {
|
|
17664
17916
|
this.siSymbolSpan.textContent = symbol;
|
|
17665
17917
|
this.siSymbolSpan.style.display = symbol ? "inline" : "none";
|
|
@@ -18667,7 +18919,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18667
18919
|
return;
|
|
18668
18920
|
}
|
|
18669
18921
|
const { chartW, priceH } = this.getChartLayout();
|
|
18670
|
-
const priceRange = this.getScaledPriceRange(viewport);
|
|
18922
|
+
const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(viewport);
|
|
18671
18923
|
const bracketOrders = this.levels.filter(
|
|
18672
18924
|
(l) => l.type === "pending" && l.ToClose != null
|
|
18673
18925
|
);
|
|
@@ -18732,7 +18984,12 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18732
18984
|
this.tradeDragHandler?.dragLabel ?? null,
|
|
18733
18985
|
this.tradeDragNewPrice,
|
|
18734
18986
|
this.selectedTradeLabel,
|
|
18735
|
-
|
|
18987
|
+
// Include _editOpenLabel so confirm/cancel buttons appear while the edit form is open
|
|
18988
|
+
// even before any change is staged — e.g. editing a position with no SL/TP.
|
|
18989
|
+
/* @__PURE__ */ new Set([
|
|
18990
|
+
...this.pendingLevelDrags.keys(),
|
|
18991
|
+
...this._editOpenLabel !== null ? [this._editOpenLabel] : []
|
|
18992
|
+
]),
|
|
18736
18993
|
this.positionRenderStyle,
|
|
18737
18994
|
this.hideLevelConfirmCancel,
|
|
18738
18995
|
this.draftOrderLabel,
|
|
@@ -18935,6 +19192,11 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18935
19192
|
}
|
|
18936
19193
|
}
|
|
18937
19194
|
}
|
|
19195
|
+
if (result.newPrice === oldPrice) {
|
|
19196
|
+
this.tradeDragNewPrice = null;
|
|
19197
|
+
this.renderTradeLayer();
|
|
19198
|
+
return;
|
|
19199
|
+
}
|
|
18938
19200
|
const field = result.bracketType ?? "main";
|
|
18939
19201
|
const isNew = this._isBracketNew;
|
|
18940
19202
|
this._isBracketNew = false;
|
|
@@ -19040,7 +19302,22 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19040
19302
|
}
|
|
19041
19303
|
applyPendingChanges(label) {
|
|
19042
19304
|
const changes = this.pendingLevelDrags.get(label);
|
|
19043
|
-
if (!changes)
|
|
19305
|
+
if (!changes) {
|
|
19306
|
+
if (this._editOpenLabel === label) {
|
|
19307
|
+
const level2 = this.levels.find((l) => l.label === label);
|
|
19308
|
+
this._editOpenLabel = null;
|
|
19309
|
+
this.selectedTradeLabel = null;
|
|
19310
|
+
this.renderTradeLayer();
|
|
19311
|
+
if (level2) {
|
|
19312
|
+
this.emitter.emit("tradeLevelConfirmed", {
|
|
19313
|
+
label,
|
|
19314
|
+
type: level2.type,
|
|
19315
|
+
isFullscreen: this.isFullscreen
|
|
19316
|
+
});
|
|
19317
|
+
}
|
|
19318
|
+
}
|
|
19319
|
+
return;
|
|
19320
|
+
}
|
|
19044
19321
|
const level = this.levels.find((l) => l.label === label);
|
|
19045
19322
|
if (!level) return;
|
|
19046
19323
|
if (this._editOpenLabel === label) this._editOpenLabel = null;
|
|
@@ -19119,6 +19396,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19119
19396
|
if (!label && this.draftOrderLabel) {
|
|
19120
19397
|
this.removeDraftOrder();
|
|
19121
19398
|
}
|
|
19399
|
+
if (label && !this.pendingLevelDrags.has(label) && this._editOpenLabel === label) {
|
|
19400
|
+
this._cancelEditAndDeselect(label);
|
|
19401
|
+
return;
|
|
19402
|
+
}
|
|
19122
19403
|
const toRevert = label ? this.pendingLevelDrags.has(label) ? [[label, this.pendingLevelDrags.get(label) ?? []]] : [] : [...this.pendingLevelDrags.entries()];
|
|
19123
19404
|
for (const [lbl] of toRevert) {
|
|
19124
19405
|
this.undoIsNewBracketMutations(lbl);
|
|
@@ -19691,6 +19972,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19691
19972
|
this.leftBar?.setMobileMode(isMobile);
|
|
19692
19973
|
this.topBar?.setMobileLayout(isMobile);
|
|
19693
19974
|
this.tradeButton?.setMobileMode(isMobile);
|
|
19975
|
+
this.tradePopover?.setCompact(isMobile);
|
|
19694
19976
|
if (this.siEl) {
|
|
19695
19977
|
this.siEl.style.flexDirection = isMobile ? "column" : "row";
|
|
19696
19978
|
this.siEl.style.alignItems = isMobile ? "flex-start" : "center";
|
|
@@ -19781,10 +20063,21 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19781
20063
|
const y = touch.clientY - rect.top;
|
|
19782
20064
|
const { chartW, priceH } = this.getChartLayout();
|
|
19783
20065
|
const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(this.viewport);
|
|
19784
|
-
return
|
|
19785
|
-
|
|
19786
|
-
|
|
19787
|
-
|
|
20066
|
+
return this.makeDrawingPoint(
|
|
20067
|
+
this.scaleManager.pixelToBarIndex(x, this.viewport, chartW),
|
|
20068
|
+
this.scaleManager.pixelToPrice(y, priceRange, priceH)
|
|
20069
|
+
);
|
|
20070
|
+
}
|
|
20071
|
+
/**
|
|
20072
|
+
* Builds a fully-populated DrawingPoint from raw float coordinates. Captures
|
|
20073
|
+
* the absolute timestamp at this fractional barIndex so the anchor survives
|
|
20074
|
+
* timeframe changes, data reloads, and serialization round-trips.
|
|
20075
|
+
*/
|
|
20076
|
+
makeDrawingPoint(barIndex, price) {
|
|
20077
|
+
const point = { barIndex, price };
|
|
20078
|
+
const t = this.barIndexToTimestamp(barIndex);
|
|
20079
|
+
if (t != null) point.timestamp = t;
|
|
20080
|
+
return point;
|
|
19788
20081
|
}
|
|
19789
20082
|
/** Shared close-button handler used by both mouse click and touch tap. */
|
|
19790
20083
|
_handleTradeLevelClose(closedLevel) {
|
|
@@ -19894,6 +20187,49 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19894
20187
|
}
|
|
19895
20188
|
this.scheduleRender();
|
|
19896
20189
|
}
|
|
20190
|
+
/**
|
|
20191
|
+
* Closes the edit form, clears selection, and emits `tradeLevelEditCancelled` when
|
|
20192
|
+
* the edit form was actually open for this label. Shared between click-outside,
|
|
20193
|
+
* repeat-click toggle, and the ✗ button so all exit paths produce the same payload.
|
|
20194
|
+
*/
|
|
20195
|
+
_cancelEditAndDeselect(label) {
|
|
20196
|
+
const wasEditFormOpen = this._editOpenLabel === label;
|
|
20197
|
+
const level = this.levels.find((l) => l.label === label);
|
|
20198
|
+
this._editOpenLabel = null;
|
|
20199
|
+
this.selectedTradeLabel = null;
|
|
20200
|
+
this.draftBracketPnl = {};
|
|
20201
|
+
this.renderTradeLayer();
|
|
20202
|
+
if (wasEditFormOpen && level) {
|
|
20203
|
+
this.emitter.emit("tradeLevelEditCancelled", {
|
|
20204
|
+
label,
|
|
20205
|
+
type: level.type,
|
|
20206
|
+
isFullscreen: this.isFullscreen
|
|
20207
|
+
});
|
|
20208
|
+
}
|
|
20209
|
+
}
|
|
20210
|
+
/**
|
|
20211
|
+
* Opens the external edit form for a trade level by emitting `tradeLevelEditOpen`.
|
|
20212
|
+
* Shared between pencil click, bare box click, and drag start so every entry
|
|
20213
|
+
* point fires the same payload. No-op when the form is already open for this
|
|
20214
|
+
* label, so repeat opens (e.g. pencil click followed by drag) don't spam the event.
|
|
20215
|
+
*/
|
|
20216
|
+
_openTradeLevelEditForm(level) {
|
|
20217
|
+
if (this._editOpenLabel === level.label) return;
|
|
20218
|
+
this.selectedTradeLabel = level.label;
|
|
20219
|
+
this._editOpenLabel = level.label;
|
|
20220
|
+
const { slPrice, tpPrice } = this._resolveBracketPrices(level);
|
|
20221
|
+
this.renderTradeLayer();
|
|
20222
|
+
this.emitter.emit("tradeLevelEditOpen", {
|
|
20223
|
+
label: level.label,
|
|
20224
|
+
type: level.type,
|
|
20225
|
+
data: level.data,
|
|
20226
|
+
price: level.price,
|
|
20227
|
+
side: level.side,
|
|
20228
|
+
stopLossPrice: slPrice,
|
|
20229
|
+
takeProfitPrice: tpPrice,
|
|
20230
|
+
isFullscreen: this.isFullscreen
|
|
20231
|
+
});
|
|
20232
|
+
}
|
|
19897
20233
|
/** Shared helper: initiate a trade level main-line drag from a hit area. */
|
|
19898
20234
|
_startTradeDragFromHit(dragHit, level, hitY) {
|
|
19899
20235
|
if (!this.tradeDragHandler) return;
|
|
@@ -19969,6 +20305,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19969
20305
|
);
|
|
19970
20306
|
}
|
|
19971
20307
|
}
|
|
20308
|
+
this._openTradeLevelEditForm(level);
|
|
19972
20309
|
}
|
|
19973
20310
|
/** Shared helper: initiate a +SL/+TP add-bracket drag from a hit area. */
|
|
19974
20311
|
_startAddBracketFromHit(addHit, hitY) {
|
|
@@ -20082,6 +20419,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
20082
20419
|
this.dataLoader({ start: new Date(fromTime), end: new Date(toTime), interval }).then((rawBars) => {
|
|
20083
20420
|
const bars = this.maybeAggregate(rawBars);
|
|
20084
20421
|
if (bars.length === 0) return;
|
|
20422
|
+
this.backfillDrawingTimestamps();
|
|
20085
20423
|
const all = this.dataStore.all;
|
|
20086
20424
|
const liveBar = all[all.length - 1];
|
|
20087
20425
|
let insertCount = 0;
|
|
@@ -20102,6 +20440,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
20102
20440
|
startIndex: this.viewport.startIndex + insertCount,
|
|
20103
20441
|
endIndex: this.viewport.endIndex + insertCount
|
|
20104
20442
|
};
|
|
20443
|
+
this.reanchorDrawingsByTimestamp();
|
|
20105
20444
|
this.recomputeIndicators();
|
|
20106
20445
|
this.scheduleRender();
|
|
20107
20446
|
}
|
|
@@ -20296,6 +20635,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
20296
20635
|
}
|
|
20297
20636
|
};
|
|
20298
20637
|
_ChartEngine.DRAW_GESTURE_THRESHOLD = 4;
|
|
20638
|
+
_ChartEngine.TRADE_DRAG_PIXEL_THRESHOLD = 3;
|
|
20299
20639
|
var ChartEngine = _ChartEngine;
|
|
20300
20640
|
|
|
20301
20641
|
// src/data/WebSocketAdapter.ts
|