Skip to content

Commit b0ab682

Browse files
authored
Coalesce continuous chart saves (#241)
1 parent 0d4dd26 commit b0ab682

7 files changed

Lines changed: 766 additions & 29 deletions

File tree

‎scripts/binance-orderbook-trade.user.js‎

Lines changed: 190 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// @namespace binance.orderbook.trade
44
// @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E
55
// @icon64 data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E
6-
// @version 2.7.177
6+
// @version 2.7.178
77
// @author jackhai9
88
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
99
// @match https://www.binance.com/*/futures/*
@@ -2186,6 +2186,40 @@
21862186
}
21872187
return matches.length === 1 ? matches[0] : null;
21882188
}
2189+
function createTradeInputResolver(ownerDocument, {
2190+
initialRoot = null,
2191+
panelId,
2192+
isVisibleElement
2193+
}) {
2194+
if (!ownerDocument?.querySelectorAll || typeof isVisibleElement !== "function") {
2195+
throw new Error("交易表单解析依赖异常");
2196+
}
2197+
let cachedRoot = initialRoot;
2198+
const resolveInputs = (options = null) => {
2199+
const requirePrice = options?.requirePrice !== false;
2200+
if (cachedRoot?.isConnected) {
2201+
const cachedInputs = findActiveTradeInputs(cachedRoot, {
2202+
panelId,
2203+
isVisibleElement,
2204+
requirePrice
2205+
});
2206+
if (cachedInputs) {
2207+
cachedRoot = cachedInputs.root;
2208+
return cachedInputs;
2209+
}
2210+
return null;
2211+
}
2212+
cachedRoot = null;
2213+
const discoveredInputs = findActiveTradeInputs(ownerDocument, {
2214+
panelId,
2215+
isVisibleElement,
2216+
requirePrice
2217+
});
2218+
if (discoveredInputs) cachedRoot = discoveredInputs.root;
2219+
return discoveredInputs;
2220+
};
2221+
return resolveInputs;
2222+
}
21892223
function createBoundedInputWriter({ writeValue, maxWriteAttempts }) {
21902224
if (typeof writeValue !== "function") {
21912225
throw new Error("输入框写入依赖异常");
@@ -2804,6 +2838,96 @@
28042838
throw new Error("图表保存接口未能恢复");
28052839
}
28062840
}
2841+
function createTradingViewRemoveSaveBurstController(api, {
2842+
settleQuietMs = 120,
2843+
maxWaitMs = 400,
2844+
setTimeoutFn = setTimeout,
2845+
clearTimeoutFn = clearTimeout
2846+
} = {}) {
2847+
validateTradingViewApi(api);
2848+
if (!Number.isFinite(settleQuietMs) || settleQuietMs <= 0) {
2849+
throw new Error("图表保存合并静默时间无效");
2850+
}
2851+
if (!Number.isFinite(maxWaitMs) || maxWaitMs < settleQuietMs) {
2852+
throw new Error("图表保存合并最长等待时间无效");
2853+
}
2854+
const sessionSaveChart = api.saveChart;
2855+
let activeBurst = null;
2856+
let removeEventCount = 0;
2857+
let saveRequestCount = 0;
2858+
let fullSaveCount = 0;
2859+
let stopped = false;
2860+
const clearBurstTimers = (burst) => {
2861+
if (burst.settleTimer !== null) clearTimeoutFn(burst.settleTimer);
2862+
if (burst.maxWaitTimer !== null) clearTimeoutFn(burst.maxWaitTimer);
2863+
burst.settleTimer = null;
2864+
burst.maxWaitTimer = null;
2865+
};
2866+
const flushActiveBurst = () => {
2867+
const burst = activeBurst;
2868+
if (!burst) return void 0;
2869+
clearBurstTimers(burst);
2870+
activeBurst = null;
2871+
restoreSaveChartMethod(
2872+
api,
2873+
burst.wrapper,
2874+
burst.originalSaveChart,
2875+
burst.originalDescriptor
2876+
);
2877+
if (!burst.pendingSave) return void 0;
2878+
fullSaveCount += 1;
2879+
return burst.originalSaveChart.apply(
2880+
burst.pendingSave.thisValue,
2881+
burst.pendingSave.args
2882+
);
2883+
};
2884+
const scheduleBurstSettle = (burst) => {
2885+
if (burst.settleTimer !== null) clearTimeoutFn(burst.settleTimer);
2886+
burst.settleTimer = setTimeoutFn(flushActiveBurst, settleQuietMs);
2887+
};
2888+
const startBurst = () => {
2889+
if (activeBurst || api.saveChart !== sessionSaveChart) return activeBurst;
2890+
const originalSaveChart = api.saveChart;
2891+
const originalDescriptor = Object.getOwnPropertyDescriptor(api, "saveChart");
2892+
const burst = {
2893+
maxWaitTimer: null,
2894+
originalDescriptor,
2895+
originalSaveChart,
2896+
pendingSave: null,
2897+
settleTimer: null,
2898+
wrapper: null
2899+
};
2900+
burst.wrapper = function removeSaveBurstWrapper(...args) {
2901+
saveRequestCount += 1;
2902+
burst.pendingSave = { thisValue: this, args };
2903+
return void 0;
2904+
};
2905+
api.saveChart = burst.wrapper;
2906+
if (api.saveChart !== burst.wrapper) {
2907+
throw new Error("图表保存接口无法启用删除事件合并");
2908+
}
2909+
activeBurst = burst;
2910+
burst.maxWaitTimer = setTimeoutFn(flushActiveBurst, maxWaitMs);
2911+
return burst;
2912+
};
2913+
const handleDrawingEvent = (_drawingId, eventType) => {
2914+
if (stopped || eventType !== "remove") return;
2915+
removeEventCount += 1;
2916+
const burst = startBurst();
2917+
if (burst) scheduleBurstSettle(burst);
2918+
};
2919+
api.subscribe("drawing_event", handleDrawingEvent);
2920+
return {
2921+
flush: flushActiveBurst,
2922+
stop() {
2923+
if (stopped) throw new Error("图表删除事件保存合并器已停止");
2924+
stopped = true;
2925+
api.unsubscribe("drawing_event", handleDrawingEvent);
2926+
flushActiveBurst();
2927+
return { fullSaveCount, removeEventCount, saveRequestCount };
2928+
}
2929+
};
2930+
}
28072931
async function coalesceTradingViewDrawingSaves(api, action, {
28082932
eventDiscoveryTimeoutMs = 800,
28092933
settleQuietMs = 50,
@@ -2924,6 +3048,28 @@
29243048
}
29253049
}
29263050

