Complete legacy operator UI and playout migration
This commit is contained in:
146
Web/candle-options-workflow.js
Normal file
146
Web/candle-options-workflow.js
Normal file
@@ -0,0 +1,146 @@
|
||||
(function (root, factory) {
|
||||
"use strict";
|
||||
|
||||
const api = Object.freeze(factory());
|
||||
if (typeof module === "object" && module.exports) module.exports = api;
|
||||
if (root) root.MbnCandleOptionsWorkflow = api;
|
||||
})(typeof globalThis === "object" ? globalThis : this, function () {
|
||||
"use strict";
|
||||
|
||||
const CANDLE_BUILDER_KEY = "s8010";
|
||||
const supportedSources = Object.freeze([
|
||||
"legacy-stock-workflow",
|
||||
"legacy-fixed-workflow",
|
||||
"legacy-industry-workflow",
|
||||
"legacy-overseas-workflow",
|
||||
"legacy-trading-halt-workflow"
|
||||
]);
|
||||
|
||||
function normalizeOptions(ma5, ma20) {
|
||||
if (typeof ma5 !== "boolean" || typeof ma20 !== "boolean") {
|
||||
throw new TypeError("Global moving-average flags must be boolean values.");
|
||||
}
|
||||
return Object.freeze({ ma5, ma20 });
|
||||
}
|
||||
|
||||
function expectedGraphicType(ma5, ma20) {
|
||||
const options = normalizeOptions(ma5, ma20);
|
||||
const flags = [];
|
||||
if (options.ma5) flags.push("MA5");
|
||||
if (options.ma20) flags.push("MA20");
|
||||
return flags.join(",");
|
||||
}
|
||||
|
||||
function hasAdapters(value) {
|
||||
return value && typeof value === "object" && !Array.isArray(value) &&
|
||||
value.stock && value.fixed && value.industry && value.overseas && value.tradingHalt;
|
||||
}
|
||||
|
||||
function recreateCandleEntry(value, ma5, ma20, adapters, now = new Date()) {
|
||||
const movingAverages = normalizeOptions(ma5, ma20);
|
||||
if (!value || typeof value !== "object" || Array.isArray(value) ||
|
||||
value.builderKey !== CANDLE_BUILDER_KEY || typeof value.enabled !== "boolean" ||
|
||||
!hasAdapters(adapters)) return null;
|
||||
const operator = value.operator;
|
||||
if (!operator || typeof operator !== "object" || !supportedSources.includes(operator.source)) return null;
|
||||
const options = {
|
||||
id: value.id,
|
||||
fadeDuration: value.fadeDuration,
|
||||
ma5: movingAverages.ma5,
|
||||
ma20: movingAverages.ma20
|
||||
};
|
||||
let recreated = null;
|
||||
try {
|
||||
switch (operator.source) {
|
||||
case "legacy-stock-workflow": {
|
||||
const cut = adapters.stock.getStockCut(operator.cutId);
|
||||
if (cut?.kind !== "candle") return null;
|
||||
recreated = adapters.stock.createStockPlaylistEntry(operator.stock, cut.id, options);
|
||||
break;
|
||||
}
|
||||
case "legacy-fixed-workflow": {
|
||||
const action = adapters.fixed.actions.find(item => item.id === operator.actionId);
|
||||
if (action?.builderKey !== CANDLE_BUILDER_KEY || action.available !== true) return null;
|
||||
recreated = adapters.fixed.createFixedPlaylistEntry(action.id, { ...options, now });
|
||||
break;
|
||||
}
|
||||
case "legacy-industry-workflow": {
|
||||
const cut = adapters.industry.cuts.find(item => item.id === operator.cutId);
|
||||
if (cut?.kind !== "candle") return null;
|
||||
recreated = adapters.industry.createIndustryPlaylistEntry(operator.market, cut.id, {
|
||||
...options,
|
||||
selected: operator.selected,
|
||||
pair: operator.pair,
|
||||
now
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "legacy-overseas-workflow": {
|
||||
const action = adapters.overseas.stockActions.find(item => item.id === operator.actionId);
|
||||
if (operator.kind !== "stock" || action?.kind !== "candle") return null;
|
||||
recreated = adapters.overseas.createOverseasStockPlaylistEntry(
|
||||
operator.identity,
|
||||
action.id,
|
||||
options);
|
||||
break;
|
||||
}
|
||||
case "legacy-trading-halt-workflow": {
|
||||
const action = adapters.tradingHalt.getAction(operator.actionId);
|
||||
if (action?.kind !== "candle") return null;
|
||||
recreated = adapters.tradingHalt.createTradingHaltPlaylistEntry(
|
||||
operator.tradingHalt,
|
||||
action.id,
|
||||
options);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
return recreated ? { ...recreated, enabled: value.enabled } : null;
|
||||
}
|
||||
|
||||
function entryAlreadyMatches(value, ma5, ma20) {
|
||||
const graphicType = expectedGraphicType(ma5, ma20);
|
||||
return value?.selection?.graphicType === graphicType &&
|
||||
value?.graphicType === graphicType &&
|
||||
value?.operator?.movingAverages?.ma5 === ma5 &&
|
||||
value?.operator?.movingAverages?.ma20 === ma20;
|
||||
}
|
||||
|
||||
function synchronizePlaylist(values, ma5, ma20, adapters, now = new Date()) {
|
||||
normalizeOptions(ma5, ma20);
|
||||
if (!Array.isArray(values)) throw new TypeError("A playlist array is required.");
|
||||
const invalidIds = [];
|
||||
let changed = false;
|
||||
const playlist = values.map(value => {
|
||||
if (value?.builderKey !== CANDLE_BUILDER_KEY) return value;
|
||||
if (entryAlreadyMatches(value, ma5, ma20)) return value;
|
||||
const recreated = recreateCandleEntry(value, ma5, ma20, adapters, now);
|
||||
if (!recreated) {
|
||||
invalidIds.push(typeof value?.id === "string" ? value.id : "");
|
||||
return value;
|
||||
}
|
||||
changed = true;
|
||||
return recreated;
|
||||
});
|
||||
return Object.freeze({
|
||||
playlist: Object.freeze(playlist),
|
||||
changed,
|
||||
valid: invalidIds.length === 0,
|
||||
invalidIds: Object.freeze(invalidIds)
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
candleBuilderKey: CANDLE_BUILDER_KEY,
|
||||
supportedSources,
|
||||
normalizeOptions,
|
||||
expectedGraphicType,
|
||||
recreateCandleEntry,
|
||||
synchronizePlaylist
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user