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
16 changes: 15 additions & 1 deletion docs/binance-strategy29-bollinger-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ already-loaded native chart candles. The optional summary reads the authenticate
loopback observer gateway; it does not call Binance market-data or account APIs,
submit orders, rotate hidden charts, or add remote events as chart drawings.

Install Strategy29 0.2.1 with orderbook 2.7.199 or later, or use it alone.
Install Strategy29 0.2.2 with orderbook 2.7.199 or later, or use it alone.
Do not combine it with the embedded observer in orderbook 2.7.198.
After updating/disabling the old script, reload the page. An embedded observer
is an explicit conflict: Strategy29 stops and displays an upgrade/reload notice.
Expand All @@ -35,6 +35,20 @@ BFCache pagehide pause it; visibility/pageshow resumes it. Permanent disposal
removes its listeners, aborts an in-flight summary request, and invalidates
pending drawing work. Non-trading routes perform no candle exports or gateway
requests.
The read-only diagnostics retain one `lastLocalFailure` after local fatal cleanup
or stop. It records the export/reconcile/detect/render stage, thrown value type,
bounded name (64 characters) and message (512 characters), route, interval and
pre-cleanup counts. String rejections supply the message directly; missing string
name/message fields are null, and arbitrary rejected objects are not serialized.
Each field is read once. A throwing host accessor leaves that field null and adds
its name to `unreadableFields`; this diagnostic boundary cannot replace the original
monitor failure with a property-read failure.
If a host Proxy throws during rejection classification, the monitor stops that
context and records `classificationFailed: true`; normal fatal errors record false.
The frozen detector/core source and its direct invalid-context assertion remain unchanged.
It describes the last fatal event, not necessarily the active context. It does not
store stack traces, candles, requests or gateway credentials, and is never persisted
or sent to the remote service. Recoverable snapshot races leave it unchanged.
Independent instances of each bundle share the same controller regardless of
load order. Strategy27 does not participate in this protocol.

