Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions scripts/binance-orderbook-trade.user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// @namespace binance.orderbook.trade
// @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
// @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
// @version 2.7.183
// @version 2.7.184
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down Expand Up @@ -3797,7 +3797,9 @@
var DEPTH_PROFILE_ID = "jh-binance-depth-profile";
var STYLE_ID = "jh-binance-depth-profile-style";
var CHART_ROOT_SELECTOR2 = ".chart-widget-root";
var PRICE_AXIS_SELECTOR = ".chart-markup-table.price-axis-container";
var PRICE_COORDINATE_SEARCH_STEPS = 13;
var GEOMETRY_TOLERANCE_PX = 1;
function hasVisibleBox2(element) {
if (!element?.getClientRects().length) return false;
const rect = element.getBoundingClientRect();
Expand Down Expand Up @@ -3832,6 +3834,15 @@
const scale = panes[0]?.getMainSourcePriceScale?.();
if (!(height > 0) || !scale) return null;
if (typeof scale.coordinateToPrice !== "function" || typeof scale.getVisiblePriceRange !== "function" || typeof scale.getMode !== "function" || typeof scale.isInverted !== "function") return null;
const viewportWidth = Number(frame.contentWindow.innerWidth);
if (!Number.isFinite(viewportWidth) || !(viewportWidth > 0)) return null;
const priceAxisRects = Array.from(
frame.contentDocument.querySelectorAll(PRICE_AXIS_SELECTOR),
(element) => element.getBoundingClientRect()
).filter((rect) => rect.width > 0 && rect.height > 0 && Math.abs(rect.top) <= GEOMETRY_TOLERANCE_PX && Math.abs(rect.height - height) <= GEOMETRY_TOLERANCE_PX && Math.abs(rect.right - viewportWidth) <= GEOMETRY_TOLERANCE_PX);
if (priceAxisRects.length !== 1) return null;
const rightInset = Number(priceAxisRects[0].width);
if (!Number.isFinite(rightInset) || !(rightInset > 0)) return null;
const visibleRange = scale.getVisiblePriceRange();
const mode = scale.getMode();
const inverted = scale.isInverted();
Expand All @@ -3845,6 +3856,7 @@
return {
top: 0,
height,
rightInset,
minPrice,
maxPrice,
mode,
Expand Down Expand Up @@ -3877,7 +3889,7 @@
position: absolute;
z-index: 3;
top: 0;
right: 72px;
right: 0;
bottom: auto;
height: 0;
width: 132px;
Expand Down Expand Up @@ -3997,8 +4009,10 @@
function setDepthProfileGeometry(root, geometry) {
const top = `${geometry.top}px`;
const height = `${geometry.height}px`;
const right = `${geometry.rightInset}px`;
if (root.style.top !== top) root.style.top = top;
if (root.style.height !== height) root.style.height = height;
if (root.style.right !== right) root.style.right = right;
}
function bucketVisibleLevels(levels, geometry) {
const lastRow = Math.max(0, Math.ceil(geometry.height) - 1);
Expand Down
23 changes: 22 additions & 1 deletion src/binance-orderbook-trade/dom/depth-profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ export const DEPTH_PROFILE_ID = 'jh-binance-depth-profile';

const STYLE_ID = 'jh-binance-depth-profile-style';
const CHART_ROOT_SELECTOR = '.chart-widget-root';
const PRICE_AXIS_SELECTOR = '.chart-markup-table.price-axis-container';
const PRICE_COORDINATE_SEARCH_STEPS = 13;
const GEOMETRY_TOLERANCE_PX = 1;

function hasVisibleBox(element) {
if (!element?.getClientRects().length) return false;
Expand Down Expand Up @@ -49,6 +51,22 @@ export function getTradingViewDepthProfileGeometry(frame) {
|| typeof scale.isInverted !== 'function'
) return null;

const viewportWidth = Number(frame.contentWindow.innerWidth);
if (!Number.isFinite(viewportWidth) || !(viewportWidth > 0)) return null;
const priceAxisRects = Array.from(
frame.contentDocument.querySelectorAll(PRICE_AXIS_SELECTOR),
(element) => element.getBoundingClientRect(),
).filter((rect) => (
rect.width > 0
&& rect.height > 0
&& Math.abs(rect.top) <= GEOMETRY_TOLERANCE_PX
&& Math.abs(rect.height - height) <= GEOMETRY_TOLERANCE_PX
&& Math.abs(rect.right - viewportWidth) <= GEOMETRY_TOLERANCE_PX
));
if (priceAxisRects.length !== 1) return null;
const rightInset = Number(priceAxisRects[0].width);
if (!Number.isFinite(rightInset) || !(rightInset > 0)) return null;

const visibleRange = scale.getVisiblePriceRange();
const mode = scale.getMode();
const inverted = scale.isInverted();
Expand Down Expand Up @@ -78,6 +96,7 @@ export function getTradingViewDepthProfileGeometry(frame) {
return {
top: 0,
height,
rightInset,
minPrice,
maxPrice,
mode,
Expand Down Expand Up @@ -111,7 +130,7 @@ function installStyle(document) {
position: absolute;
z-index: 3;
top: 0;
right: 72px;
right: 0;
bottom: auto;
height: 0;
width: 132px;
Expand Down Expand Up @@ -239,8 +258,10 @@ export function setDepthProfileViewState(root, {
export function setDepthProfileGeometry(root, geometry) {
const top = `${geometry.top}px`;
const height = `${geometry.height}px`;
const right = `${geometry.rightInset}px`;
if (root.style.top !== top) root.style.top = top;
if (root.style.height !== height) root.style.height = height;
if (root.style.right !== right) root.style.right = right;
}

function bucketVisibleLevels(levels, geometry) {
Expand Down
2 changes: 1 addition & 1 deletion src/binance-orderbook-trade/index.user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// @namespace binance.orderbook.trade
// @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
// @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
// @version 2.7.183
// @version 2.7.184
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
Expand Down
48 changes: 45 additions & 3 deletions test/dom/binance-orderbook-trade/depth-profile.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,27 @@ function createChartDom({ hiddenFrame = false } = {}) {

function installTradingViewApi(frame, {
height = 200,
viewportWidth = 1200,
priceAxisWidth = 88,
mode = 0,
inverted = false,
coordinateToPrice = (coordinate) => 110 - coordinate / 10,
} = {}) {
Object.defineProperty(frame.contentWindow, 'innerWidth', {
configurable: true,
value: viewportWidth,
});
const priceAxis = frame.contentDocument.createElement('div');
priceAxis.className = 'chart-markup-table price-axis-container';
priceAxis.getBoundingClientRect = () => ({
width: priceAxisWidth,
height,
left: viewportWidth - priceAxisWidth,
right: viewportWidth,
top: 0,
bottom: height,
});
frame.contentDocument.body.appendChild(priceAxis);
const scale = {
coordinateToPrice,
getMode: () => mode,
Expand Down Expand Up @@ -80,6 +97,7 @@ test('maps prices through the active TradingView main-pane scale', () => {

assert.equal(geometry.top, 0);
assert.equal(geometry.height, 200);
assert.equal(geometry.rightInset, 88);
assert.equal(geometry.minPrice, 90);
assert.equal(geometry.maxPrice, 110);
assert.ok(Math.abs(geometry.priceToCoordinate(100) - 100) < 0.02);
Expand Down Expand Up @@ -129,6 +147,29 @@ test('fails closed when TradingView returns a non-monotonic price transform', ()
assert.equal(getTradingViewDepthProfileGeometry(frame), null);
});

test('fails closed when the TradingView main-pane price axis is missing or ambiguous', () => {
const missing = createChartDom();
const missingFrame = missing.window.document.querySelector('iframe');
installTradingViewApi(missingFrame);
missingFrame.contentDocument.querySelector('.price-axis-container').remove();
assert.equal(getTradingViewDepthProfileGeometry(missingFrame), null);

const ambiguous = createChartDom();
const ambiguousFrame = ambiguous.window.document.querySelector('iframe');
installTradingViewApi(ambiguousFrame);
const duplicate = ambiguousFrame.contentDocument.querySelector('.price-axis-container').cloneNode();
duplicate.getBoundingClientRect = () => ({
width: 88,
height: 200,
left: 1112,
right: 1200,
top: 0,
bottom: 200,
});
ambiguousFrame.contentDocument.body.appendChild(duplicate);
assert.equal(getTradingViewDepthProfileGeometry(ambiguousFrame), null);
});

test('rejects ambiguous visible chart roots', () => {
const first = createChartDom();
const secondRoot = first.window.document.querySelector('.chart-widget-root').cloneNode(true);
Expand Down Expand Up @@ -297,9 +338,10 @@ test('updates root geometry without rewriting unchanged styles', () => {
const { host } = findDepthProfileHost(document);
const root = ensureDepthProfileView(document, host, { onToggle: () => {} });

setDepthProfileGeometry(root, { top: 4, height: 320 });
setDepthProfileGeometry(root, { top: 4, height: 320, rightInset: 88 });
assert.equal(root.style.top, '4px');
assert.equal(root.style.height, '320px');
setDepthProfileGeometry(root, { top: 4, height: 320 });
assert.equal(root.getAttribute('style'), 'top: 4px; height: 320px;');
assert.equal(root.style.right, '88px');
setDepthProfileGeometry(root, { top: 4, height: 320, rightInset: 88 });
assert.equal(root.getAttribute('style'), 'top: 4px; height: 320px; right: 88px;');
});
Loading