diff --git a/archived_code/test_DataCache.py b/archived_code/test_DataCache.py index 46f3489..26c6333 100644 --- a/archived_code/test_DataCache.py +++ b/archived_code/test_DataCache.py @@ -1,27 +1,222 @@ """ - { - "type": "strategy", - "statements": [ - { - "type": "trade_action", - "trade_type": "buy", - "inputs": { - "size": { - "type": "sqrt", - "inputs": { - "number": { - "type": "power", - "inputs": { - "base": 2, - "exponent": 3 + "name": "fff", + "strategy_json": { + "type": "strategy", + "statements": [ + { + "type": "execute_if", + "inputs": { + "CONDITION": { + "type": "comparison", + "operator": "<", + "inputs": { + "LEFT": { + "type": "risk_ratio", + "inputs": {} + }, + "RIGHT": { + "type": "dynamic_value", + "values": [ + { + "type": "current_balance", + "inputs": {} + } + ] } } } + }, + "statements": { + "DO": [ + { + "type": "max_position_size", + "inputs": { + "MAX_SIZE": { + "type": "available_balance", + "inputs": {} + } + }, + "next": { + "type": "execute_if", + "inputs": { + "CONDITION": { + "type": "is_false", + "inputs": { + "condition": { + "type": "order_status", + "inputs": { + "order_name": "order_name", + "status": "filled" + } + } + } + } + }, + "statements": { + "DO": [ + { + "type": "execute_if", + "inputs": { + "CONDITION": { + "type": "comparison", + "operator": ">", + "inputs": { + "LEFT": { + "type": "dynamic_value", + "values": [ + { + "type": "last_candle_value", + "inputs": { + "source": {}, + "candle_part": "open" + } + } + ] + }, + "RIGHT": { + "type": "dynamic_value", + "values": [ + 44 + ] + } + } + } + }, + "statements": { + "DO": [ + { + "type": "trade_action", + "trade_type": "buy", + "inputs": { + "size": { + "type": "math_operation", + "inputs": { + "operator": "divide", + "left_operand": { + "type": "starting_balance", + "inputs": {} + }, + "right_operand": 15 + } + } + }, + "trade_options": [ + { + "type": "name_order", + "inputs": { + "order_name": "order_name" + } + }, + { + "type": "target_market", + "inputs": { + "time_frame": "15m", + "exchange": "binance", + "symbol": "ETH/BTC" + } + } + ] + } + ] + } + } + ] + } + } + }, + { + "type": "execute_if", + "inputs": { + "CONDITION": { + "type": "is_false", + "inputs": { + "condition": { + "type": "order_status", + "inputs": { + "order_name": "order_name", + "status": "filled" + } + } + } + } + }, + "statements": { + "DO": [ + { + "type": "execute_if", + "inputs": { + "CONDITION": { + "type": "comparison", + "operator": ">", + "inputs": { + "LEFT": { + "type": "dynamic_value", + "values": [ + { + "type": "last_candle_value", + "inputs": { + "source": {}, + "candle_part": "open" + } + } + ] + }, + "RIGHT": { + "type": "dynamic_value", + "values": [ + 44 + ] + } + } + } + }, + "statements": { + "DO": [ + { + "type": "trade_action", + "trade_type": "buy", + "inputs": { + "size": { + "type": "math_operation", + "inputs": { + "operator": "divide", + "left_operand": { + "type": "starting_balance", + "inputs": {} + }, + "right_operand": 15 + } + } + }, + "trade_options": [ + { + "type": "name_order", + "inputs": { + "order_name": "order_name" + } + }, + { + "type": "target_market", + "inputs": { + "time_frame": "15m", + "exchange": "binance", + "symbol": "ETH/BTC" + } + } + ] + } + ] + } + } + ] + } + } + ] } } - } - ] + ] + }, + "workspace": "Execute the enclosed statements if the condition is true.<Compare two values using operators like >, <, ==.Calculate and retrieve the current risk ratio.Retrieve the current balance of the strategy.Set a maximum limit on the number of positions the strategy can hold.Retrieve the overall available (liquid) balance of the user.Execute the enclosed statements if the condition is true.Check if a condition is false.order_namepartialGet the status of a named order.Execute the enclosed statements if the condition is true.>Compare two values using operators like >, <, ==.openRetrieve a specific part (Open, High, Low, Close) of the last candle from a given source.44Enter a numerical value. Chain multiple for a list.buyExecute a Buy/Sell trade based on a condition with specified size and options.DIVIDEPerform basic arithmetic operations between two values.Retrieve the starting balance of the strategy.15Enter a numerical value. Chain multiple for a list.order_nameAssign a custom name to the current order.15mbinanceETH/BTCChoose the target market for posting orders." } - """ \ No newline at end of file diff --git a/markdown/App/json_base_generator.md b/markdown/App/json_base_generator.md new file mode 100644 index 0000000..66fb49f --- /dev/null +++ b/markdown/App/json_base_generator.md @@ -0,0 +1,120 @@ +### general: +```plantuml +@startuml +start +:User creates blocks in Blockly workspace; +:User triggers code generation; +:Blockly.JSON.blockToCode(block) is called; +if (Generator function exists for block type?) then (Yes) + :Call the generator function for the block type; + if (Valid JSON object returned?) then (Yes) + :Proceed to process inputs and statements; + else (No) + :Log error about invalid JSON object; + stop + endif +else (No) + :Log error about missing generator function; + stop +endif +:Process inputs and statements recursively; +:Handle 'next' blocks recursively; +:Assemble final JSON representation; +stop +@enduml + + +``` + +### blockToCode: +```plantuml +@startuml +start +:Input block; +if (Generator function exists for block.type?) then (Yes) + :Call generator function to get JSON; + if (JSON is valid?) then (Yes) + if (skipAdditionalParsing flag is set?) then (Yes) + :Return JSON as is; + else (No) + :Call _blockToJson for further processing; + endif + else (No) + :Log error about invalid JSON; + stop + endif +else (No) + :Log error about missing generator function; + stop +endif +stop +@enduml + +``` +### _blockToJson: +```plantuml +@startuml +start +:Check if block is null; +if (block is null?) then (Yes) + :Return null; + stop +endif + +:Call _calculateNestingDepth(block, currentDepth); +if (Depth exceeds maxDepth?) then (Yes) + :Throw Error "Maximum block nesting depth exceeded."; + stop +endif + +if (Custom JSON generator exists for block.type?) then (Yes) + :Call custom generator to get json; + if (json is not valid?) then (Yes) + :Log error; + :Return null; + stop + endif + + if (json.skipAdditionalParsing is true?) then (Yes) + :Remove skipAdditionalParsing from json; + if (json.type is not defined?) then (Yes) + :Return null; + else + :Return json; + stop + endif + endif +else (No) + :Create default json object with type, fields, inputs, statements; + :Capture all fields in the block; +endif + +:Initialize json.inputs and json.statements if not already present; +:Initialize index = 0; +while (index < length of block.inputList) is (Yes) + :input = block.inputList[index]; + if (input.connection and input.connection.targetBlock() exist?) then (Yes) + :Get targetBlock and targetJson; + :Call _blockToJson(targetBlock, currentDepth + 1); + if (input.type == INPUT_VALUE) then (Yes) + :Add targetJson to json.inputs[input.name]; + else if (input.type == NEXT_STATEMENT) then (Yes) + :Add targetJson to json.statements[input.name] array; + endif + endif + :Increment index; +endwhile + +:Handle 'next' connection; +if (block has next block?) then (Yes) + :Call _blockToJson(block.getNextBlock(), currentDepth); + :Assign result to json.next; +endif + +:Return json; +stop +@enduml + + + +``` \ No newline at end of file diff --git a/markdown/StratWorkspaceManager.md b/markdown/StratWorkspaceManager.md new file mode 100644 index 0000000..33a7d3b --- /dev/null +++ b/markdown/StratWorkspaceManager.md @@ -0,0 +1,254 @@ +### StratWorkspaceManager: +```plantuml +@startuml +start +:Initialize StratWorkspaceManager; +note right +- workspace = null +- blocksDefined = false +- MAX_TOP_LEVEL_BLOCKS = 10 +end note + +if (blocklyDiv exists?) then (yes) + if (workspace exists?) then (yes) + :Dispose of existing workspace; + endif + + :Load Modules and Init Workspace; + note right + Calls _loadModulesAndInitWorkspace() + end note + + :Initialize Blockly Workspace; + note right + - Set blocksDefined = true + - Inject workspace into blocklyDiv + end note +else (no) + :Log error "blocklyDiv is not loaded."; +endif + +:Adjust Workspace; +note right +Calls adjustWorkspace() +end note + +stop +@enduml + +``` +### initWorkspace: +```plantuml +@startuml +participant "StratWorkspaceManager" as SWM +participant "Document" as Doc +participant "Blockly" + +SWM -> Doc: getElementById('blocklyDiv') +alt blocklyDiv exists + SWM -> SWM: workspace.dispose() [if workspace exists] + SWM -> SWM: _loadModulesAndInitWorkspace() + SWM -> SWM: blocksDefined = true + SWM -> Blockly: inject('blocklyDiv', options) +else + SWM -> SWM: console.error("blocklyDiv is not loaded.") +end +@enduml + +``` + +### _loadModulesAndInitWorkspace: +```plantuml +@startuml +participant "StratWorkspaceManager" as SWM +participant "Document" as Doc +participant "Blockly" +@startuml +participant "StratWorkspaceManager" as SWM +participant "Custom Blocks Modules" as Modules +participant "Document" as Doc +participant "Blockly" + +SWM -> SWM: Check blocksDefined +alt blocksDefined is false + SWM -> Modules: Import and define modules + SWM -> SWM: blocksDefined = true +else + SWM -> SWM: Skip module loading +end + +SWM -> Doc: getElementById('toolbox_advanced') +alt toolbox exists + SWM -> Blockly: inject('blocklyDiv', options) + SWM -> SWM: Workspace initialized +else + SWM -> SWM: console.error("toolbox is not loaded.") +end +@enduml + +SWM -> Doc: getElementById('blocklyDiv') +alt blocklyDiv exists + SWM -> SWM: workspace.dispose() [if workspace exists] + SWM -> SWM: _loadModulesAndInitWorkspace() + SWM -> SWM: blocksDefined = true + SWM -> Blockly: inject('blocklyDiv', options) +else + SWM -> SWM: console.error("blocklyDiv is not loaded.") +end +@enduml + +``` +### adjustWorkspace: +```plantuml +@startuml +participant "StratWorkspaceManager" as SWM +participant "Document" as Doc +participant "Blockly" + +SWM -> Doc: getElementById('blocklyDiv') +alt blocklyDiv and workspace exist + SWM -> Blockly: svgResize(workspace) +else + SWM -> SWM: console.error("Cannot resize workspace.") +end +@enduml + +``` +### compileStrategyJson: +```plantuml +@startuml +start +:Check if workspace is available; +if (workspace exists?) then (yes) + :Get strategy name from name_box; + :Generate strategy JSON; + note right + Calls _generateStrategyJsonFromWorkspace() + end note + if (strategyJson is null?) then (yes) + :Log error "Failed to generate strategy JSON."; + stop + endif + :Serialize workspace to XML; + :Return JSON string containing strategy data; +else (no) + :Log error "Workspace is not available."; + stop +endif +@enduml + + +``` +### _generateStrategyJsonFromWorkspace: +```plantuml +@startuml +start +:Categorize orphaned blocks; +note right +Calls _categorizeOrphanedBlocks(workspace) +end note + +:Calculate totalTopLevelBlocks; + +if (totalTopLevelBlocks > MAX_TOP_LEVEL_BLOCKS?) then (yes) + :Log error "Too many top-level blocks."; + :Alert user; + :Return null; + stop +else (no) + :Create strategy JSON; + note right + Calls _createStrategyJson(initializationBlocks, actionBlocks) + end note + :Return strategy JSON; + stop +endif +@enduml + +``` + +### _categorizeOrphanedBlocks: +```plantuml +@startuml +start +:Get top-level blocks from workspace; + +:Initialize initializationBlocks and actionBlocks arrays; + +:Initialize index = 0; + +while (index < total number of blocks?) is (yes) + :Get block at index; + if (block.type is initialization type?) then (yes) + :Add block to initializationBlocks; + else if (block.type is action type?) then (yes) + :Add block to actionBlocks; + else + :Log warning "Block type not allowed as top-level block."; + endif + :Increment index; +endwhile + +:Return { initializationBlocks, actionBlocks }; +stop +@enduml + + + + +``` + +### _createStrategyJson: +```plantuml +@startuml +start +:Initialize empty statements array; + +:Initialize index = 0; + +while (index < number of initializationBlocks?) is (yes) + :Get block at index from initializationBlocks; + :Convert block to JSON; + :Add to statements array; + :Increment index; +endwhile + +:Reset index = 0; + +while (index < number of actionBlocks?) is (yes) + :Get block at index from actionBlocks; + :Convert block to JSON; + :Add to statements array; + :Increment index; +endwhile + +:Create strategy JSON with type 'strategy' and statements; +:Return strategy JSON; +stop +@enduml +``` + +### _loadWorkspaceFromXml: +```plantuml +@startuml +participant "StratWorkspaceManager" as SWM +participant "Blockly" +participant "Workspace" as WS + +SWM -> SWM: Check if workspace is initialized +alt workspace exists + SWM -> Blockly: textToDom(workspaceXmlText) + alt workspaceXml is valid + SWM -> WS: clear() + SWM -> Blockly: domToWorkspace(workspaceXml, workspace) + else + SWM -> SWM: console.error('Invalid workspace XML.') + SWM -> User: alert('Invalid workspace data.') + end +else + SWM -> SWM: console.error('Workspace is not initialized.') +end +@enduml + + +``` \ No newline at end of file diff --git a/markdown/strategies.md b/markdown/strategies.md new file mode 100644 index 0000000..2b84f87 --- /dev/null +++ b/markdown/strategies.md @@ -0,0 +1,74 @@ +### Strategies: +```plantuml +@startuml +class StratUIManager { + - workspaceManager + - targetEl + - formElement + -- + + initUI(targetId, formElId) + + displayForm(action, strategyData = null) + + hideForm() + + updateStrategiesHtml(strategies) + + toggleFeeInput() + + registerDeleteStrategyCallback(callback) +} + +class StratDataManager { + - strategies + -- + + fetchSavedStrategies(comms, data) + + addNewStrategy(data) + + updateStrategyData(data) + + removeStrategy(name) + + applyBatchUpdates(data) + + getAllStrategies() +} + +class StratWorkspaceManager { + - workspace + - blocksDefined + - MAX_TOP_LEVEL_BLOCKS + -- + + initWorkspace() + - loadModulesAndInitWorkspace() + + adjustWorkspace() + + compileStrategyJson() + - generateStrategyJsonFromWorkspace() + - categorizeOrphanedBlocks(workspace) + - createStrategyJson(initializationBlocks, actionBlocks) + + loadWorkspaceFromXml(workspaceXmlText) +} + +class Strategies { + - dataManager + - workspaceManager + - uiManager + - comms + - data + - _initialized + -- + + initialize(targetId, formElId, data) + + handleStrategyError(errorData) + + handleStrategies(data) + + handleStrategyCreated(data) + + handleStrategyUpdated(data) + + handleStrategyDeleted(data) + + handleUpdates(data) + + resizeWorkspace() + + generateStrategyJson() + + restoreWorkspaceFromXml(workspaceXmlText) + + submitStrategy(action) + + deleteStrategy(name) +} + +' Relationships +Strategies --> StratDataManager +Strategies --> StratWorkspaceManager +Strategies --> StratUIManager +StratUIManager --> StratWorkspaceManager + +@enduml + + +... \ No newline at end of file diff --git a/src/static/Strategies.js b/src/static/Strategies.js index 5d8be9e..c434e48 100644 --- a/src/static/Strategies.js +++ b/src/static/Strategies.js @@ -1,5 +1,6 @@ class StratUIManager { - constructor() { + constructor(workspaceManager) { + this.workspaceManager = workspaceManager; this.targetEl = null; this.formElement = null; } @@ -26,7 +27,7 @@ class StratUIManager { /** * Displays the form for creating or editing a strategy. * @param {string} action - The action to perform ('new' or 'edit'). - * @param {string|null} strategyData - The data of the strategy to edit (only applicable for 'edit' action). + * @param {object|null} strategyData - The data of the strategy to edit (only applicable for 'edit' action). */ async displayForm(action, strategyData = null) { console.log(`Opening form for action: ${action}, strategy: ${strategyData?.name}`); @@ -64,9 +65,9 @@ class StratUIManager { this.formElement.style.display = "grid"; // Initialize Blockly workspace after the form becomes visible - if (UI.strats && UI.strats.workspaceManager) { + if (UI.strats && this.workspaceManager) { try { - await UI.strats.workspaceManager.initWorkspace(); + await this.workspaceManager.initWorkspace(); console.log("Blockly workspace initialized."); } catch (error) { console.error("Failed to initialize Blockly workspace:", error); @@ -121,8 +122,11 @@ class StratUIManager { // Strategy icon const strategyIcon = document.createElement('div'); strategyIcon.className = 'strategy-icon'; + // Open the form with strategy data when clicked strategyIcon.addEventListener('click', () => { - this.displayForm('edit', strat); // Open the form with strategy data when clicked + this.displayForm('edit', strat).catch(error => { + console.error('Error displaying form:', error); + }); }); // Strategy name @@ -150,6 +154,7 @@ class StratUIManager { * Toggles the fee input field based on the state of the public checkbox. */ toggleFeeInput() { + /** @type {HTMLInputElement} */ const publicCheckbox = document.getElementById('public_checkbox'); const feeBox = document.getElementById('fee_box'); @@ -223,7 +228,7 @@ class StratDataManager { /** * Handles the deletion of a strategy. - * @param {Object} data - The name for the deleted strategy. + * @param {Object} name - The name for the deleted strategy. */ removeStrategy(name) { try { @@ -259,7 +264,7 @@ class StratWorkspaceManager { this.workspace = null; this.blocksDefined = false; this.MAX_TOP_LEVEL_BLOCKS = 10; // Set your desired limit - this.DEFAULT_MAX_NESTING_DEPTH = 5; // Set your desired default max depth + this.MAX_DEPTH = 10; // Set your desired limit } /** @@ -280,6 +285,9 @@ class StratWorkspaceManager { // Initialize custom blocks and Blockly workspace await this._loadModulesAndInitWorkspace(); + + // Set the maximum allowed nesting depth + Blockly.JSON.maxDepth = this.MAX_DEPTH; // or any desired value } @@ -291,7 +299,7 @@ class StratWorkspaceManager { jsonBaseModule.defineJsonBaseGenerator(); console.log('Defined defineJsonBaseGenerator from json_base_generator.js'); - // Define lists of generator and block files with their corresponding define functions + // Map generator and block files to the functions that define them. const generatorModules = [ { file: 'balances_generators.js', defineFunc: 'defineBalancesGenerators' }, { file: 'order_metrics_generators.js', defineFunc: 'defineOrderMetricsGenerators' }, @@ -408,7 +416,7 @@ class StratWorkspaceManager { console.error("Workspace is not available."); return null; } - + /** @type {HTMLInputElement} */ const nameElement = document.getElementById('name_box'); if (!nameElement) { console.error("Name input element (name_box) is not available."); @@ -416,9 +424,6 @@ class StratWorkspaceManager { } const strategyName = nameElement.value; - // Remove or comment out the following line as 'init' is undefined - // Blockly.JSON.init(this.workspace); - // Generate code and data representations const strategyJson = this._generateStrategyJsonFromWorkspace(); @@ -442,7 +447,7 @@ class StratWorkspaceManager { /** * Generates a JSON representation of the strategy from the workspace. * @private - * @returns {Object[]} - An array of JSON objects representing the top-level blocks. + * @returns {Object} - An array of JSON objects representing the top-level blocks. */ _generateStrategyJsonFromWorkspace() { const { initializationBlocks, actionBlocks } = this._categorizeOrphanedBlocks(this.workspace); @@ -456,15 +461,13 @@ class StratWorkspaceManager { } // Proceed with strategy JSON creation - const strategyJson = this._createStrategyJson(initializationBlocks, actionBlocks); - - return strategyJson; + return this._createStrategyJson(initializationBlocks, actionBlocks); } /** * Identify all orphaned blocks on the workspace. Categorize them into initialization blocks and action blocks. * @private - * @returns {Object[]} - an object containing an array of initializationBlocks and actionBlocks. + * @returns {Object} - an object containing an array of initializationBlocks and actionBlocks. */ _categorizeOrphanedBlocks(workspace) { const blocks = workspace.getTopBlocks(true); @@ -481,6 +484,7 @@ class StratWorkspaceManager { case 'set_variable': case 'set_flag': case 'set_leverage': + case 'max_position_size': case 'pause_strategy': case 'strategy_resume': case 'strategy_exit': @@ -525,12 +529,10 @@ class StratWorkspaceManager { }); // Create the root strategy block - const strategyJson = { - type: 'strategy', - statements: statements + return { + type: 'strategy', + statements: statements }; - - return strategyJson; } /** @@ -567,9 +569,9 @@ class StratWorkspaceManager { class Strategies { constructor() { - this.uiManager = new StratUIManager(); this.dataManager = new StratDataManager(); this.workspaceManager = new StratWorkspaceManager(); + this.uiManager = new StratUIManager(this.workspaceManager); this.comms = null; this.data = null; this._initialized = false; @@ -593,17 +595,20 @@ class Strategies { this.uiManager.initUI(targetId, formElId); if (!data || typeof data !== 'object') { - throw new Error("Invalid data object provided for initialization."); + console.error("Invalid data provided for initialization."); + return; } this.data = data; if (!this.data.user_name || typeof this.data.user_name !== 'string') { - throw new Error("Invalid user_name provided in data object."); + console.error("Invalid user_name provided in data object."); + return; } this.comms = this.data?.comms; if (!this.comms) { - throw new Error('Communications instance not provided in data.'); + console.error("Communications instance not provided in data."); + return; } // Register handlers with Comms for specific message types @@ -704,14 +709,6 @@ class Strategies { this.uiManager.updateStrategiesHtml(this.dataManager.getAllStrategies()); } - /** - * Creates the Blockly workspace using StratWorkspaceManager. - * @async - */ - async createWorkspace() { - await this.workspaceManager.initializeWorkspace(); - } - /** * Resizes the Blockly workspace using StratWorkspaceManager. */ @@ -740,8 +737,11 @@ class Strategies { * @param {string} action - Action type, either 'new' or 'edit'. */ submitStrategy(action) { + /** @type {HTMLInputElement} */ const feeBox = document.getElementById('fee_box'); + /** @type {HTMLInputElement} */ const nameBox = document.getElementById('name_box'); + /** @type {HTMLInputElement} */ const publicCheckbox = document.getElementById('public_checkbox'); if (!feeBox || !nameBox || !publicCheckbox) { diff --git a/src/static/blocks/blocks/logical_blocks.js b/src/static/blocks/blocks/logical_blocks.js index 71e295a..2c8a466 100644 --- a/src/static/blocks/blocks/logical_blocks.js +++ b/src/static/blocks/blocks/logical_blocks.js @@ -67,13 +67,13 @@ export function defineLogicalBlocks() { "args0": [ { "type": "input_value", - "name": "left", + "name": "LEFT", "check": "Boolean", "align": "RIGHT" }, { "type": "input_value", - "name": "right", + "name": "RIGHT", "check": "Boolean", "align": "LEFT" } @@ -98,13 +98,13 @@ export function defineLogicalBlocks() { "args0": [ { "type": "input_value", - "name": "left", + "name": "LEFT", "check": "Boolean", "align": "RIGHT" }, { "type": "input_value", - "name": "right", + "name": "RIGHT", "check": "Boolean", "align": "LEFT" } diff --git a/src/static/blocks/blocks/market_data_blocks.js b/src/static/blocks/blocks/market_data_blocks.js index a7ae075..1f1d302 100644 --- a/src/static/blocks/blocks/market_data_blocks.js +++ b/src/static/blocks/blocks/market_data_blocks.js @@ -166,7 +166,7 @@ export function defineMarketDataBlocks() { }, { "type": "input_value", - "name": "NEXT", + "name": "VALUES", "check": "dynamic_value", "align": "RIGHT" } diff --git a/src/static/blocks/blocks/order_metrics_blocks.js b/src/static/blocks/blocks/order_metrics_blocks.js index 558c3b7..5158eda 100644 --- a/src/static/blocks/blocks/order_metrics_blocks.js +++ b/src/static/blocks/blocks/order_metrics_blocks.js @@ -36,7 +36,7 @@ export function defineOrderMetricsBlocks() { }, { "type": "input_value", - "name": "NEXT", + "name": "VALUES", "check": "dynamic_value", "align": "LEFT" } @@ -60,7 +60,7 @@ export function defineOrderMetricsBlocks() { "args0": [ { "type": "input_value", - "name": "NEXT", + "name": "VALUES", "check": "dynamic_value", "align": "LEFT" } @@ -84,7 +84,7 @@ export function defineOrderMetricsBlocks() { "args0": [ { "type": "input_value", - "name": "NEXT", + "name": "VALUES", "check": "dynamic_value", "align": "LEFT" } diff --git a/src/static/blocks/blocks/time_metrics_blocks.js b/src/static/blocks/blocks/time_metrics_blocks.js index ea80a74..fb73c14 100644 --- a/src/static/blocks/blocks/time_metrics_blocks.js +++ b/src/static/blocks/blocks/time_metrics_blocks.js @@ -37,7 +37,7 @@ export function defineTimeMetricsBlocks() { }, { "type": "input_value", - "name": "NEXT", + "name": "VALUES", "check": "dynamic_value", "align": "LEFT" } diff --git a/src/static/blocks/blocks/trade_metrics_blocks.js b/src/static/blocks/blocks/trade_metrics_blocks.js index 484f4f0..aa8ce3c 100644 --- a/src/static/blocks/blocks/trade_metrics_blocks.js +++ b/src/static/blocks/blocks/trade_metrics_blocks.js @@ -26,7 +26,7 @@ export function defineTradeMetricsBlocks() { "args0": [ { "type": "input_value", - "name": "NEXT", + "name": "VALUES", "check": "dynamic_value", "align": "LEFT" } @@ -49,7 +49,7 @@ export function defineTradeMetricsBlocks() { "args0": [ { "type": "input_value", - "name": "NEXT", + "name": "VALUES", "check": "dynamic_value", "align": "LEFT" } @@ -82,7 +82,7 @@ export function defineTradeMetricsBlocks() { }, { "type": "input_value", - "name": "NEXT", + "name": "VALUES", "check": "dynamic_value", "align": "LEFT" } @@ -105,7 +105,7 @@ export function defineTradeMetricsBlocks() { "args0": [ { "type": "input_value", - "name": "NEXT", + "name": "VALUES", "check": "dynamic_value", "align": "LEFT" } @@ -128,7 +128,7 @@ export function defineTradeMetricsBlocks() { "args0": [ { "type": "input_value", - "name": "NEXT", + "name": "VALUES", "check": "dynamic_value", "align": "LEFT" } @@ -152,7 +152,7 @@ export function defineTradeMetricsBlocks() { "args0": [ { "type": "input_value", - "name": "NEXT", + "name": "VALUES", "check": "dynamic_value" } ], diff --git a/src/static/blocks/generators/advanced_math_generators.js b/src/static/blocks/generators/advanced_math_generators.js index 2be8ff8..373ca58 100644 --- a/src/static/blocks/generators/advanced_math_generators.js +++ b/src/static/blocks/generators/advanced_math_generators.js @@ -15,39 +15,11 @@ export function defineAdvancedMathGenerators() { return; } - /** - * Helper Function: processValuesInput - * Processes the 'VALUES' input and returns an array of values. - * - * @param {Blockly.Block} block - The block containing the 'VALUES' input. - * @returns {Array} - An array of values from the 'VALUES' input. - */ - function processValuesInput(block) { - const valuesInputBlock = block.getInputTargetBlock('VALUES'); - let values = []; - - if (valuesInputBlock) { - const valuesJson = Blockly.JSON.blockToCode(valuesInputBlock); - - // Check if valuesJson is a 'dynamic_value' object and has an array of values - if (valuesJson && valuesJson.type === 'dynamic_value' && Array.isArray(valuesJson.values)) { - values = valuesJson.values; // Use the values array directly from dynamic_value - } else if (Array.isArray(valuesJson)) { - values = valuesJson; // Use valuesJson as-is if it's already an array - } else { - values = [valuesJson]; // Wrap non-array values in a list - } - } - - return values; - } - - /** * Generator for 'power' Block */ Blockly.JSON['power'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); const base = valuesArray[0] !== undefined ? valuesArray[0] : 0; const exponent = valuesArray[1] !== undefined ? valuesArray[1] : 1; @@ -75,7 +47,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'modulo' Block */ Blockly.JSON['modulo'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); const dividend = valuesArray[0] !== undefined ? valuesArray[0] : 0; const divisor = valuesArray[1] !== undefined ? valuesArray[1] : 1; @@ -103,7 +75,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'sqrt' Block */ Blockly.JSON['sqrt'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); const number = valuesArray[0] !== undefined ? valuesArray[0] : 0; @@ -129,7 +101,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'abs' Block */ Blockly.JSON['abs'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); const number = valuesArray[0] !== undefined ? valuesArray[0] : 0; @@ -155,7 +127,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'max' Block */ Blockly.JSON['max'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); // Create operation object const operationObject = Blockly.JSON.createOperationObject('max', { @@ -173,7 +145,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'min' Block */ Blockly.JSON['min'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); // Create operation object const operationObject = Blockly.JSON.createOperationObject('min', { @@ -191,7 +163,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'factorial' Block */ Blockly.JSON['factorial'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); const number = valuesArray[0] !== undefined ? valuesArray[0] : 1; @@ -217,7 +189,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'log' Block */ Blockly.JSON['log'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); const number = valuesArray[0] !== undefined ? valuesArray[0] : 1; @@ -243,7 +215,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'ln' Block */ Blockly.JSON['ln'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); const number = valuesArray[0] !== undefined ? valuesArray[0] : 1; @@ -270,7 +242,7 @@ export function defineAdvancedMathGenerators() { */ Blockly.JSON['trig'] = function(block) { const operator = block.getFieldValue('operator'); // 'sin', 'cos', 'tan' - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); const angle = valuesArray[0] !== undefined ? valuesArray[0] : 0; @@ -304,7 +276,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'mean' Block */ Blockly.JSON['mean'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); // Create operation object const operationObject = Blockly.JSON.createOperationObject('mean', { @@ -322,7 +294,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'median' Block */ Blockly.JSON['median'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); // Create operation object const operationObject = Blockly.JSON.createOperationObject('median', { @@ -340,7 +312,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'std_dev' Block */ Blockly.JSON['std_dev'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); // Create operation object const operationObject = Blockly.JSON.createOperationObject('std_dev', { @@ -358,7 +330,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'round' Block */ Blockly.JSON['round'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); const number = valuesArray[0] !== undefined ? valuesArray[0] : 0; const decimals = valuesArray[1] !== undefined ? valuesArray[1] : 0; @@ -389,7 +361,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'floor' Block */ Blockly.JSON['floor'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); const number = valuesArray[0] !== undefined ? valuesArray[0] : 0; @@ -415,7 +387,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'ceil' Block */ Blockly.JSON['ceil'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); const number = valuesArray[0] !== undefined ? valuesArray[0] : 0; @@ -441,7 +413,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'random' Block */ Blockly.JSON['random'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); let min, max; if (valuesArray.length >= 2) { @@ -482,7 +454,7 @@ export function defineAdvancedMathGenerators() { * Generator for 'clamp' Block */ Blockly.JSON['clamp'] = function(block) { - const valuesArray = processValuesInput(block); + const valuesArray = Blockly.JSON.processValuesInput(block); const number = valuesArray[0] !== undefined ? valuesArray[0] : 0; const min = valuesArray[1] !== undefined ? valuesArray[1] : null; @@ -516,36 +488,50 @@ export function defineAdvancedMathGenerators() { * Generator for 'math_operation' Block */ Blockly.JSON['math_operation'] = function(block) { + /** + * Helper Function: _extractValues + * Extracts values from a given input block, handling 'dynamic_value' objects, arrays, or single values. + * + * @param {Blockly.Block} inputBlock - The block to extract values from. + * @returns {Array} - An array of values extracted from the input block. + */ + Blockly.JSON._extractValues = function(inputBlock) { + if (!inputBlock) return []; + const inputJson = Blockly.JSON.blockToCode(inputBlock); + if (inputJson?.type === 'dynamic_value' && Array.isArray(inputJson.values)) { + return inputJson.values; // Use values directly if it's a 'dynamic_value' array + } + return Array.isArray(inputJson) ? inputJson : [inputJson]; // Wrap in array if not already an array + }; + const operator = block.getFieldValue('operator'); // 'ADD', 'SUBTRACT', 'MULTIPLY', 'DIVIDE' - const valuesArray = processValuesInput(block); + const leftValues = Blockly.JSON._extractValues(block.getInputTargetBlock('LEFT')); + const rightValues = Blockly.JSON._extractValues(block.getInputTargetBlock('RIGHT')); - const leftOperand = valuesArray[0] !== undefined ? valuesArray[0] : 0; - const rightOperand = valuesArray[1] !== undefined ? valuesArray[1] : 0; + // Use the first values or default to 0 if not available + const leftOperand = leftValues[0] ?? 0; + const rightOperand = rightValues[0] ?? 0; - // Validate operator + // Validate and format the operator const validOperators = ['ADD', 'SUBTRACT', 'MULTIPLY', 'DIVIDE']; + const validatedOperator = validOperators.includes(operator) ? operator.toLowerCase() : 'add'; if (!validOperators.includes(operator)) { console.error(`Invalid operator '${operator}' in 'math_operation' block. Defaulting to 'ADD'.`); } - const validatedOperator = validOperators.includes(operator) ? operator : 'ADD'; - // Create operation object + // Create the primary operation object const operationObject = Blockly.JSON.createOperationObject('math_operation', { - operator: validatedOperator.toLowerCase(), // Assuming server expects lowercase operators + operator: validatedOperator, left_operand: leftOperand, right_operand: rightOperand }); - // Include unused values if any - const outputValues = [operationObject]; - if (valuesArray.length > 2) { - outputValues.push(...valuesArray.slice(2)); - } // Output as dynamic_value return { type: 'dynamic_value', - values: outputValues + values: [operationObject] }; }; + } diff --git a/src/static/blocks/generators/balances_generators.js b/src/static/blocks/generators/balances_generators.js index 72273b1..df63a61 100644 --- a/src/static/blocks/generators/balances_generators.js +++ b/src/static/blocks/generators/balances_generators.js @@ -120,26 +120,15 @@ export function defineBalancesGenerators() { * This is a statement block, so it generates an action object and handles 'NEXT' connections. */ Blockly.JSON['set_available_strategy_balance'] = function(block) { - // Retrieve the 'BALANCE' input block - const balanceBlock = block.getInputTargetBlock('BALANCE'); - let balance = 0; - if (balanceBlock) { - const balanceJson = Blockly.JSON.blockToCode(balanceBlock); - if (typeof balanceJson === 'number') { - balance = balanceJson; - } else { - console.error("Invalid balance value in 'set_available_strategy_balance' block. Expected a number."); - } - } else { - console.warn("No balance provided in 'set_available_strategy_balance' block. Using default value 0."); - } + const values = Blockly.JSON.processNumericInput(block,'BALANCE',0); + + const balance = values !== undefined ? values : 0; const json = { type: 'set_available_strategy_balance', - inputs: { - balance: balance - } + inputs: { BALANCE: balance }, +// skipAdditionalParsing: true }; // No need to handle 'NEXT' input; sequencing is managed by _blockToJson diff --git a/src/static/blocks/generators/json_base_generator.js b/src/static/blocks/generators/json_base_generator.js index 2db8481..524fceb 100644 --- a/src/static/blocks/generators/json_base_generator.js +++ b/src/static/blocks/generators/json_base_generator.js @@ -4,12 +4,6 @@ export function defineJsonBaseGenerator() { if (!Blockly.JSON) { Blockly.JSON = new Blockly.Generator('JSON'); - /** - * List of trade_option block types to exclude 'next' processing. - */ - const TRADE_OPTION_TYPES = ['time_in_force', 'limit', "trailing_limit", 'target_market', - "stop_loss", "trailing_stop", "take_profit", "name_order"]; - // Define operator precedence constants Blockly.JSON.ORDER_ATOMIC = 0; // 0 "" ... Blockly.JSON.ORDER_NONE = 99; // (no precedence) @@ -89,134 +83,124 @@ export function defineJsonBaseGenerator() { }; }; - /** - * Helper Function: _blockToJson - * Recursively converts a block and its connected blocks into JSON. + * Converts a Blockly block into a JSON representation. + * Handles both custom generators and default JSON object creation. * - * @param {Blockly.Block} block - The block to convert. - * @param {number} currentDepth - The current recursion depth. - * @returns {Object} - The JSON representation of the block. + * @param {Block} block - The block to convert. + * @param {number} currentDepth - The current depth of the block in the workspace (for handling nesting). + * @returns {object|null} - The JSON representation of the block, or null if conversion fails. */ Blockly.JSON._blockToJson = function(block, currentDepth = 0) { + // If the block is null, return null if (!block) { return null; } - // Calculate nesting depth and enforce limits + // Handle situations where recursion exceeds the maximum allowed depth. try { Blockly.JSON._calculateNestingDepth(block, currentDepth); } catch (error) { console.error(error.message); alert(error.message); - throw error; // Stop processing if limit exceeded + throw error; } - // Use the block's JSON generator if it exists + let json; + if (Blockly.JSON && Blockly.JSON[block.type]) { - // Generate JSON object using the block's JSON generator - const json = Blockly.JSON[block.type](block); + // Custom generator exists + json = Blockly.JSON[block.type](block); if (!json || typeof json !== 'object') { console.error(`Generator for block type '${block.type}' did not return a valid JSON object.`); return null; } - // **Check for the skipAdditionalParsing flag** + // Skip additional parsing if specified if (json.skipAdditionalParsing) { - // Remove the flag to prevent it from being included in the final JSON delete json.skipAdditionalParsing; - - // **Decide whether to include the block based on the presence of meaningful content** - if (!json.type) { - // If 'type' is undefined or null, exclude the block - return null; - } - - // Return the JSON as it has been fully handled by the generator return json; } - // **Proceed with existing parsing logic only if skipAdditionalParsing is not set** - block.inputList.forEach(input => { - if (input.type === Blockly.INPUT_VALUE && input.connection && input.connection.targetBlock()) { - - const targetBlock = input.connection.targetBlock(); - const targetJson = Blockly.JSON._blockToJson(targetBlock, currentDepth + 1); // Increment currentDepth - - if (!json.inputs) json.inputs = {}; - json.inputs[input.name] = targetJson; - } - }); - - // **Process Statement Inputs** - block.inputList.forEach(input => { - if (input.type === Blockly.NEXT_STATEMENT && input.connection && input.connection.targetBlock()) { - - const targetBlock = input.connection.targetBlock(); - const targetJson = Blockly.JSON._blockToJson(targetBlock, currentDepth + 1); // Increment currentDepth - - if (!json.statements) json.statements = {}; - if (!json.statements[input.name]) json.statements[input.name] = []; - json.statements[input.name].push(targetJson); - } - }); - - // **Handle 'next' connections** - if (block.getNextBlock()) { - const nextBlock = Blockly.JSON._blockToJson(block.getNextBlock(), currentDepth); // Same depth - json.next = nextBlock; - } - - return json; } else { - // Fallback: Manually construct JSON if no JSON generator exists - const json = { - type: block.type, - fields: {}, - inputs: {}, - statements: {} + // No custom generator, create a default JSON object + json = { + type: block.type }; - // Capture all fields in the block - block.inputList.forEach(input => { - if (input.fieldRow) { - input.fieldRow.forEach(field => { - if (field.name && field.getValue) { - json.fields[field.name] = field.getValue(); - } - }); - } - }); + // Capture fields and process inputs + Blockly.JSON._captureFields(block, json); + Blockly.JSON._processInputs(block, json, currentDepth); - // Process inputs - block.inputList.forEach(input => { - if (input.type === Blockly.INPUT_VALUE && input.connection && input.connection.targetBlock()) { - const targetBlock = input.connection.targetBlock(); - const targetJson = Blockly.JSON._blockToJson(targetBlock, currentDepth + 1); - - json.inputs[input.name] = targetJson; - } else if (input.type === Blockly.NEXT_STATEMENT && input.connection && input.connection.targetBlock()) { - - const targetBlock = input.connection.targetBlock(); - const targetJson = Blockly.JSON._blockToJson(targetBlock, currentDepth + 1); - - if (!json.statements) json.statements = {}; - if (!json.statements[input.name]) json.statements[input.name] = []; - json.statements[input.name].push(targetJson); - } - }); - - // **Handle 'next' connections** - if (block.getNextBlock()) { - const nextBlock = Blockly.JSON._blockToJson(block.getNextBlock(), currentDepth); - json.next = nextBlock; - } - - return json; } + // Handle 'next' connections recursively + if (block.getNextBlock()) { + json.next = Blockly.JSON._blockToJson(block.getNextBlock(), currentDepth); + } + + return json; }; + /** + * Processes the inputs of a block and adds them to the JSON representation. + * Handles both value and statement inputs. + * + * @param {Blockly.Block} block - The block whose inputs are to be processed. + * @param {object} json - The JSON object to populate with input data. + * @param {number} currentDepth - The current depth of the block in the workspace. + */ + Blockly.JSON._processInputs = function(block, json, currentDepth) { + // Iterate over each input in the block's input list + block.inputList.forEach(input => { + // If the input does not have a connection to another block, skip to the next input + if (!input.connection || !input.connection.targetBlock()) return; + + // Get and convert the connected block to JSON, increasing the depth + const targetBlock = input.connection.targetBlock(); + const targetJson = Blockly.JSON._blockToJson(targetBlock, currentDepth + 1); + + // If the input is a value input, add it to the 'inputs' section of the json + if (input.type === Blockly.INPUT_VALUE) { + if (!json.inputs) json.inputs = {}; + json.inputs[input.name] = targetJson; + } + // If the input is a statement input, add it to the 'statements' section of the json + else if (input.type === Blockly.NEXT_STATEMENT) { + if (!json.statements) json.statements = {}; + if (!json.statements[input.name]) json.statements[input.name] = []; + json.statements[input.name].push(targetJson); + } + }); + }; + + + /** + * Captures the fields of a block and adds them to the JSON representation. + * + * @param {Blockly.Block} block - The block whose fields are to be captured. + * @param {object} json - The JSON object to populate with field data. + */ + Blockly.JSON._captureFields = function(block, json) { + // Iterate over each input in the block's input list + block.inputList.forEach(input => { + // If the input doesn't contain a fieldRow, skip to the next input. + if (!input.fieldRow) return; + + // Iterate over each field in the fieldRow + input.fieldRow.forEach(field => { + // If the field has no name or method to get its value, skip to the next field. + if (!field.name || !field.getValue) return; + + // Initialize the fields object in JSON if it doesn't exist + if (!json.fields) json.fields = {}; + // Add the field name and its value to the JSON fields object + json.fields[field.name] = field.getValue(); + }); + }); + }; + + /** * Helper Function: _calculateNestingDepth * Calculates the nesting depth of a block and throws an error if it exceeds the maximum allowed depth. @@ -225,43 +209,56 @@ export function defineJsonBaseGenerator() { * @param {number} currentDepth - The current recursion depth. */ Blockly.JSON._calculateNestingDepth = function(block, currentDepth) { - const MAX_DEPTH = 10; - if (currentDepth > MAX_DEPTH) { - throw new Error(`Maximum block nesting depth of ${MAX_DEPTH} exceeded.`); + if (currentDepth > Blockly.JSON.maxDepth) { + throw new Error(`Maximum block nesting depth of ${Blockly.JSON.maxDepth} exceeded.`); } // Add any additional depth calculations or checks if necessary }; /** - * Helper Function: processNumericInput - * Processes a numeric input. + * Helper Function: extractValues + * Extracts values from a block's input, handling dynamic_value arrays or single values. * * @param {Blockly.Block} block - The block containing the input. * @param {string} inputName - The name of the input field. - * @param {number} defaultValue - The default value to use if input is invalid. - * @returns {number} - The validated input. + * @param {number | null} defaultValue - Default value for single extractions (null for array extraction). + * @returns {Array | number} - An array of values if defaultValue is null; otherwise, the first value or the default. */ - Blockly.JSON.processNumericInput = function (block, inputName, defaultValue) { + Blockly.JSON.extractValues = function(block, inputName, defaultValue = null) { const inputBlock = block.getInputTargetBlock(inputName); - - // Return default value if input block is missing if (!inputBlock) { - console.warn(`No block connected to '${inputName}' input of '${block.type}' block. Using default value ${defaultValue}.`); - return defaultValue; + if (defaultValue !== null) { + console.warn(`No block connected to '${inputName}' input of '${block.type}' block. Using default value ${defaultValue}.`); + return defaultValue; + } + return []; // Return empty array for multi-value cases } const inputJson = Blockly.JSON.blockToCode(inputBlock); - // Check if inputJson is correctly structured - if (!inputJson || inputJson.type !== 'dynamic_value' || !Array.isArray(inputJson.values) || inputJson.values.length === 0) { + if (inputJson?.type === 'dynamic_value' && Array.isArray(inputJson.values)) { + return defaultValue !== null ? inputJson.values[0] : inputJson.values; + } + + if (defaultValue !== null) { console.error(`Invalid JSON structure for '${inputName}' in '${block.type}' block. Expected a 'dynamic_value' with at least one value.`); return defaultValue; } - const extractedValue = inputJson.values[0]; - return extractedValue; + return Array.isArray(inputJson) ? inputJson : [inputJson]; }; + // Usage in processValuesInput (array case) + Blockly.JSON.processValuesInput = function(block) { + return Blockly.JSON.extractValues(block, 'VALUES'); + }; + + // Usage in processNumericInput (single numeric case) + Blockly.JSON.processNumericInput = function(block, inputName, defaultValue) { + return Blockly.JSON.extractValues(block, inputName, defaultValue); + }; + + console.log('Blockly.JSON generator initialized with helper functions: createOperationObject, _blockToJson, collectTradeOptions.'); } } diff --git a/src/static/blocks/generators/logical_generators.js b/src/static/blocks/generators/logical_generators.js index e4be697..3fcd97a 100644 --- a/src/static/blocks/generators/logical_generators.js +++ b/src/static/blocks/generators/logical_generators.js @@ -48,75 +48,77 @@ export function defineLogicalGenerators() { return { left, right }; } -/** - * Generator for 'comparison' Block - * - * Generates a JSON object representing a comparison operation between two values - * using operators like '>', '<', '=='. Outputs a Boolean value based on the comparison result. - */ -Blockly.JSON['comparison'] = function(block) { /** - * Helper Function: processTwoValueInput - * Processes 'LEFT' and 'RIGHT' inputs and returns their JSON representations. + * Generator for 'comparison' Block * - * @param {Blockly.Block} block - The block containing the 'LEFT' and 'RIGHT' inputs. - * @returns {Object} - An object with 'LEFT' and 'RIGHT' properties. + * Generates a JSON object representing a comparison operation between two values + * using operators like '>', '<', '=='. Outputs a Boolean value based on the comparison result. */ - function processTwoValueInput(block) { - const leftBlock = block.getInputTargetBlock('LEFT'); - const rightBlock = block.getInputTargetBlock('RIGHT'); + Blockly.JSON['comparison'] = function(block) { + /** + * Helper Function: processTwoValueInput + * Processes 'LEFT' and 'RIGHT' inputs and returns their JSON representations. + * + * @param {Blockly.Block} block - The block containing the 'LEFT' and 'RIGHT' inputs. + * @returns {Object} - An object with 'LEFT' and 'RIGHT' properties. + */ + function processTwoValueInput(block) { + const leftBlock = block.getInputTargetBlock('LEFT'); + const rightBlock = block.getInputTargetBlock('RIGHT'); - let left = null; - let right = null; + let left = null; + let right = null; - if (leftBlock) { - left = Blockly.JSON.blockToCode(leftBlock); - if (typeof left !== 'object' || left === null) { - console.error(`Invalid 'LEFT' input in 'comparison' block. Expected an object.`); - left = 0; + if (leftBlock) { + left = Blockly.JSON.blockToCode(leftBlock); + if (typeof left !== 'object' || left === null) { + console.error(`Invalid 'LEFT' input in 'comparison' block. Expected an object.`); + left = 0; + } + } else { + console.warn(`No block connected to 'LEFT' input of 'comparison' block. Using default value 0.`); + left = 0; // Assign a default value if necessary } - } else { - console.warn(`No block connected to 'LEFT' input of 'comparison' block. Using default value 0.`); - left = 0; // Assign a default value if necessary - } - if (rightBlock) { - right = Blockly.JSON.blockToCode(rightBlock); - if (typeof right !== 'object' || right === null) { - console.error(`Invalid 'RIGHT' input in 'comparison' block. Expected an object.`); - right = 0; + if (rightBlock) { + right = Blockly.JSON.blockToCode(rightBlock); + if (typeof right !== 'object' || right === null) { + console.error(`Invalid 'RIGHT' input in 'comparison' block. Expected an object.`); + right = 0; + } + } else { + console.warn(`No block connected to 'RIGHT' input of 'comparison' block. Using default value 0.`); + right = 0; // Assign a default value if necessary } - } else { - console.warn(`No block connected to 'RIGHT' input of 'comparison' block. Using default value 0.`); - right = 0; // Assign a default value if necessary + + return { LEFT: left, RIGHT: right }; } - return { LEFT: left, RIGHT: right }; - } + const { LEFT, RIGHT } = processTwoValueInput(block); - const { LEFT, RIGHT } = processTwoValueInput(block); - - // Retrieve the selected operator - const operator = block.getFieldValue('OPERATOR'); // Must match the block definition - const validOperators = ['>', '<', '==', '!=', '>=', '<=']; - if (!validOperators.includes(operator)) { - console.error(`Invalid operator '${operator}' in 'comparison' block. Defaulting to '=='`); - } - const validatedOperator = validOperators.includes(operator) ? operator : '=='; - - // Create operation object with 'inputs' - const operationObject = { - type: 'comparison', - operator: validatedOperator, - inputs: { - LEFT: LEFT !== null ? LEFT : { type: 'value_input', values: [0] }, - RIGHT: RIGHT !== null ? RIGHT : { type: 'value_input', values: [0] } + // Retrieve the selected operator + const operator = block.getFieldValue('OPERATOR'); // Must match the block definition + const validOperators = ['>', '<', '==', '!=', '>=', '<=']; + if (!validOperators.includes(operator)) { + console.error(`Invalid operator '${operator}' in 'comparison' block. Defaulting to '=='`); } + const validatedOperator = validOperators.includes(operator) ? operator : '=='; + + // Create operation object with 'inputs' + const operationObject = { + type: 'comparison', + operator: validatedOperator, + inputs: { + LEFT: LEFT !== null ? LEFT : { type: 'dynamic_value', values: [0] }, + RIGHT: RIGHT !== null ? RIGHT : { type: 'dynamic_value', values: [0] } + }, + skipAdditionalParsing: true + + }; + + return operationObject; }; - return operationObject; -}; - /** * Generator for 'logical_and' Block @@ -124,101 +126,142 @@ Blockly.JSON['comparison'] = function(block) { * Generates a JSON object representing a logical AND operation between two conditions. */ Blockly.JSON['logical_and'] = function(block) { - const { left, right } = processTwoValueInput(block); + // Get the connected blocks + const leftBlock = block.getInputTargetBlock('LEFT'); + const rightBlock = block.getInputTargetBlock('RIGHT'); - // Default to false if any condition is null - const leftCondition = typeof left === 'boolean' ? left : false; - const rightCondition = typeof right === 'boolean' ? right : false; + let leftValue = null; + let rightValue = null; - // Create operation object - const operationObject = Blockly.JSON.createOperationObject('logical_and', { - left: leftCondition, - right: rightCondition - }); + // Process the left input + if (leftBlock) { + const leftGenerator = Blockly.JSON[leftBlock.type]; + if (typeof leftGenerator === 'function') { + leftValue = leftGenerator(leftBlock); + } else { + console.warn(`No generator found for block type "${leftBlock.type}". Defaulting left input to false.`); + leftValue = false; + } + } else { + console.warn("No block connected to 'LEFT' input of 'logical_and' block. Defaulting to false."); + leftValue = false; + } - // Output as dynamic_value - return operationObject; + // Process the right input + if (rightBlock) { + const rightGenerator = Blockly.JSON[rightBlock.type]; + if (typeof rightGenerator === 'function') { + rightValue = rightGenerator(rightBlock); + } else { + console.warn(`No generator found for block type "${rightBlock.type}". Defaulting right input to false.`); + rightValue = false; + } + } else { + console.warn("No block connected to 'RIGHT' input of 'logical_and' block. Defaulting to false."); + rightValue = false; + } + + // Create the logical_and operation object + const json = { + type: 'logical_and', + inputs: { + left: leftValue, + right: rightValue + } + }; + + return json; }; + /** * Generator for 'logical_or' Block * * Generates a JSON object representing a logical OR operation between two conditions. */ Blockly.JSON['logical_or'] = function(block) { - const { left, right } = processTwoValueInput(block); + // Get the connected blocks + const leftBlock = block.getInputTargetBlock('LEFT'); + const rightBlock = block.getInputTargetBlock('RIGHT'); - // Default to false if any condition is null - const leftCondition = typeof left === 'boolean' ? left : false; - const rightCondition = typeof right === 'boolean' ? right : false; + let leftValue = null; + let rightValue = null; - // Create operation object - const operationObject = Blockly.JSON.createOperationObject('logical_or', { - left: leftCondition, - right: rightCondition - }); + // Process the left input + if (leftBlock) { + const leftGenerator = Blockly.JSON[leftBlock.type]; + if (typeof leftGenerator === 'function') { + leftValue = leftGenerator(leftBlock); + } else { + console.warn(`No generator found for block type "${leftBlock.type}". Defaulting left input to false.`); + leftValue = false; + } + } else { + console.warn("No block connected to 'LEFT' input of 'logical_or' block. Defaulting to false."); + leftValue = false; + } - // Output as dynamic_value - return operationObject; + // Process the right input + if (rightBlock) { + const rightGenerator = Blockly.JSON[rightBlock.type]; + if (typeof rightGenerator === 'function') { + rightValue = rightGenerator(rightBlock); + } else { + console.warn(`No generator found for block type "${rightBlock.type}". Defaulting right input to false.`); + rightValue = false; + } + } else { + console.warn("No block connected to 'RIGHT' input of 'logical_or' block. Defaulting to false."); + rightValue = false; + } + + // Create the logical_or operation object + const json = { + type: 'logical_or', + inputs: { + left: leftValue, + right: rightValue + } + }; + + return json; }; + /** * Generator for 'is_false' Block * * Generates a JSON object to check if a given condition is false. */ Blockly.JSON['is_false'] = function(block) { - // Retrieve and parse the 'CONDITION' input - const conditionBlock = block.getInputTargetBlock('CONDITION'); - let condition = false; + // Get the connected block + const conditionBlock = block.getInputTargetBlock('condition'); + let conditionValue = null; + + // Process the condition input if (conditionBlock) { - const conditionJson = Blockly.JSON.blockToCode(conditionBlock); - if (typeof conditionJson === 'boolean') { - condition = conditionJson; + const conditionGenerator = Blockly.JSON[conditionBlock.type]; + if (typeof conditionGenerator === 'function') { + conditionValue = conditionGenerator(conditionBlock); } else { - console.error("Invalid condition value in 'is_false' block. Expected a Boolean."); + console.warn(`No generator found for block type "${conditionBlock.type}". Defaulting condition to false.`); + conditionValue = false; } } else { - console.warn("No condition provided in 'is_false' block. Using default value 'false'."); + console.warn("No block connected to 'condition' input of 'is_false' block. Defaulting to false."); + conditionValue = false; } - // Create operation object - const operationObject = Blockly.JSON.createOperationObject('is_false', { - condition: condition - }); - - // Output as dynamic_value - return operationObject; - }; - - /** - * Generator for 'is_true' Block - * - * Generates a JSON object to check if a given condition is true. - */ - Blockly.JSON['is_true'] = function(block) { - // Retrieve and parse the 'CONDITION' input - const conditionBlock = block.getInputTargetBlock('CONDITION'); - let condition = false; - - if (conditionBlock) { - const conditionJson = Blockly.JSON.blockToCode(conditionBlock); - if (typeof conditionJson === 'boolean') { - condition = conditionJson; - } else { - console.error("Invalid condition value in 'is_true' block. Expected a Boolean."); + // Create the is_false operation object + const json = { + type: 'is_false', + inputs: { + condition: conditionValue } - } else { - console.warn("No condition provided in 'is_true' block. Using default value 'false'."); - } + }; - // Create operation object - const operationObject = Blockly.JSON.createOperationObject('is_true', { - condition: condition - }); - - // Output as dynamic_value - return operationObject; + return json; }; + } diff --git a/src/static/blocks/generators/market_data_generators.js b/src/static/blocks/generators/market_data_generators.js index 4211553..a1a938d 100644 --- a/src/static/blocks/generators/market_data_generators.js +++ b/src/static/blocks/generators/market_data_generators.js @@ -16,72 +16,70 @@ export function defineMarketDataGenerators() { /** * Helper Function: processSourceInput - * Processes the 'SOURCE' input and returns its value. + * Processes the 'source' input, ensuring it is a valid source block and returning its value if connected. * - * @param {Blockly.Block} block - The block containing the 'SOURCE' input. - * @returns {Object} - The source object or a default empty object. + * @param {Blockly.Block} block - The block containing the 'source' input. + * @returns {Object} - The source object or a default empty object if validation fails. */ function processSourceInput(block) { - const sourceBlock = block.getInputTargetBlock('SOURCE'); - let source = {}; - - if (sourceBlock) { - const sourceJson = Blockly.JSON.blockToCode(sourceBlock); - if (typeof sourceJson === 'object' && sourceJson !== null) { - source = sourceJson; - } else { - console.error(`Invalid source object in '${block.type}' block. Expected an object.`); - } - } else { - console.warn(`No source connected to 'SOURCE' input of '${block.type}' block. Using default empty object.`); + const sourceBlock = block.getInputTargetBlock('source'); + if (sourceBlock && sourceBlock.type === 'source') { + return Blockly.JSON.blockToCode(sourceBlock); // Process source configuration } - return source; + console.warn(`Invalid or missing source block connected to 'source' input of '${block.type}' block. Using default configuration.`); + return {}; // Default empty object if validation fails } + /** + * General function to generate market data blocks, handling additional values. + * Uses extractValues to pull optional extra values connected to 'source'. + */ + function generateMarketDataBlock(block, operationType) { + // Process the 'source' input (optional) and additional values + const source = processSourceInput(block); + const operationObject = Blockly.JSON.createOperationObject(operationType, { source: source.values ? source.values[0] : source }); + + // Initialize output values array + const outputValues = [operationObject]; + + // Add the remaining values from source if it contains a dynamic_value structure + if (source?.type === 'dynamic_value' && Array.isArray(source.values)) { + outputValues.push(...source.values.slice(1)); + } + + // Use extractValues to capture additional values beyond the primary source + const additionalValues = Blockly.JSON.extractValues(block, 'source'); // Extract additional values if any + if (additionalValues.length > 0) { + outputValues.push(...additionalValues); + } + + return { + type: 'dynamic_value', + values: outputValues + }; + } + + /** * Generator for 'current_price' Block */ Blockly.JSON['current_price'] = function(block) { - const source = processSourceInput(block); - - // Create operation object - const operationObject = Blockly.JSON.createOperationObject('current_price', { - source: source - }); - - // Output as dynamic_value - return operationObject; + return generateMarketDataBlock(block, 'current_price'); }; /** * Generator for 'bid_price' Block */ Blockly.JSON['bid_price'] = function(block) { - const source = processSourceInput(block); - - // Create operation object - const operationObject = Blockly.JSON.createOperationObject('bid_price', { - source: source - }); - - // Output as dynamic_value - return operationObject; + return generateMarketDataBlock(block, 'bid_price'); }; /** * Generator for 'ask_price' Block */ Blockly.JSON['ask_price'] = function(block) { - const source = processSourceInput(block); - - // Create operation object - const operationObject = Blockly.JSON.createOperationObject('ask_price', { - source: source - }); - - // Output as dynamic_value - return operationObject; + return generateMarketDataBlock(block, 'ask_price'); }; /** @@ -89,49 +87,51 @@ export function defineMarketDataGenerators() { */ Blockly.JSON['last_candle_value'] = function(block) { const source = processSourceInput(block); - - // Retrieve the 'CANDLE_PART' field - const candlePart = block.getFieldValue('CANDLE_PART') || 'close'; - const validCandleParts = ['open', 'high', 'low', 'close', 'volume']; - if (!validCandleParts.includes(candlePart)) { - console.error(`Invalid candle part '${candlePart}' in 'last_candle_value' block. Defaulting to 'close'.`); - } + const candlePart = block.getFieldValue('candle_part') || 'close'; + const validCandleParts = ['open', 'high', 'low', 'close']; const validatedCandlePart = validCandleParts.includes(candlePart) ? candlePart : 'close'; - // Create operation object const operationObject = Blockly.JSON.createOperationObject('last_candle_value', { - source: source, + source, candle_part: validatedCandlePart }); - // Output as dynamic_value - return operationObject; + const outputValues = [operationObject]; + const additionalValues = Blockly.JSON.extractValues(block, 'source'); // Use extractValues for any additional values + if (additionalValues.length > 0) { + outputValues.push(...additionalValues); + } + + return { + type: 'dynamic_value', + values: outputValues + }; }; /** * Generator for 'source' Block - * - * Generates a source object defining the data source based on Time Frame (TF), - * Exchange (EXC), and Symbol (SYM). */ Blockly.JSON['source'] = function(block) { - // Retrieve dropdown values - const timeFrame = block.getFieldValue('TF') || '1m'; // Default to '1m' if not set - const exchange = block.getFieldValue('EXC') || 'Binance'; // Default to 'Binance' - const symbol = block.getFieldValue('SYM') || 'BTCUSDT'; // Default to 'BTCUSDT' - - // Validate fields if necessary - // Example: Ensure timeFrame matches expected patterns - // Skipping detailed validation for brevity + const timeFrame = block.getFieldValue('TF') || '1m'; + const exchange = block.getFieldValue('EXC') || 'Binance'; + const symbol = block.getFieldValue('SYM') || 'BTCUSDT'; // Create source object - const sourceObject = { + const operationObject = Blockly.JSON.createOperationObject('source', { time_frame: timeFrame, exchange: exchange, - symbol: symbol - }; + symbol: symbol}); - // Output as source object - return sourceObject; + // Prepare output, handling any additional values + const outputValues = [operationObject]; + const valuesArray = Blockly.JSON.processValuesInput(block); + if (valuesArray.length > 0) { + outputValues.push(...valuesArray); + } + + return { + type: 'dynamic_value', + values: outputValues + }; }; } diff --git a/src/static/blocks/generators/order_metrics_generators.js b/src/static/blocks/generators/order_metrics_generators.js index d622b11..2efeaf8 100644 --- a/src/static/blocks/generators/order_metrics_generators.js +++ b/src/static/blocks/generators/order_metrics_generators.js @@ -34,39 +34,60 @@ export function defineOrderMetricsGenerators() { * Generator for 'order_volume' Block */ Blockly.JSON['order_volume'] = function(block) { - // Retrieve the 'ORDER_TYPE' from the dropdown - const orderType = block.getFieldValue('ORDER_TYPE'); // 'filled' or 'unfilled' + const orderType = block.getFieldValue('ORDER_TYPE'); const validatedOrderType = validateOrderType(orderType); - // Create operation object const operationObject = Blockly.JSON.createOperationObject('order_volume', { order_type: validatedOrderType }); - // Output as dynamic_value - return operationObject; + // Prepare output, handling any additional values + const outputValues = [operationObject]; + const valuesArray = Blockly.JSON.processValuesInput(block); + if (valuesArray.length > 0) { + outputValues.push(...valuesArray); + } + + return { + type: 'dynamic_value', + values: outputValues + }; }; /** * Generator for 'filled_orders' Block */ Blockly.JSON['filled_orders'] = function(block) { - // Create operation object const operationObject = Blockly.JSON.createOperationObject('filled_orders', {}); - // Output as dynamic_value - return operationObject; + const outputValues = [operationObject]; + const valuesArray = Blockly.JSON.processValuesInput(block); + if (valuesArray.length > 0) { + outputValues.push(...valuesArray); + } + + return { + type: 'dynamic_value', + values: outputValues + }; }; /** * Generator for 'unfilled_orders' Block */ Blockly.JSON['unfilled_orders'] = function(block) { - // Create operation object const operationObject = Blockly.JSON.createOperationObject('unfilled_orders', {}); - // Output as dynamic_value - return operationObject; + const outputValues = [operationObject]; + const valuesArray = Blockly.JSON.processValuesInput(block); + if (valuesArray.length > 0) { + outputValues.push(...valuesArray); + } + + return { + type: 'dynamic_value', + values: outputValues + }; }; /** @@ -74,11 +95,11 @@ export function defineOrderMetricsGenerators() { */ Blockly.JSON['order_status'] = function(block) { // Retrieve the 'ORDER_NAME' field input - const orderName = (block.getFieldValue('ORDER_NAME') || 'undefined_order').trim(); + const orderName = (block.getFieldValue('ORDER_NAME') || 'last_order').trim(); // Validate order name if (!orderName) { - console.warn("Empty 'ORDER_NAME' in 'order_status' block. Using 'undefined_order'."); + console.warn("Empty 'ORDER_NAME' in 'order_status' block. Using 'last_order'."); } // Retrieve the selected order status from the dropdown @@ -92,7 +113,7 @@ export function defineOrderMetricsGenerators() { // Create operation object const operationObject = Blockly.JSON.createOperationObject('order_status', { - order_name: orderName || 'undefined_order', + order_name: orderName || 'last_order', status: validatedStatus }); diff --git a/src/static/blocks/generators/risk_management_generators.js b/src/static/blocks/generators/risk_management_generators.js index 9343203..4ab03bd 100644 --- a/src/static/blocks/generators/risk_management_generators.js +++ b/src/static/blocks/generators/risk_management_generators.js @@ -8,63 +8,89 @@ export function defineRiskManagementGenerators() { - // Ensure the base generator is initialized and 'createOperationObject' is available - if (!Blockly.JSON || !Blockly.JSON.createOperationObject) { - console.error("Blockly.JSON or createOperationObject not initialized. Please ensure 'json_base_generator.js' is loaded first."); + // Ensure the base generator is initialized + if (!Blockly.JSON) { + console.error("Blockly.JSON not initialized."); return; } /** * Helper Function: processNumericInput - * Processes a numeric input and ensures it is a valid number. + * Processes a numeric input and returns the value. * * @param {Blockly.Block} block - The block containing the input. * @param {string} inputName - The name of the input field. - * @param {number} defaultValue - The default value to use if input is invalid. - * @param {number} minValue - The minimum allowable value (inclusive). - * @param {number} maxValue - The maximum allowable value (inclusive). - * @returns {number} - The validated numeric input. + * @param {*} defaultValue - The default value to use if the input is invalid. + * @returns {*} - The value from the input. */ - function processNumericInput(block, inputName, defaultValue, minValue = Number.NEGATIVE_INFINITY, maxValue = Number.POSITIVE_INFINITY) { + Blockly.JSON.processNumericInput = function(block, inputName, defaultValue) { const inputBlock = block.getInputTargetBlock(inputName); - let value = defaultValue; - if (inputBlock) { - const inputJson = Blockly.JSON.blockToCode(inputBlock); - if (typeof inputJson === 'number') { - value = inputJson; + const generator = Blockly.JSON[inputBlock.type]; + if (typeof generator === 'function') { + const inputJson = generator(inputBlock); + + // Handle dynamic_value + if (inputJson && inputJson.type === 'dynamic_value' && Array.isArray(inputJson.values)) { + const firstValue = inputJson.values[0]; + if (typeof firstValue === 'number' || typeof firstValue === 'object') { + return firstValue; + } else { + console.warn(`Invalid value in '${inputName}'. Using default value ${defaultValue}.`); + return defaultValue; + } + } else if (typeof inputJson === 'number' || typeof inputJson === 'object') { + return inputJson; + } else { + console.warn(`Invalid input for '${inputName}'. Using default value ${defaultValue}.`); + return defaultValue; + } } else { - console.error(`Invalid value for '${inputName}' in '${block.type}' block. Expected a number.`); + console.warn(`No generator found for block type '${inputBlock.type}'. Using default value ${defaultValue}.`); + return defaultValue; } } else { - console.warn(`No block connected to '${inputName}' input of '${block.type}' block. Using default value ${defaultValue}.`); + console.warn(`No block connected to '${inputName}'. Using default value ${defaultValue}.`); + return defaultValue; } - - // Enforce limits - if (value < minValue) { - console.warn(`Value for '${inputName}' in '${block.type}' block is below minimum (${minValue}). Clamping to ${minValue}.`); - value = minValue; - } else if (value > maxValue) { - console.warn(`Value for '${inputName}' in '${block.type}' block is above maximum (${maxValue}). Clamping to ${maxValue}.`); - value = maxValue; - } - - return value; - } + }; /** * Generator for 'set_leverage' Block */ Blockly.JSON['set_leverage'] = function(block) { - // Process the 'LEVERAGE' input - const leverage = processNumericInput(block, 'LEVERAGE', 1, 1, 100); // Leverage between 1 and 100 + // Process the 'LEVERAGE' input using processNumericInput + const leverageValue = Blockly.JSON.processNumericInput(block, 'LEVERAGE', 1); + + // Validate the leverage value + let leverage = leverageValue; + + if (typeof leverageValue === 'number') { + // Enforce limits (e.g., leverage between 1 and 100) + if (leverageValue < 1) { + console.warn(`Leverage value too low. Clamping to 1.`); + leverage = 1; + } else if (leverageValue > 100) { + console.warn(`Leverage value too high. Clamping to 100.`); + leverage = 100; + } + } else if (typeof leverageValue === 'object' && leverageValue !== null) { + // Include the object directly (e.g., if it's an operation) + leverage = leverageValue; + } else { + console.warn(`Invalid leverage value. Defaulting to 1.`); + leverage = 1; + } // Create operation object - const json = Blockly.JSON.createOperationObject('set_leverage', { - leverage: leverage - }); + const json = { + type: 'set_leverage', + inputs: { + LEVERAGE: leverage + } + }; - // Output as dynamic_value + // Return the operation object directly return json; }; @@ -73,10 +99,13 @@ export function defineRiskManagementGenerators() { */ Blockly.JSON['current_margin'] = function(block) { // Create operation object - const operationObject = Blockly.JSON.createOperationObject('current_margin', {}); + const json = { + type: 'current_margin', + inputs: {} + }; - // Output as dynamic_value - return operationObject; + // Return the operation object directly + return json; }; /** @@ -84,25 +113,47 @@ export function defineRiskManagementGenerators() { */ Blockly.JSON['risk_ratio'] = function(block) { // Create operation object - const operationObject = Blockly.JSON.createOperationObject('risk_ratio', {}); + const json = { + type: 'risk_ratio', + inputs: {} + }; - // Output as dynamic_value - return operationObject; + // Return the operation object directly + return json; }; /** * Generator for 'max_position_size' Block */ Blockly.JSON['max_position_size'] = function(block) { - // Process the 'MAX_SIZE' input - const maxSize = processNumericInput(block, 'MAX_SIZE', 1, 1); // Minimum of 1 + // Process the 'MAX_SIZE' input using processNumericInput + const maxSizeValue = Blockly.JSON.processNumericInput(block, 'MAX_SIZE', 1); + + let maxSize = maxSizeValue; + + if (typeof maxSizeValue === 'number') { + // Enforce limits (e.g., minimum of 1) + if (maxSizeValue < 1) { + console.warn(`Max position size too low. Clamping to 1.`); + maxSize = 1; + } + } else if (typeof maxSizeValue === 'object' && maxSizeValue !== null) { + // Include the object directly (e.g., if it's an operation) + maxSize = maxSizeValue; + } else { + console.warn(`Invalid max position size value. Defaulting to 1.`); + maxSize = 1; + } // Create operation object - const json = Blockly.JSON.createOperationObject('max_position_size', { - max_size: maxSize - }); + const json = { + type: 'max_position_size', + inputs: { + MAX_SIZE: maxSize + } + }; - // Output as dynamic_value + // Return the action object directly return json; }; } diff --git a/src/static/blocks/generators/time_metrics_generators.js b/src/static/blocks/generators/time_metrics_generators.js index f521226..2bb5977 100644 --- a/src/static/blocks/generators/time_metrics_generators.js +++ b/src/static/blocks/generators/time_metrics_generators.js @@ -34,16 +34,21 @@ export function defineTimeMetricsGenerators() { * Generator for 'time_since_start' Block */ Blockly.JSON['time_since_start'] = function(block) { - // Retrieve the selected time unit from the dropdown const timeUnit = block.getFieldValue('TIME_UNIT') || 'seconds'; const validatedTimeUnit = validateTimeUnit(timeUnit); - // Create operation object - const operationObject = Blockly.JSON.createOperationObject('time_since_start', { - unit: validatedTimeUnit - }); + const operationObject = Blockly.JSON.createOperationObject('time_since_start', {unit: validatedTimeUnit} ); - // Output as dynamic_value - return operationObject; + // Prepare output, handling any additional values + const outputValues = [operationObject]; + const valuesArray = Blockly.JSON.processValuesInput(block); + if (valuesArray.length > 0) { + outputValues.push(...valuesArray); + } + + return { + type: 'dynamic_value', + values: outputValues + }; }; } diff --git a/src/static/blocks/generators/trade_metrics_generators.js b/src/static/blocks/generators/trade_metrics_generators.js index c7d68a6..6179650 100644 --- a/src/static/blocks/generators/trade_metrics_generators.js +++ b/src/static/blocks/generators/trade_metrics_generators.js @@ -18,74 +18,119 @@ export function defineTradeMetricsGenerators() { * Generator for 'active_trades' Block */ Blockly.JSON['active_trades'] = function(block) { - // Create operation object const operationObject = Blockly.JSON.createOperationObject('active_trades', {}); - // Output as dynamic_value - return JSON.stringify(operationObject); + // Handle additional values + const outputValues = [operationObject]; + const valuesArray = Blockly.JSON.processValuesInput(block); + if (valuesArray.length > 0) { + outputValues.push(...valuesArray); + } + + return { + type: 'dynamic_value', + values: outputValues + }; }; /** * Generator for 'total_trades' Block */ Blockly.JSON['total_trades'] = function(block) { - // Create operation object const operationObject = Blockly.JSON.createOperationObject('total_trades', {}); - // Output as dynamic_value - return JSON.stringify(operationObject); + // Handle additional values + const outputValues = [operationObject]; + const valuesArray = Blockly.JSON.processValuesInput(block); + if (valuesArray.length > 0) { + outputValues.push(...valuesArray); + } + + return { + type: 'dynamic_value', + values: outputValues + }; }; /** * Generator for 'last_trade_details' Block */ Blockly.JSON['last_trade_details'] = function(block) { - // Retrieve the 'DETAILS' field input - const details = block.getFieldValue('DETAILS'); // 'price', 'volume', 'direction' - - // Validate the details selection + const details = block.getFieldValue('DETAILS'); const validDetails = ['price', 'volume', 'direction']; const validatedDetails = validDetails.includes(details) ? details : 'price'; - // Create operation object const operationObject = Blockly.JSON.createOperationObject('last_trade_details', { details: validatedDetails }); - // Output as dynamic_value - return JSON.stringify(operationObject); + // Handle additional values + const outputValues = [operationObject]; + const valuesArray = Blockly.JSON.processValuesInput(block); + if (valuesArray.length > 0) { + outputValues.push(...valuesArray); + } + + return { + type: 'dynamic_value', + values: outputValues + }; }; /** * Generator for 'average_entry_price' Block */ Blockly.JSON['average_entry_price'] = function(block) { - // Create operation object const operationObject = Blockly.JSON.createOperationObject('average_entry_price', {}); - // Output as dynamic_value - return JSON.stringify(operationObject); + // Handle additional values + const outputValues = [operationObject]; + const valuesArray = Blockly.JSON.processValuesInput(block); + if (valuesArray.length > 0) { + outputValues.push(...valuesArray); + } + + return { + type: 'dynamic_value', + values: outputValues + }; }; /** * Generator for 'unrealized_profit_loss' Block */ Blockly.JSON['unrealized_profit_loss'] = function(block) { - // Create operation object const operationObject = Blockly.JSON.createOperationObject('unrealized_profit_loss', {}); - // Output as dynamic_value - return JSON.stringify(operationObject); + // Handle additional values + const outputValues = [operationObject]; + const valuesArray = Blockly.JSON.processValuesInput(block); + if (valuesArray.length > 0) { + outputValues.push(...valuesArray); + } + + return { + type: 'dynamic_value', + values: outputValues + }; }; /** * Generator for 'user_active_trades' Block */ Blockly.JSON['user_active_trades'] = function(block) { - // Create operation object const operationObject = Blockly.JSON.createOperationObject('user_active_trades', {}); - // Output as dynamic_value - return JSON.stringify(operationObject); + // Handle additional values + const outputValues = [operationObject]; + const valuesArray = Blockly.JSON.processValuesInput(block); + if (valuesArray.length > 0) { + outputValues.push(...valuesArray); + } + + return { + type: 'dynamic_value', + values: outputValues + }; }; } diff --git a/src/static/blocks/generators/values_and_flags_generators.js b/src/static/blocks/generators/values_and_flags_generators.js index a3ae0cb..6fbb37e 100644 --- a/src/static/blocks/generators/values_and_flags_generators.js +++ b/src/static/blocks/generators/values_and_flags_generators.js @@ -35,22 +35,13 @@ export function defineVAFGenerators() { }; return json; }; - /** - * Generator for 'value_input' Block - * - * Generates a JSON object representing a list of numerical values or nested objects. - * Handles 'VALUE' and 'NEXT' inputs separately to support dynamic block chaining. - * - * @param {Block} block - The current block to generate JSON for. - * @returns {Object} The generated JSON object. - */ Blockly.JSON['value_input'] = function(block) { const values = []; // Initialize the values array /** * Recursively processes each block in the chain. * - * @param {Block} currentBlock - The current block to process. + * @param {Blockly.Block} currentBlock - The current block to process. * @param {number} currentDepth - The current depth in the recursion. */ function processBlock(currentBlock, currentDepth) { @@ -63,17 +54,38 @@ export function defineVAFGenerators() { // Process 'VALUE' and add to values const valueJson = processConnectedBlock(currentBlock, 'VALUE'); - if (valueJson !== null) values.push(valueJson); + if (valueJson !== null) { + // Flatten any nested dynamic_value objects + if (valueJson && valueJson.type === 'dynamic_value' && Array.isArray(valueJson.values)) { + values.push(...valueJson.values); + } else { + values.push(valueJson); + } + } // Process 'NEXT' block in the chain const nextBlock = currentBlock.getInputTargetBlock('NEXT'); - if (nextBlock) processBlock(nextBlock, currentDepth + 1); + if (nextBlock) { + const generator = Blockly.JSON[nextBlock.type]; + if (typeof generator === 'function') { + const generatedValue = generator(nextBlock); + + // Flatten any nested dynamic_value objects + if (generatedValue && generatedValue.type === 'dynamic_value' && Array.isArray(generatedValue.values)) { + values.push(...generatedValue.values); + } else { + values.push(generatedValue); + } + } else { + console.warn(`No generator found for block type "${nextBlock.type}". Skipping this block.`); + } + } } /** * Processes a connected block and retrieves its generated JSON or field value. * - * @param {Block} block - The current block to process. + * @param {Blockly.Block} block - The current block to process. * @param {string} inputName - The input name to retrieve. * @returns {*} The processed value or null if invalid. */ @@ -83,29 +95,31 @@ export function defineVAFGenerators() { const generator = Blockly.JSON[connectedBlock.type]; if (typeof generator === 'function') { const generatedValue = generator(connectedBlock); - return generatedValue.type === 'dynamic_value' && Array.isArray(generatedValue.values) - ? generatedValue.values - : generatedValue; + + // Flatten any nested dynamic_value objects + if (generatedValue && generatedValue.type === 'dynamic_value' && Array.isArray(generatedValue.values)) { + return generatedValue.values; // Return the values array directly + } + return generatedValue; } console.warn(`No generator found for block type "${connectedBlock.type}". Skipping this block.`); return null; } // If no connected block, parse field value as float - const fieldValue = parseFloat(block.getFieldValue(inputName) || NaN); + let fieldValue = parseFloat(block.getFieldValue(inputName) || NaN); if (isNaN(fieldValue)) { - console.warn(`Invalid VALUE "${block.getFieldValue(inputName)}" in value_input block. Skipping this block.`); - return null; + fieldValue = block.getFieldValue(inputName); } return fieldValue; } processBlock(block, 1); // Start processing from the initial block + // Build the JSON object without the skipAdditionalParsing flag const json = { type: 'dynamic_value', - values: values.flat(), // Flatten values array to handle nested arrays - skipAdditionalParsing: true // Flag to skip further parsing + values: values.flat() // Flatten values array to handle nested arrays }; console.log(`Generated JSON for 'value_input' block:`, json); @@ -114,7 +128,6 @@ export function defineVAFGenerators() { - /** * Generator for 'get_variable' Block * diff --git a/src/static/blocks/indicator_blocks.js b/src/static/blocks/indicator_blocks.js index ae4f9d7..85d3d44 100644 --- a/src/static/blocks/indicator_blocks.js +++ b/src/static/blocks/indicator_blocks.js @@ -49,11 +49,13 @@ export function defineIndicatorBlocks() { fields: { NAME: indicatorName, OUTPUT: selectedOutput - }, - inputs: {}, - statements: {} + } + }; + // Output as dynamic_value + return { + type: 'dynamic_value', + values: [json] }; - return json; }; // Append the newly created block to the Indicators category in the toolbox