Expand Down
58 changes: 52 additions & 6 deletions scripts/binance-strategy29-bollinger.user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// @namespace binance.strategy29.bollinger
// @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 0.2.1
// @version 0.2.2
// @author jackhai9
// @description Native Bollinger/SMA60 markers with an optional read-only cross-timeframe summary
// @match https://www.binance.com/*/futures/*
Expand Down Expand Up @@ -1473,6 +1473,7 @@
let bearishBollingerAlertTask = null;
let bearishBollingerAlertContext = null;
let bollingerIntervalSession = null;
let lastLocalFailure = null;
const retiredBollingerLayers = /* @__PURE__ */ new Set();
function clearBearishBollingerAlertContext() {
if (bearishBollingerAlertContext) {
Expand Down Expand Up @@ -1552,19 +1553,31 @@
context.cleanupPending = false;
}
if (context.failed || bearishBollingerAlertTask) return;
let stage = "export";
const task = (async () => {
const bars = await exportClosedTradingViewBars(context.target, context.intervalSession);
if (!bars || !isBearishBollingerAlertContextCurrent(context)) return;
if (bars.length === 0) return;
stage = "reconcile";
const result = await reconcileBearishBollingerAlertWindow({
bars,
cachedWindowKey: context.lastProcessedClosedBarsWindowKey,
cachedContentSnapshot: context.lastProcessedClosedBarsContentSnapshot,
cachedSignals: context.lastProcessedSignals,
detectSignals: detectBollingerSignals,
renderSignals: (signals) => context.layer.render(signals, {
isCurrent: () => isBearishBollingerAlertContextCurrent(context)
})
detectSignals: (bars2) => {
stage = "detect";
const signals = detectBollingerSignals(bars2);
stage = "reconcile";
return signals;
},
renderSignals: async (signals) => {
stage = "render";
const rendered = await context.layer.render(signals, {
isCurrent: () => isBearishBollingerAlertContextCurrent(context)
});
stage = "reconcile";
return rendered;
}
});
if (result.rendered && isBearishBollingerAlertContextCurrent(context)) {
context.lastProcessedClosedBarsWindowKey = result.closedBarsWindowKey;
Expand All @@ -1575,11 +1588,43 @@
bearishBollingerAlertTask = task;
task.catch((error) => {
if (bearishBollingerAlertContext !== context || context.intervalSession !== bollingerIntervalSession?.session || context.intervalRevision !== context.intervalSession.revision) return;
const failureKind = applyBollingerAlertTaskFailure(context, error);
let failureKind;
let classificationFailed = false;
try {
failureKind = applyBollingerAlertTaskFailure(context, error);
} catch {
classificationFailed = true;
context.failed = true;
context.cleanupPending = true;
failureKind = "fatal";
}
if (failureKind === "retry") {
warn("布林带形态预警本轮快照不一致,保留现有标记并等待下一次采样:", error);
return;
}
const details = { name: null, message: null };
const unreadableFields = [];
for (const [key, limit] of [["name", 64], ["message", 512]]) {
try {
const value = key === "message" && typeof error === "string" ? error : error?.[key];
details[key] = typeof value === "string" ? value.slice(0, limit) : null;
} catch {
unreadableFields.push(key);
}
}
lastLocalFailure = Object.freeze({
thrownType: error === null ? "null" : typeof error,
classificationFailed,
...details,
unreadableFields: Object.freeze(unreadableFields),
stage,
routeSymbol: context.routeSymbol,
resolution: context.resolution,
cachedSignalCount: context.lastProcessedSignals === null ? null : context.lastProcessedSignals.length,
layerSizeBeforeCleanup: context.layer.size,
sessionRevision: context.intervalSession.revision,
contextIntervalRevision: context.intervalRevision
});
err("布林带形态预警已停止:", error);
}).finally(() => {
if (bearishBollingerAlertTask === task) bearishBollingerAlertTask = null;
Expand All @@ -1598,6 +1643,7 @@
taskPending: bearishBollingerAlertTask !== null,
contextPresent: context !== null,
failed: context ? context.failed : null,
lastLocalFailure,
cleanupPending: context ? context.cleanupPending : null,
cachedSignalCount: context?.lastProcessedSignals === null || !context ? null : context.lastProcessedSignals.length,
layerSize: context ? context.layer.size : null,
Expand Down
2 changes: 1 addition & 1 deletion src/binance-strategy29-bollinger/index.user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// @namespace binance.strategy29.bollinger
// @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 0.2.1
// @version 0.2.2
// @author jackhai9
// @description Native Bollinger/SMA60 markers with an optional read-only cross-timeframe summary
// @match https://www.binance.com/*/futures/*
Expand Down
59 changes: 54 additions & 5 deletions src/binance-strategy29-bollinger/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function createBollingerMonitor({
let bearishBollingerAlertTask = null;
let bearishBollingerAlertContext = null;
let bollingerIntervalSession = null;
let lastLocalFailure = null;
const retiredBollingerLayers = new Set();

function clearBearishBollingerAlertContext() {
Expand Down Expand Up @@ -129,19 +130,31 @@ export function createBollingerMonitor({
context.cleanupPending = false;
}
if (context.failed || bearishBollingerAlertTask) return;
let stage = 'export';
const task = (async () => {
const bars = await exportClosedTradingViewBars(context.target, context.intervalSession);
if (!bars || !isBearishBollingerAlertContextCurrent(context)) return;
if (bars.length === 0) return;
stage = 'reconcile';
const result = await reconcileBearishBollingerAlertWindow({
bars,
cachedWindowKey: context.lastProcessedClosedBarsWindowKey,
cachedContentSnapshot: context.lastProcessedClosedBarsContentSnapshot,
cachedSignals: context.lastProcessedSignals,
detectSignals: detectBollingerSignals,
renderSignals: (signals) => context.layer.render(signals, {
isCurrent: () => isBearishBollingerAlertContextCurrent(context),
}),
detectSignals: (bars) => {
stage = 'detect';
const signals = detectBollingerSignals(bars);
stage = 'reconcile';
return signals;
},
renderSignals: async (signals) => {
stage = 'render';
const rendered = await context.layer.render(signals, {
isCurrent: () => isBearishBollingerAlertContextCurrent(context),
});
stage = 'reconcile';
return rendered;
},
});
if (result.rendered && isBearishBollingerAlertContextCurrent(context)) {
context.lastProcessedClosedBarsWindowKey = result.closedBarsWindowKey;
Expand All @@ -156,14 +169,49 @@ export function createBollingerMonitor({
|| context.intervalSession !== bollingerIntervalSession?.session
|| context.intervalRevision !== context.intervalSession.revision
) return;
const failureKind = applyBollingerAlertTaskFailure(context, error);
let failureKind;
let classificationFailed = false;
try {
failureKind = applyBollingerAlertTaskFailure(context, error);
} catch {
// A revoked host Proxy can throw during instanceof; an unclassifiable rejection must stop this context.
classificationFailed = true;
context.failed = true;
context.cleanupPending = true;
failureKind = 'fatal';
}
if (failureKind === 'retry') {
// TradingView can expose one feed-update race through exportData(). Keep the
// already-rendered layer and retry the next poll instead of turning a transient
// snapshot into a permanent failed context.
warn('布林带形态预警本轮快照不一致,保留现有标记并等待下一次采样:', error);
return;
}
/** Preserve host rejection types without serializing arbitrary objects or stacks. */
const details = { name: null, message: null };
const unreadableFields = [];
for (const [key, limit] of [['name', 64], ['message', 512]]) {
Comment thread
jackhai9 marked this conversation as resolved.
try {
const value = key === 'message' && typeof error === 'string' ? error : error?.[key];
details[key] = typeof value === 'string' ? value.slice(0, limit) : null;
} catch {
// Host rejection accessors may throw; preserve that fact without replacing the original failure.
unreadableFields.push(key);
}
}
lastLocalFailure = Object.freeze({
thrownType: error === null ? 'null' : typeof error,
classificationFailed,
...details,
unreadableFields: Object.freeze(unreadableFields),
stage,
routeSymbol: context.routeSymbol,
resolution: context.resolution,
cachedSignalCount: context.lastProcessedSignals === null ? null : context.lastProcessedSignals.length,
layerSizeBeforeCleanup: context.layer.size,
sessionRevision: context.intervalSession.revision,
contextIntervalRevision: context.intervalRevision,
});
err('布林带形态预警已停止:', error);
}).finally(() => {
if (bearishBollingerAlertTask === task) bearishBollingerAlertTask = null;
Expand All @@ -186,6 +234,7 @@ export function createBollingerMonitor({
taskPending: bearishBollingerAlertTask !== null,
contextPresent: context !== null,
failed: context ? context.failed : null,
lastLocalFailure,
cleanupPending: context ? context.cleanupPending : null,
cachedSignalCount: context?.lastProcessedSignals === null || !context
? null : context.lastProcessedSignals.length,
Expand Down
Loading
Loading