3051+
// src/binance-orderbook-trade/dom/tradingview-target.js
3052+
var CHART_ROOT_SELECTOR = ".chart-widget-root";
3053+
function hasVisibleBox(element) {
3054+
if (!element?.getClientRects().length) return false;
3055+
const rect = element.getBoundingClientRect();
3056+
return rect.width > 0 && rect.height > 0;
3057+
}
3058+
function findBinanceTradingViewTarget(document2) {
3059+
const chartRoots = Array.from(document2.querySelectorAll(CHART_ROOT_SELECTOR)).filter(hasVisibleBox);
3060+
if (!chartRoots.length) return null;
3061+
if (chartRoots.length > 1) {
3062+
throw new Error(`可见图表区域数量异常:${chartRoots.length}`);
3063+
}
3064+
const chartRoot = chartRoots[0];
3065+
const tradingViewApis = Array.from(chartRoot.querySelectorAll("iframe")).map((frame) => frame.contentWindow?.tradingViewApi).filter(Boolean);
3066+
if (!tradingViewApis.length) return null;
3067+
if (tradingViewApis.length > 1) {
3068+
throw new Error(`图表接口数量异常:${tradingViewApis.length}`);
3069+
}
3070+
return { chartRoot, tradingViewApi: tradingViewApis[0] };
3071+
}
3072+
29273073
// src/binance-orderbook-trade/core/cancel-dialog-decision.js
29283074
function resolveCancelDialogDecision({
29293075
seenDialog,
@@ -3113,28 +3259,6 @@
31133259
return cells.length >= MIN_OPEN_ORDER_COLUMNS ? cells : [];
31143260
}
31153261

3116-
// src/binance-orderbook-trade/dom/tradingview-target.js
3117-
var CHART_ROOT_SELECTOR = ".chart-widget-root";
3118-
function hasVisibleBox(element) {
3119-
if (!element?.getClientRects().length) return false;
3120-
const rect = element.getBoundingClientRect();
3121-
return rect.width > 0 && rect.height > 0;
3122-
}
3123-
function findBinanceTradingViewTarget(document2) {
3124-
const chartRoots = Array.from(document2.querySelectorAll(CHART_ROOT_SELECTOR)).filter(hasVisibleBox);
3125-
if (!chartRoots.length) return null;
3126-
if (chartRoots.length > 1) {
3127-
throw new Error(`可见图表区域数量异常:${chartRoots.length}`);
3128-
}
3129-
const chartRoot = chartRoots[0];
3130-
const tradingViewApis = Array.from(chartRoot.querySelectorAll("iframe")).map((frame) => frame.contentWindow?.tradingViewApi).filter(Boolean);
3131-
if (!tradingViewApis.length) return null;
3132-
if (tradingViewApis.length > 1) {
3133-
throw new Error(`图表接口数量异常:${tradingViewApis.length}`);
3134-
}
3135-
return { chartRoot, tradingViewApi: tradingViewApis[0] };
3136-
}
3137-
31383262
// src/binance-orderbook-trade/dom/chart-orders.js
31393263
var ACTIVE_POPOVER_SELECTOR = ".bn-bubble.active";
31403264
var OPEN_ORDERS_LABEL_PATTERN = /^(?:当前委托|Open Orders)$/i;
@@ -3604,6 +3728,8 @@
36043728
const CANCEL_NO_ORDERS_FEEDBACK_MS = 600;
36053729
const CHART_ORDERS_MENU_TIMEOUT_MS = 1800;
36063730
const CHART_ORDERS_MENU_POLL_MS = 50;
3731+
const CONTINUOUS_CHART_REMOVE_SAVE_QUIET_MS = 120;
3732+
const CONTINUOUS_CHART_REMOVE_SAVE_MAX_WAIT_MS = 400;
36073733
const LADDER_MAKER_BUFFER_LEVELS = 1;
36083734
const LADDER_REPRICE_PAUSE_EVERY_ATTEMPTS = 5;
36093735
const LADDER_REPRICE_PAUSE_MS = 3e3;
@@ -3702,6 +3828,7 @@
37023828
let singleOrderTask = null;
37033829
let ladderAbortController = null;
37043830
let continuousLadderAbortController = null;
3831+
let continuousChartSaveCoalescer = null;
37053832
let activeLadderActionType = null;
37063833
let activeContinuousLadderActionType = null;
37073834
let activeContinuousLadderProgress = null;
@@ -5801,8 +5928,13 @@
58015928
);
58025929
}
58035930
const observationRoot = inputs.root;
5931+
const resolveSynchronizedTradeInputs = createTradeInputResolver(document, {
5932+
initialRoot: observationRoot,
5933+
panelId: PANEL_ID,
5934+
isVisibleElement
5935+
});
58045936
const readTradeState = createTradeInputStateReader({
5805-
resolveInputs: findTradeInputs,
5937+
resolveInputs: resolveSynchronizedTradeInputs,
58065938
expectedPrice,
58075939
expectedQty,
58085940
includePrice: true,
@@ -6500,6 +6632,32 @@
65006632
formatContinuousLadderProgress(label, phase, progress, titleReason)
65016633
);
65026634
}
6635+
function startContinuousChartSaveCoalescing() {
6636+
try {
6637+
const target = findBinanceTradingViewTarget(document);
6638+
if (!target) return null;
6639+
return createTradingViewRemoveSaveBurstController(target.tradingViewApi, {
6640+
settleQuietMs: CONTINUOUS_CHART_REMOVE_SAVE_QUIET_MS,
6641+
maxWaitMs: CONTINUOUS_CHART_REMOVE_SAVE_MAX_WAIT_MS
6642+
});
6643+
} catch (error) {
6644+
warn("未启用连续交易图表保存合并:", error?.message || error);
6645+
return null;
6646+
}
6647+
}
6648+
function stopContinuousChartSaveCoalescing(coalescer) {
6649+
if (!coalescer) return;
6650+
try {
6651+
const result = coalescer.stop();
6652+
log("连续交易图表保存已合并", result);
6653+
} catch (error) {
6654+
err("连续交易图表保存合并清理失败:", error);
6655+
} finally {
6656+
if (continuousChartSaveCoalescer === coalescer) {
6657+
continuousChartSaveCoalescer = null;
6658+
}
6659+
}
6660+
}
65036661
async function startContinuousLadder(actionType) {
65046662
const spec = getLadderActionSpec2(actionType);
65056663
if (!spec || spec.mode !== "CLOSE") return startLadder(actionType);
@@ -6516,7 +6674,9 @@
65166674
const abortController = new AbortController();
65176675
const continuousProgress = createContinuousLadderProgress();
65186676
const positionCheckState = { checkedAt: Date.now(), retryAt: 0 };
6677+
const chartSaveCoalescer = startContinuousChartSaveCoalescing();
65196678
continuousLadderAbortController = abortController;
6679+
continuousChartSaveCoalescer = chartSaveCoalescer;
65206680
activeContinuousLadderActionType = actionType;
65216681
activeContinuousLadderProgress = continuousProgress;
65226682
const executionTask = (async () => {
@@ -6616,6 +6776,7 @@
66166776
);
66176777
return { status: "failed", error: e };
66186778
}).finally(() => {
6779+
stopContinuousChartSaveCoalescing(chartSaveCoalescer);
66196780
if (continuousLadderAbortController === abortController) {
66206781
continuousLadderAbortController = null;
66216782
}
@@ -10483,6 +10644,11 @@
1048310644
scheduleChartOrdersRecovery();
1048410645
document.addEventListener("visibilitychange", () => {
1048510646
if (document.hidden) {
10647+
try {
10648+
continuousChartSaveCoalescer?.flush();
10649+
} catch (error) {
10650+
err("页面隐藏前图表保存刷新失败:", error);
10651+
}
1048610652
stopTradingTimers();
1048710653
stopRouteWatcher();
1048810654
return;

0 commit comments

Comments
 (0)