From bb7fdbf8f913d840311f2270704ce47b60b64b4e Mon Sep 17 00:00:00 2001 From: Rob Date: Sun, 27 Oct 2024 16:51:10 -0300 Subject: [PATCH] The generator definitions are still being tested but progress has been made. --- archived_code/test_DataCache.py | 251 +----- src/static/Strategies.js | 185 ++-- .../blocks/blocks/advanced_math_blocks.js | 45 +- src/static/blocks/blocks/balances_blocks.js | 125 ++- src/static/blocks/blocks/logical_blocks.js | 6 +- .../generators/advanced_math_generators.js | 796 +++++++----------- .../blocks/generators/balances_generators.js | 188 +++-- .../blocks/generators/control_generators.js | 266 +++--- .../blocks/generators/json_base_generator.js | 303 +++++-- .../blocks/generators/logical_generators.js | 290 ++++--- .../generators/market_data_generators.js | 299 ++----- .../generators/order_metrics_generators.js | 177 ++-- .../generators/risk_management_generators.js | 191 ++--- .../generators/time_metrics_generators.js | 78 +- .../generators/trade_metrics_generators.js | 170 +--- .../generators/trade_order_generators.js | 402 ++++----- .../generators/values_and_flags_generators.js | 310 +++---- src/static/blocks/indicator_blocks.js | 66 ++ src/static/brightertrades_favicon.ico | Bin 0 -> 2522 bytes src/static/trading_strategy_icon.webp | Bin 0 -> 191948 bytes 20 files changed, 1835 insertions(+), 2313 deletions(-) create mode 100644 src/static/blocks/indicator_blocks.js create mode 100644 src/static/brightertrades_favicon.ico create mode 100644 src/static/trading_strategy_icon.webp diff --git a/archived_code/test_DataCache.py b/archived_code/test_DataCache.py index 88a4a65..46f3489 100644 --- a/archived_code/test_DataCache.py +++ b/archived_code/test_DataCache.py @@ -1,228 +1,27 @@ -from DataCache import DataCache -from ExchangeInterface import ExchangeInterface -import unittest -import pandas as pd -import datetime as dt -import os -from Database import SQLite, Database -from shared_utilities import unix_time_millis +""" +{ + "type": "strategy", + "statements": [ + { + "type": "trade_action", + "trade_type": "buy", + "inputs": { + "size": { + "type": "sqrt", + "inputs": { + "number": { + "type": "power", + "inputs": { + "base": 2, + "exponent": 3 + } + } + } + } + } + } + ] +} -class TestDataCache(unittest.TestCase): - def setUp(self): - # Set the database connection here - self.exchanges = ExchangeInterface() - self.exchanges.connect_exchange(exchange_name='binance', user_name='test_guy', api_keys=None) - # This object maintains all the cached data. Pass it connection to the exchanges. - self.db_file = 'test_db.sqlite' - self.database = Database(db_file=self.db_file) - - # Create necessary tables - with SQLite(db_file=self.db_file) as con: - cursor = con.cursor() - cursor.execute(""" - CREATE TABLE IF NOT EXISTS exchange ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT UNIQUE - ) - """) - cursor.execute(""" - CREATE TABLE IF NOT EXISTS markets ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - symbol TEXT, - exchange_id INTEGER, - FOREIGN KEY (exchange_id) REFERENCES exchange(id) - ) - """) - cursor.execute(""" - CREATE TABLE IF NOT EXISTS test_table ( - id INTEGER PRIMARY KEY, - market_id INTEGER, - time INTEGER UNIQUE, - open REAL NOT NULL, - high REAL NOT NULL, - low REAL NOT NULL, - close REAL NOT NULL, - volume REAL NOT NULL, - FOREIGN KEY (market_id) REFERENCES markets(id) - ) - """) - - self.data = DataCache(self.exchanges) - self.data.db = self.database - - asset, timeframe, exchange = 'BTC/USD', '2h', 'binance' - self.key1 = f'{asset}_{timeframe}_{exchange}' - - asset, timeframe, exchange = 'ETH/USD', '2h', 'binance' - self.key2 = f'{asset}_{timeframe}_{exchange}' - - def tearDown(self): - if os.path.exists(self.db_file): - os.remove(self.db_file) - - def test_set_cache(self): - print('Testing set_cache flag not set:') - self.data.set_cache(data='data', key=self.key1) - attr = self.data.__getattribute__('cached_data') - self.assertEqual(attr[self.key1], 'data') - - self.data.set_cache(data='more_data', key=self.key1) - attr = self.data.__getattribute__('cached_data') - self.assertEqual(attr[self.key1], 'more_data') - - print('Testing set_cache no-overwrite flag set:') - self.data.set_cache(data='even_more_data', key=self.key1, do_not_overwrite=True) - attr = self.data.__getattribute__('cached_data') - self.assertEqual(attr[self.key1], 'more_data') - - def test_cache_exists(self): - print('Testing cache_exists() method:') - self.assertFalse(self.data.cache_exists(key=self.key2)) - self.data.set_cache(data='data', key=self.key1) - self.assertTrue(self.data.cache_exists(key=self.key1)) - - def test_update_candle_cache(self): - print('Testing update_candle_cache() method:') - df_initial = pd.DataFrame({ - 'time': [1, 2, 3], - 'open': [100, 101, 102], - 'high': [110, 111, 112], - 'low': [90, 91, 92], - 'close': [105, 106, 107], - 'volume': [1000, 1001, 1002] - }) - - df_new = pd.DataFrame({ - 'time': [3, 4, 5], - 'open': [102, 103, 104], - 'high': [112, 113, 114], - 'low': [92, 93, 94], - 'close': [107, 108, 109], - 'volume': [1002, 1003, 1004] - }) - - self.data.set_cache(data=df_initial, key=self.key1) - self.data.update_candle_cache(more_records=df_new, key=self.key1) - - result = self.data.get_cache(key=self.key1) - expected = pd.DataFrame({ - 'time': [1, 2, 3, 4, 5], - 'open': [100, 101, 102, 103, 104], - 'high': [110, 111, 112, 113, 114], - 'low': [90, 91, 92, 93, 94], - 'close': [105, 106, 107, 108, 109], - 'volume': [1000, 1001, 1002, 1003, 1004] - }) - - pd.testing.assert_frame_equal(result, expected) - - def test_update_cached_dict(self): - print('Testing update_cached_dict() method:') - self.data.set_cache(data={}, key=self.key1) - self.data.update_cached_dict(cache_key=self.key1, dict_key='sub_key', data='value') - - cache = self.data.get_cache(key=self.key1) - self.assertEqual(cache['sub_key'], 'value') - - def test_get_cache(self): - print('Testing get_cache() method:') - self.data.set_cache(data='data', key=self.key1) - result = self.data.get_cache(key=self.key1) - self.assertEqual(result, 'data') - - def test_get_records_since(self): - print('Testing get_records_since() method:') - df_initial = pd.DataFrame({ - 'time': [unix_time_millis(dt.datetime.utcnow() - dt.timedelta(minutes=i)) for i in range(3)], - 'open': [100, 101, 102], - 'high': [110, 111, 112], - 'low': [90, 91, 92], - 'close': [105, 106, 107], - 'volume': [1000, 1001, 1002] - }) - - self.data.set_cache(data=df_initial, key=self.key1) - start_datetime = dt.datetime.utcnow() - dt.timedelta(minutes=2) - result = self.data.get_records_since(key=self.key1, start_datetime=start_datetime, record_length=60, - ex_details=['BTC/USD', '2h', 'binance']) - - expected = pd.DataFrame({ - 'time': df_initial['time'][:2].values, - 'open': [100, 101], - 'high': [110, 111], - 'low': [90, 91], - 'close': [105, 106], - 'volume': [1000, 1001] - }) - - pd.testing.assert_frame_equal(result, expected) - - def test_get_records_since_from_db(self): - print('Testing get_records_since_from_db() method:') - df_initial = pd.DataFrame({ - 'market_id': [None], - 'time': [unix_time_millis(dt.datetime.utcnow())], - 'open': [1.0], - 'high': [1.0], - 'low': [1.0], - 'close': [1.0], - 'volume': [1.0] - }) - - with SQLite(self.db_file) as con: - df_initial.to_sql('test_table', con, if_exists='append', index=False) - - start_datetime = dt.datetime.utcnow() - dt.timedelta(minutes=1) - end_datetime = dt.datetime.utcnow() - result = self.data.get_records_since_from_db(table_name='test_table', st=start_datetime, et=end_datetime, - rl=1, ex_details=['BTC/USD', '2h', 'binance']).sort_values( - by='time').reset_index(drop=True) - - print("Columns in the result DataFrame:", result.columns) - print("Result DataFrame:\n", result) - - # Remove 'id' column from the result DataFrame if it exists - if 'id' in result.columns: - result = result.drop(columns=['id']) - - expected = pd.DataFrame({ - 'market_id': [None], - 'time': [unix_time_millis(dt.datetime.utcnow())], - 'open': [1.0], - 'high': [1.0], - 'low': [1.0], - 'close': [1.0], - 'volume': [1.0] - }) - - print("Expected DataFrame:\n", expected) - - pd.testing.assert_frame_equal(result, expected) - - def test_populate_db(self): - print('Testing _populate_db() method:') - start_time = dt.datetime.utcnow() - dt.timedelta(days=1) - end_time = dt.datetime.utcnow() - - result = self.data._populate_db(table_name='test_table', start_time=start_time, - end_time=end_time, ex_details=['BTC/USD', '2h', 'binance', 'test_guy']) - - self.assertIsInstance(result, pd.DataFrame) - self.assertFalse(result.empty) - - def test_fetch_candles_from_exchange(self): - print('Testing _fetch_candles_from_exchange() method:') - start_time = dt.datetime.utcnow() - dt.timedelta(days=1) - end_time = dt.datetime.utcnow() - - result = self.data._fetch_candles_from_exchange(symbol='BTC/USD', interval='2h', exchange_name='binance', - user_name='test_guy', start_datetime=start_time, - end_datetime=end_time) - - self.assertIsInstance(result, pd.DataFrame) - self.assertFalse(result.empty) - - -if __name__ == '__main__': - unittest.main() +""" \ No newline at end of file diff --git a/src/static/Strategies.js b/src/static/Strategies.js index d847168..5d8be9e 100644 --- a/src/static/Strategies.js +++ b/src/static/Strategies.js @@ -258,6 +258,8 @@ class StratWorkspaceManager { constructor() { 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 } /** @@ -287,10 +289,10 @@ class StratWorkspaceManager { // Separate json_base_generator as it needs to be loaded first const jsonBaseModule = await import('./blocks/generators/json_base_generator.js'); jsonBaseModule.defineJsonBaseGenerator(); + console.log('Defined defineJsonBaseGenerator from json_base_generator.js'); // Define lists of generator and block files with their corresponding define functions const generatorModules = [ - { file: 'json_base_generator.js', defineFunc: 'defineJsonBaseGenerator' }, { file: 'balances_generators.js', defineFunc: 'defineBalancesGenerators' }, { file: 'order_metrics_generators.js', defineFunc: 'defineOrderMetricsGenerators' }, { file: 'trade_metrics_generators.js', defineFunc: 'defineTradeMetricsGenerators' }, @@ -398,28 +400,33 @@ class StratWorkspaceManager { } /** - * Generates the strategy data including Python code, JSON representation, and workspace XML. - * @returns {string} - A JSON string containing the strategy data. + * Generates the strategy data including, JSON representation, and workspace XML. + * @returns {string|null} - A JSON string containing the strategy data or null if failed. */ compileStrategyJson() { if (!this.workspace) { console.error("Workspace is not available."); - return; + return null; } const nameElement = document.getElementById('name_box'); if (!nameElement) { console.error("Name input element (name_box) is not available."); - return; + return null; } const strategyName = nameElement.value; - // Initialize code generators - Blockly.JSON.init(this.workspace); + // 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(); + if (!strategyJson) { + console.error("Failed to generate strategy JSON."); + return null; + } + // Generate workspace XML for restoration when editing const workspaceXml = Blockly.Xml.workspaceToDom(this.workspace); const workspaceXmlText = Blockly.Xml.domToText(workspaceXml); @@ -431,101 +438,99 @@ 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. */ _generateStrategyJsonFromWorkspace() { - const topBlocks = this.workspace.getTopBlocks(true); - return topBlocks.map(block => this._blockToJson(block)); + const { initializationBlocks, actionBlocks } = this._categorizeOrphanedBlocks(this.workspace); + + const totalTopLevelBlocks = initializationBlocks.length + actionBlocks.length; + + if (totalTopLevelBlocks > this.MAX_TOP_LEVEL_BLOCKS) { + console.error(`Too many top-level blocks. Maximum allowed is ${this.MAX_TOP_LEVEL_BLOCKS}.`); + alert(`Your strategy has too many top-level blocks. Please reduce the number of top-level blocks to ${this.MAX_TOP_LEVEL_BLOCKS} or fewer.`); + return null; + } + + // Proceed with strategy JSON creation + const strategyJson = this._createStrategyJson(initializationBlocks, actionBlocks); + + return strategyJson; } - /** - * Recursively converts a block and its connected blocks into JSON. + /** + * Identify all orphaned blocks on the workspace. Categorize them into initialization blocks and action blocks. * @private - * @param {Blockly.Block} block - The block to convert. - * @returns {Object} - The JSON representation of the block. + * @returns {Object[]} - an object containing an array of initializationBlocks and actionBlocks. */ - _blockToJson(block) { - if (!block) { - return null; + _categorizeOrphanedBlocks(workspace) { + const blocks = workspace.getTopBlocks(true); + const orphanedBlocks = blocks.filter(block => !block.getParent()); + + const initializationBlocks = []; + const actionBlocks = []; + + orphanedBlocks.forEach(block => { + // Only consider specified block types + switch (block.type) { + // Initialization block types (run first) + case 'set_available_strategy_balance': + case 'set_variable': + case 'set_flag': + case 'set_leverage': + case 'pause_strategy': + case 'strategy_resume': + case 'strategy_exit': + case 'notify_user': + initializationBlocks.push(block); + break; + + // Action block types (run last) + case 'trade_action': + case 'schedule_action': + case 'execute_if': + actionBlocks.push(block); + break; + + // Ignore other blocks or log a warning + default: + console.warn(`Block type '${block.type}' is not allowed as a top-level block and will be ignored.`); + break; } + }); - // Use the block's JSON generator if it exists - if (Blockly.JSON && Blockly.JSON[block.type]) { - // Generate JSON string using the block's JSON generator - const jsonString = Blockly.JSON[block.type](block); - // Parse the JSON string into an object - const json = JSON.parse(jsonString); + return { initializationBlocks, actionBlocks }; + } - // Process inputs - block.inputList.forEach(input => { - if (input.connection && input.connection.targetBlock()) { - const targetBlock = input.connection.targetBlock(); - const targetJson = this._blockToJson(targetBlock); + _createStrategyJson(initializationBlocks, actionBlocks) { + const statements = []; - if (input.type === Blockly.INPUT_VALUE) { - if (!json.inputs) json.inputs = {}; - json.inputs[input.name] = targetJson; - } 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); - } - } - }); - - // Handle the next connected block at the same level - if (block.getNextBlock()) { - const nextBlock = this._blockToJson(block.getNextBlock()); - json.next = nextBlock; - } - - return json; - } else { - // Fallback: Manually construct JSON if no JSON generator exists - const json = { - type: block.type, - fields: {}, - inputs: {}, - statements: {} - }; - - // 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(); - } - }); - } - }); - - // Process inputs - block.inputList.forEach(input => { - if (input.connection && input.connection.targetBlock()) { - const targetBlock = input.connection.targetBlock(); - const targetJson = this._blockToJson(targetBlock); - - if (input.type === Blockly.INPUT_VALUE) { - json.inputs[input.name] = targetJson; - } else if (input.type === Blockly.NEXT_STATEMENT) { - if (!json.statements[input.name]) json.statements[input.name] = []; - json.statements[input.name].push(targetJson); - } - } - }); - - // Handle the next connected block at the same level - if (block.getNextBlock()) { - const nextBlock = this._blockToJson(block.getNextBlock()); - json.next = nextBlock; - } - - return json; + // Process initialization blocks (run first) + initializationBlocks.forEach(block => { + const blockJson = Blockly.JSON._blockToJson(block); + if (blockJson) { + statements.push(blockJson); } + }); + + // Process action blocks (run last) + actionBlocks.forEach(block => { + const blockJson = Blockly.JSON._blockToJson(block); + if (blockJson) { + statements.push(blockJson); + } + }); + + // Create the root strategy block + const strategyJson = { + type: 'strategy', + statements: statements + }; + + return strategyJson; } /** @@ -751,6 +756,12 @@ class Strategies { const compiledStrategy = this.generateStrategyJson(); // Returns JSON string const parsedStrategy = JSON.parse(compiledStrategy); // Object with 'name', 'strategy_json', 'workspace' + // Check for an incomplete strategy (no root blocks) + if (!parsedStrategy.strategy_json.statements || parsedStrategy.strategy_json.statements.length === 0) { + alert("Your strategy is incomplete. Please add actions or conditions before submitting."); + return; + } + // Prepare the strategy data to send strategyData = { code: parsedStrategy.strategy_json, // The compiled strategy JSON string diff --git a/src/static/blocks/blocks/advanced_math_blocks.js b/src/static/blocks/blocks/advanced_math_blocks.js index c214422..4aece06 100644 --- a/src/static/blocks/blocks/advanced_math_blocks.js +++ b/src/static/blocks/blocks/advanced_math_blocks.js @@ -27,6 +27,7 @@ export function defineAdvancedMathBlocks() { } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Raise a connected number to a power. Attach a second value to specify the exponent.", "helpUrl": "" @@ -48,6 +49,7 @@ export function defineAdvancedMathBlocks() { } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Find the remainder after division. Attach a second value to specify the divisor.", "helpUrl": "" @@ -64,11 +66,12 @@ export function defineAdvancedMathBlocks() { "args0": [ { "type": "input_value", - "name": "NUMBER", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Calculate the square root of a connected number.", "helpUrl": "" @@ -85,11 +88,12 @@ export function defineAdvancedMathBlocks() { "args0": [ { "type": "input_value", - "name": "NUMBER", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Get the absolute value of a connected number.", "helpUrl": "" @@ -106,11 +110,12 @@ export function defineAdvancedMathBlocks() { "args0": [ { "type": "input_value", - "name": "NUMBERS", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Determine the maximum value among connected numbers.", "helpUrl": "" @@ -127,11 +132,12 @@ export function defineAdvancedMathBlocks() { "args0": [ { "type": "input_value", - "name": "NUMBERS", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Determine the minimum value among connected numbers.", "helpUrl": "" @@ -148,11 +154,12 @@ export function defineAdvancedMathBlocks() { "args0": [ { "type": "input_value", - "name": "NUMBER", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Calculate the factorial of a connected number.", "helpUrl": "" @@ -169,11 +176,12 @@ export function defineAdvancedMathBlocks() { "args0": [ { "type": "input_value", - "name": "NUMBER", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Calculate the base 10 logarithm of a connected number.", "helpUrl": "" @@ -190,11 +198,12 @@ export function defineAdvancedMathBlocks() { "args0": [ { "type": "input_value", - "name": "NUMBER", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Calculate the natural logarithm of a connected number.", "helpUrl": "" @@ -220,11 +229,12 @@ export function defineAdvancedMathBlocks() { }, { "type": "input_value", - "name": "ANGLE", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Calculate the sine, cosine, or tangent of a connected angle (in radians).", "helpUrl": "" @@ -241,11 +251,12 @@ export function defineAdvancedMathBlocks() { "args0": [ { "type": "input_value", - "name": "NUMBERS", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Calculate the mean of connected numbers.", "helpUrl": "" @@ -262,11 +273,12 @@ export function defineAdvancedMathBlocks() { "args0": [ { "type": "input_value", - "name": "NUMBERS", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Calculate the median of connected numbers.", "helpUrl": "" @@ -283,11 +295,12 @@ export function defineAdvancedMathBlocks() { "args0": [ { "type": "input_value", - "name": "NUMBERS", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Calculate the standard deviation of connected numbers.", "helpUrl": "" @@ -309,6 +322,7 @@ export function defineAdvancedMathBlocks() { } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Round a connected number. Attach a second value to specify the decimal places.", "helpUrl": "" @@ -325,11 +339,12 @@ export function defineAdvancedMathBlocks() { "args0": [ { "type": "input_value", - "name": "NUMBER", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Round down a connected number to the nearest integer.", "helpUrl": "" @@ -346,11 +361,12 @@ export function defineAdvancedMathBlocks() { "args0": [ { "type": "input_value", - "name": "NUMBER", + "name": "VALUES", "check": "dynamic_value" } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Round up a connected number to the nearest integer.", "helpUrl": "" @@ -372,6 +388,7 @@ export function defineAdvancedMathBlocks() { } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Generate a random number. Attach one value to specify the maximum, or two value to specify minimum(first value) and maximum(second value).", "helpUrl": "" @@ -393,6 +410,7 @@ export function defineAdvancedMathBlocks() { } ], "output": "dynamic_value", + "maxNestingDepth": 3, "colour": 60, "tooltip": "Clamp a connected number. Attach a second value for minimum and a third for maximum.", "helpUrl": "" @@ -430,6 +448,7 @@ export function defineAdvancedMathBlocks() { ], "inputsInline": true, "output": "dynamic_value", + "maxNestingDepth": 5, "colour": 160, "tooltip": "Perform basic arithmetic operations between two connected numbers.", "helpUrl": "" diff --git a/src/static/blocks/blocks/balances_blocks.js b/src/static/blocks/blocks/balances_blocks.js index c825ab6..d4e5708 100644 --- a/src/static/blocks/blocks/balances_blocks.js +++ b/src/static/blocks/blocks/balances_blocks.js @@ -1,108 +1,107 @@ -// balances_blocks.js - /** * Balances Subcategory Blocks Definitions + * + * Note: + * - All blocks except 'set_available_strategy_balance' accept a 'dynamic_value' input named 'VALUES'. + * - These blocks output a 'dynamic_value' containing the result and any unused input values. + * - The 'set_available_strategy_balance' block is a statement block that accepts a 'dynamic_value' input or a number. */ + export function defineBalanceBlocks() { - /* Register a new type for dynamic value. A block that returns a value may - * require an input of 0 to n values. If the block receives a dynamic value, the dynamic value may - * be a single value or a list of values. If the block expects n values it will pop n values from - * the beginning of the list and use them. The blocks result(s) will be returned inside a list - * appended by the remaining values from the original list. - */ + // Register the 'dynamic_value' type if not already registered Blockly.Types = Blockly.Types || {}; Blockly.Types.DYNAMIC_VALUE = 'dynamic_value'; - /* - * Returns the balance the strategy had when it first started. Returns a - * single value or a list of values where this first value is the output - * of this block and the following are forwarded values from other blocks. - */ + /** + * Starting Balance Block + * + * Retrieves the starting balance of the strategy. + */ Blockly.defineBlocksWithJsonArray([{ "type": "starting_balance", "message0": "Starting Balance %1", "args0": [ - { - "type": "input_value", - "name": "NEXT", - "check": "dynamic_value" - } - ], + { + "type": "input_value", + "name": "VALUES", + "check": "dynamic_value" + } + ], "output": "dynamic_value", "colour": 230, - "tooltip": "Retrieve the starting balance of the strategy.", + "tooltip": "Retrieve the starting balance of the strategy. Attach additional values to forward them.", "helpUrl": "" }]); - /* - * Returns the current balance of the user. Returns a single value - * or a list of values where this first value is the output of this - * block and the following are forwarded values from other blocks. - */ + /** + * Current Balance Block + * + * Retrieves the current balance of the strategy. + */ Blockly.defineBlocksWithJsonArray([{ "type": "current_balance", "message0": "Current Balance %1", "args0": [ - { - "type": "input_value", - "name": "NEXT", - "check": "dynamic_value" - } - ], + { + "type": "input_value", + "name": "VALUES", + "check": "dynamic_value" + } + ], "output": "dynamic_value", "colour": 230, - "tooltip": "Retrieve the current balance of the strategy.", + "tooltip": "Retrieve the current balance of the strategy. Attach additional values to forward them.", "helpUrl": "" }]); - /* - * Returns the available balance of the user. Returns a single value - * or a list of values where this first value is the output of this - * block and the following are forwarded values from other blocks. - */ + /** + * Available Balance Block + * + * Retrieves the overall available (liquid) balance of the user. + */ Blockly.defineBlocksWithJsonArray([{ "type": "available_balance", "message0": "Available Balance %1", "args0": [ - { - "type": "input_value", - "name": "NEXT", - "check": "dynamic_value" // Accepts single or list of values - } - ], + { + "type": "input_value", + "name": "VALUES", + "check": "dynamic_value" + } + ], "output": "dynamic_value", "colour": 230, - "tooltip": "Retrieve the overall available (liquid) balance of the user.", + "tooltip": "Retrieve the overall available (liquid) balance. Attach additional values to forward them.", "helpUrl": "" }]); - /* - * Returns the available balance of the strategy. Returns a single value - * or a list of values where this first value is the output of this - * block and the following are forwarded values from other blocks. - */ + /** + * Available Strategy Balance Block + * + * Retrieves the available balance allocated specifically to the strategy. + */ Blockly.defineBlocksWithJsonArray([{ "type": "available_strategy_balance", "message0": "Available Strategy Balance %1", "args0": [ - { - "type": "input_value", - "name": "NEXT", - "check": "dynamic_value" - } - ], + { + "type": "input_value", + "name": "VALUES", + "check": "dynamic_value" + } + ], "output": "dynamic_value", "colour": 230, - "tooltip": "Retrieve the available balance allocated specifically to the strategy.", + "tooltip": "Retrieve the available balance allocated to the strategy. Attach additional values to forward them.", "helpUrl": "" }]); - /* - * Sets the available balance of the strategy. Receives a single value - * or a list of values where this first value is the only value that is - * considered and all others values are ignored. - */ + /** + * Set Available Strategy Balance Block + * + * Sets the balance allocated to the strategy. + */ Blockly.defineBlocksWithJsonArray([{ "type": "set_available_strategy_balance", "message0": "Set Available Strategy Balance to %1", @@ -116,7 +115,7 @@ export function defineBalanceBlocks() { "previousStatement": null, "nextStatement": null, "colour": 230, - "tooltip": "Set the balance allocated to the strategy.", + "tooltip": "Set the balance allocated to the strategy. Accepts a number or the first value from a dynamic list.", "helpUrl": "" }]); -} \ No newline at end of file +} diff --git a/src/static/blocks/blocks/logical_blocks.js b/src/static/blocks/blocks/logical_blocks.js index 9ccfcd2..71e295a 100644 --- a/src/static/blocks/blocks/logical_blocks.js +++ b/src/static/blocks/blocks/logical_blocks.js @@ -27,13 +27,13 @@ export function defineLogicalBlocks() { "args0": [ { "type": "input_value", - "name": "left", + "name": "LEFT", "check": ["number", "dynamic_value"], "align": "RIGHT" }, { "type": "field_dropdown", - "name": "operator", + "name": "OPERATOR", "options": [ [">", ">"], ["<", "<"], @@ -42,7 +42,7 @@ export function defineLogicalBlocks() { }, { "type": "input_value", - "name": "right", + "name": "RIGHT", "check": ["number", "dynamic_value"], "align": "LEFT" } diff --git a/src/static/blocks/generators/advanced_math_generators.js b/src/static/blocks/generators/advanced_math_generators.js index d53aae0..2be8ff8 100644 --- a/src/static/blocks/generators/advanced_math_generators.js +++ b/src/static/blocks/generators/advanced_math_generators.js @@ -1,335 +1,268 @@ /** * Advanced Math Generators Definitions * - * Note: - * - All generators correspond to their respective blocks in 'advanced_math_blocks.js'. - * - Each generator processes the input 'dynamic_value' according to the block's specifications. - * - If more values are provided than needed, unused values are included in the output. - * - The output is a JSON string representing a 'dynamic_value' containing the result and any unused input values. + * This file defines the generator functions for advanced math blocks. + * Each generator corresponds to a block defined in 'advanced_math_blocks.js'. + * The generators handle a single 'VALUES' input, which is a 'dynamic_value' containing values. + * The output is a 'dynamic_value' containing the result operation object and any unused values. */ export function defineAdvancedMathGenerators() { + // 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."); + 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 valuesCode = Blockly.JSON.valueToCode(block, 'VALUES', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(valuesCode); + const valuesArray = processValuesInput(block); - let valuesArray = []; - if (Array.isArray(values)) { - valuesArray = values; - } else if (typeof values === 'number') { - valuesArray = [values]; - } else { - // No values provided; result is invalid - return JSON.stringify({ type: 'power', error: 'No values provided' }); - } + const base = valuesArray[0] !== undefined ? valuesArray[0] : 0; + const exponent = valuesArray[1] !== undefined ? valuesArray[1] : 1; - const base = valuesArray[0]; - const exponent = valuesArray.length >= 2 ? valuesArray[1] : 1; - - // Validate base and exponent - if (typeof base !== 'number') { - return JSON.stringify({ type: 'power', error: 'Invalid base value' }); - } - if (typeof exponent !== 'number') { - return JSON.stringify({ type: 'power', error: 'Invalid exponent value' }); - } - - const result = Math.pow(base, exponent); + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('power', { + base: base, + exponent: exponent + }); // Include unused values if any - const outputValues = [result]; + const outputValues = [operationObject]; if (valuesArray.length > 2) { outputValues.push(...valuesArray.slice(2)); } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** * Generator for 'modulo' Block */ Blockly.JSON['modulo'] = function(block) { - const valuesCode = Blockly.JSON.valueToCode(block, 'VALUES', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(valuesCode); + const valuesArray = processValuesInput(block); - let valuesArray = []; - if (Array.isArray(values)) { - valuesArray = values; - } else if (typeof values === 'number') { - valuesArray = [values]; - } else { - return JSON.stringify({ type: 'modulo', error: 'No values provided' }); - } + const dividend = valuesArray[0] !== undefined ? valuesArray[0] : 0; + const divisor = valuesArray[1] !== undefined ? valuesArray[1] : 1; - const dividend = valuesArray[0]; - const divisor = valuesArray.length >= 2 ? valuesArray[1] : 1; - - // Validate inputs - if (typeof dividend !== 'number') { - return JSON.stringify({ type: 'modulo', error: 'Invalid dividend' }); - } - if (typeof divisor !== 'number') { - return JSON.stringify({ type: 'modulo', error: 'Invalid divisor' }); - } - - const result = dividend % divisor; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('modulo', { + dividend: dividend, + divisor: divisor + }); // Include unused values if any - const outputValues = [result]; + const outputValues = [operationObject]; if (valuesArray.length > 2) { outputValues.push(...valuesArray.slice(2)); } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** * Generator for 'sqrt' Block */ Blockly.JSON['sqrt'] = function(block) { - const numberCode = Blockly.JSON.valueToCode(block, 'NUMBER', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(numberCode); + const valuesArray = processValuesInput(block); - let number; - let unusedValues = []; - if (Array.isArray(values)) { - number = values[0]; - if (values.length > 1) { - unusedValues = values.slice(1); - } - } else if (typeof values === 'number') { - number = values; - } else { - return JSON.stringify({ type: 'sqrt', error: 'No number provided' }); - } + const number = valuesArray[0] !== undefined ? valuesArray[0] : 0; - if (typeof number !== 'number') { - return JSON.stringify({ type: 'sqrt', error: 'Invalid number' }); - } - - const result = Math.sqrt(number); + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('sqrt', { + number: number + }); // Include unused values if any - const outputValues = [result, ...unusedValues]; + const outputValues = [operationObject]; + if (valuesArray.length > 1) { + outputValues.push(...valuesArray.slice(1)); + } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** * Generator for 'abs' Block */ Blockly.JSON['abs'] = function(block) { - const numberCode = Blockly.JSON.valueToCode(block, 'NUMBER', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(numberCode); + const valuesArray = processValuesInput(block); - let number; - let unusedValues = []; - if (Array.isArray(values)) { - number = values[0]; - if (values.length > 1) { - unusedValues = values.slice(1); - } - } else if (typeof values === 'number') { - number = values; - } else { - return JSON.stringify({ type: 'abs', error: 'No number provided' }); - } + const number = valuesArray[0] !== undefined ? valuesArray[0] : 0; - if (typeof number !== 'number') { - return JSON.stringify({ type: 'abs', error: 'Invalid number' }); - } - - const result = Math.abs(number); + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('abs', { + number: number + }); // Include unused values if any - const outputValues = [result, ...unusedValues]; + const outputValues = [operationObject]; + if (valuesArray.length > 1) { + outputValues.push(...valuesArray.slice(1)); + } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** * Generator for 'max' Block */ Blockly.JSON['max'] = function(block) { - const numbersCode = Blockly.JSON.valueToCode(block, 'NUMBERS', Blockly.JSON.ORDER_ATOMIC); - const numbers = Blockly.JSON.safeParse(numbersCode); + const valuesArray = processValuesInput(block); - let valuesArray = []; - if (Array.isArray(numbers)) { - valuesArray = numbers; - } else if (typeof numbers === 'number') { - valuesArray = [numbers]; - } else { - return JSON.stringify({ type: 'max', error: 'No numbers provided' }); - } - - // Validate numbers - const validNumbers = valuesArray.filter(value => typeof value === 'number'); - if (validNumbers.length === 0) { - return JSON.stringify({ type: 'max', error: 'No valid numbers provided' }); - } - - const result = Math.max(...validNumbers); - - return JSON.stringify({ - type: 'dynamic_value', - values: [result] + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('max', { + numbers: valuesArray }); + + // Output as dynamic_value + return { + type: 'dynamic_value', + values: [operationObject] + }; }; /** * Generator for 'min' Block */ Blockly.JSON['min'] = function(block) { - const numbersCode = Blockly.JSON.valueToCode(block, 'NUMBERS', Blockly.JSON.ORDER_ATOMIC); - const numbers = Blockly.JSON.safeParse(numbersCode); + const valuesArray = processValuesInput(block); - let valuesArray = []; - if (Array.isArray(numbers)) { - valuesArray = numbers; - } else if (typeof numbers === 'number') { - valuesArray = [numbers]; - } else { - return JSON.stringify({ type: 'min', error: 'No numbers provided' }); - } - - // Validate numbers - const validNumbers = valuesArray.filter(value => typeof value === 'number'); - if (validNumbers.length === 0) { - return JSON.stringify({ type: 'min', error: 'No valid numbers provided' }); - } - - const result = Math.min(...validNumbers); - - return JSON.stringify({ - type: 'dynamic_value', - values: [result] + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('min', { + numbers: valuesArray }); + + // Output as dynamic_value + return { + type: 'dynamic_value', + values: [operationObject] + }; }; /** * Generator for 'factorial' Block */ Blockly.JSON['factorial'] = function(block) { - const numberCode = Blockly.JSON.valueToCode(block, 'NUMBER', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(numberCode); + const valuesArray = processValuesInput(block); - let number; - let unusedValues = []; - if (Array.isArray(values)) { - number = values[0]; - if (values.length > 1) { - unusedValues = values.slice(1); - } - } else if (typeof values === 'number') { - number = values; - } else { - return JSON.stringify({ type: 'factorial', error: 'No number provided' }); - } + const number = valuesArray[0] !== undefined ? valuesArray[0] : 1; - if (typeof number !== 'number' || !Number.isInteger(number) || number < 0) { - return JSON.stringify({ type: 'factorial', error: 'Invalid number (must be a non-negative integer)' }); - } - - // Calculate factorial - let result = 1; - for (let i = 2; i <= number; i++) { - result *= i; - } + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('factorial', { + number: number + }); // Include unused values if any - const outputValues = [result, ...unusedValues]; + const outputValues = [operationObject]; + if (valuesArray.length > 1) { + outputValues.push(...valuesArray.slice(1)); + } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** * Generator for 'log' Block */ Blockly.JSON['log'] = function(block) { - const numberCode = Blockly.JSON.valueToCode(block, 'NUMBER', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(numberCode); + const valuesArray = processValuesInput(block); - let number; - let unusedValues = []; - if (Array.isArray(values)) { - number = values[0]; - if (values.length > 1) { - unusedValues = values.slice(1); - } - } else if (typeof values === 'number') { - number = values; - } else { - return JSON.stringify({ type: 'log', error: 'No number provided' }); - } + const number = valuesArray[0] !== undefined ? valuesArray[0] : 1; - if (typeof number !== 'number' || number <= 0) { - return JSON.stringify({ type: 'log', error: 'Invalid number (must be positive)' }); - } - - const result = Math.log10(number); + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('log', { + number: number + }); // Include unused values if any - const outputValues = [result, ...unusedValues]; + const outputValues = [operationObject]; + if (valuesArray.length > 1) { + outputValues.push(...valuesArray.slice(1)); + } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** * Generator for 'ln' Block */ Blockly.JSON['ln'] = function(block) { - const numberCode = Blockly.JSON.valueToCode(block, 'NUMBER', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(numberCode); + const valuesArray = processValuesInput(block); - let number; - let unusedValues = []; - if (Array.isArray(values)) { - number = values[0]; - if (values.length > 1) { - unusedValues = values.slice(1); - } - } else if (typeof values === 'number') { - number = values; - } else { - return JSON.stringify({ type: 'ln', error: 'No number provided' }); - } + const number = valuesArray[0] !== undefined ? valuesArray[0] : 1; - if (typeof number !== 'number' || number <= 0) { - return JSON.stringify({ type: 'ln', error: 'Invalid number (must be positive)' }); - } - - const result = Math.log(number); + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('ln', { + number: number + }); // Include unused values if any - const outputValues = [result, ...unusedValues]; + const outputValues = [operationObject]; + if (valuesArray.length > 1) { + outputValues.push(...valuesArray.slice(1)); + } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** @@ -337,417 +270,282 @@ export function defineAdvancedMathGenerators() { */ Blockly.JSON['trig'] = function(block) { const operator = block.getFieldValue('operator'); // 'sin', 'cos', 'tan' - const angleCode = Blockly.JSON.valueToCode(block, 'ANGLE', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(angleCode); + const valuesArray = processValuesInput(block); - let angle; - let unusedValues = []; - if (Array.isArray(values)) { - angle = values[0]; - if (values.length > 1) { - unusedValues = values.slice(1); - } - } else if (typeof values === 'number') { - angle = values; - } else { - return JSON.stringify({ type: 'trig', error: 'No angle provided' }); - } + const angle = valuesArray[0] !== undefined ? valuesArray[0] : 0; - if (typeof angle !== 'number') { - return JSON.stringify({ type: 'trig', error: 'Invalid angle' }); + // Validate operator + const validOperators = ['sin', 'cos', 'tan']; + if (!validOperators.includes(operator)) { + console.error(`Invalid operator '${operator}' in 'trig' block. Defaulting to 'sin'.`); } + const validatedOperator = validOperators.includes(operator) ? operator : 'sin'; - let result; - switch (operator) { - case 'sin': - result = Math.sin(angle); - break; - case 'cos': - result = Math.cos(angle); - break; - case 'tan': - result = Math.tan(angle); - break; - default: - return JSON.stringify({ type: 'trig', error: 'Invalid operator' }); - } + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('trig', { + operator: validatedOperator, + angle: angle + }); // Include unused values if any - const outputValues = [result, ...unusedValues]; + const outputValues = [operationObject]; + if (valuesArray.length > 1) { + outputValues.push(...valuesArray.slice(1)); + } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** * Generator for 'mean' Block */ Blockly.JSON['mean'] = function(block) { - const numbersCode = Blockly.JSON.valueToCode(block, 'NUMBERS', Blockly.JSON.ORDER_ATOMIC); - const numbers = Blockly.JSON.safeParse(numbersCode); + const valuesArray = processValuesInput(block); - let valuesArray = []; - if (Array.isArray(numbers)) { - valuesArray = numbers; - } else if (typeof numbers === 'number') { - valuesArray = [numbers]; - } else { - return JSON.stringify({ type: 'mean', error: 'No numbers provided' }); - } - - // Validate numbers - const validNumbers = valuesArray.filter(value => typeof value === 'number'); - if (validNumbers.length === 0) { - return JSON.stringify({ type: 'mean', error: 'No valid numbers provided' }); - } - - const sum = validNumbers.reduce((acc, val) => acc + val, 0); - const result = sum / validNumbers.length; - - return JSON.stringify({ - type: 'dynamic_value', - values: [result] + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('mean', { + numbers: valuesArray }); + + // Output as dynamic_value + return { + type: 'dynamic_value', + values: [operationObject] + }; }; /** * Generator for 'median' Block */ Blockly.JSON['median'] = function(block) { - const numbersCode = Blockly.JSON.valueToCode(block, 'NUMBERS', Blockly.JSON.ORDER_ATOMIC); - const numbers = Blockly.JSON.safeParse(numbersCode); + const valuesArray = processValuesInput(block); - let valuesArray = []; - if (Array.isArray(numbers)) { - valuesArray = numbers; - } else if (typeof numbers === 'number') { - valuesArray = [numbers]; - } else { - return JSON.stringify({ type: 'median', error: 'No numbers provided' }); - } - - // Validate numbers - const validNumbers = valuesArray.filter(value => typeof value === 'number').sort((a, b) => a - b); - if (validNumbers.length === 0) { - return JSON.stringify({ type: 'median', error: 'No valid numbers provided' }); - } - - const middle = Math.floor(validNumbers.length / 2); - let result; - if (validNumbers.length % 2 === 0) { - result = (validNumbers[middle - 1] + validNumbers[middle]) / 2; - } else { - result = validNumbers[middle]; - } - - return JSON.stringify({ - type: 'dynamic_value', - values: [result] + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('median', { + numbers: valuesArray }); + + // Output as dynamic_value + return { + type: 'dynamic_value', + values: [operationObject] + }; }; /** * Generator for 'std_dev' Block */ Blockly.JSON['std_dev'] = function(block) { - const numbersCode = Blockly.JSON.valueToCode(block, 'NUMBERS', Blockly.JSON.ORDER_ATOMIC); - const numbers = Blockly.JSON.safeParse(numbersCode); + const valuesArray = processValuesInput(block); - let valuesArray = []; - if (Array.isArray(numbers)) { - valuesArray = numbers; - } else if (typeof numbers === 'number') { - valuesArray = [numbers]; - } else { - return JSON.stringify({ type: 'std_dev', error: 'No numbers provided' }); - } - - // Validate numbers - const validNumbers = valuesArray.filter(value => typeof value === 'number'); - if (validNumbers.length === 0) { - return JSON.stringify({ type: 'std_dev', error: 'No valid numbers provided' }); - } - - const mean = validNumbers.reduce((acc, val) => acc + val, 0) / validNumbers.length; - const variance = validNumbers.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / validNumbers.length; - const result = Math.sqrt(variance); - - return JSON.stringify({ - type: 'dynamic_value', - values: [result] + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('std_dev', { + numbers: valuesArray }); + + // Output as dynamic_value + return { + type: 'dynamic_value', + values: [operationObject] + }; }; /** * Generator for 'round' Block */ Blockly.JSON['round'] = function(block) { - const valuesCode = Blockly.JSON.valueToCode(block, 'VALUES', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(valuesCode); + const valuesArray = processValuesInput(block); - let valuesArray = []; - if (Array.isArray(values)) { - valuesArray = values; - } else if (typeof values === 'number') { - valuesArray = [values]; - } else { - return JSON.stringify({ type: 'round', error: 'No values provided' }); - } + const number = valuesArray[0] !== undefined ? valuesArray[0] : 0; + const decimals = valuesArray[1] !== undefined ? valuesArray[1] : 0; - const number = valuesArray[0]; - const decimals = valuesArray.length >= 2 ? valuesArray[1] : 0; + // Validate decimals + const validatedDecimals = Number.isInteger(decimals) && decimals >= 0 ? decimals : 0; - // Validate inputs - if (typeof number !== 'number') { - return JSON.stringify({ type: 'round', error: 'Invalid number to round' }); - } - if (typeof decimals !== 'number') { - return JSON.stringify({ type: 'round', error: 'Invalid decimal places' }); - } - - const factor = Math.pow(10, decimals); - const result = Math.round(number * factor) / factor; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('round', { + number: number, + decimals: validatedDecimals + }); // Include unused values if any - const outputValues = [result]; + const outputValues = [operationObject]; if (valuesArray.length > 2) { outputValues.push(...valuesArray.slice(2)); } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** * Generator for 'floor' Block */ Blockly.JSON['floor'] = function(block) { - const numberCode = Blockly.JSON.valueToCode(block, 'NUMBER', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(numberCode); + const valuesArray = processValuesInput(block); - let number; - let unusedValues = []; - if (Array.isArray(values)) { - number = values[0]; - if (values.length > 1) { - unusedValues = values.slice(1); - } - } else if (typeof values === 'number') { - number = values; - } else { - return JSON.stringify({ type: 'floor', error: 'No number provided' }); - } + const number = valuesArray[0] !== undefined ? valuesArray[0] : 0; - if (typeof number !== 'number') { - return JSON.stringify({ type: 'floor', error: 'Invalid number' }); - } - - const result = Math.floor(number); + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('floor', { + number: number + }); // Include unused values if any - const outputValues = [result, ...unusedValues]; + const outputValues = [operationObject]; + if (valuesArray.length > 1) { + outputValues.push(...valuesArray.slice(1)); + } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** * Generator for 'ceil' Block */ Blockly.JSON['ceil'] = function(block) { - const numberCode = Blockly.JSON.valueToCode(block, 'NUMBER', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(numberCode); + const valuesArray = processValuesInput(block); - let number; - let unusedValues = []; - if (Array.isArray(values)) { - number = values[0]; - if (values.length > 1) { - unusedValues = values.slice(1); - } - } else if (typeof values === 'number') { - number = values; - } else { - return JSON.stringify({ type: 'ceil', error: 'No number provided' }); - } + const number = valuesArray[0] !== undefined ? valuesArray[0] : 0; - if (typeof number !== 'number') { - return JSON.stringify({ type: 'ceil', error: 'Invalid number' }); - } - - const result = Math.ceil(number); + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('ceil', { + number: number + }); // Include unused values if any - const outputValues = [result, ...unusedValues]; + const outputValues = [operationObject]; + if (valuesArray.length > 1) { + outputValues.push(...valuesArray.slice(1)); + } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** * Generator for 'random' Block */ Blockly.JSON['random'] = function(block) { - const valuesCode = Blockly.JSON.valueToCode(block, 'VALUES', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(valuesCode); - - let valuesArray = []; - if (Array.isArray(values)) { - valuesArray = values; - } else if (typeof values === 'number') { - valuesArray = [values]; - } else { - return JSON.stringify({ type: 'random', error: 'No values provided' }); - } + const valuesArray = processValuesInput(block); let min, max; - if (valuesArray.length === 1) { + if (valuesArray.length >= 2) { + min = valuesArray[0]; + max = valuesArray[1]; + } else if (valuesArray.length === 1) { min = 0; max = valuesArray[0]; } else { - min = valuesArray[0]; - max = valuesArray[1]; + min = 0; + max = 1; } - // Validate inputs - if (typeof min !== 'number' || typeof max !== 'number') { - return JSON.stringify({ type: 'random', error: 'Invalid range values' }); - } - if (min > max) { - return JSON.stringify({ type: 'random', error: 'Minimum value cannot be greater than maximum value' }); - } + // Validate min and max + const validatedMin = typeof min === 'number' ? min : 0; + const validatedMax = typeof max === 'number' ? max : 1; - const result = Math.random() * (max - min) + min; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('random', { + min: validatedMin, + max: validatedMax + }); // Include unused values if any - const outputValues = [result]; + const outputValues = [operationObject]; if (valuesArray.length > 2) { outputValues.push(...valuesArray.slice(2)); } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** * Generator for 'clamp' Block */ Blockly.JSON['clamp'] = function(block) { - const valuesCode = Blockly.JSON.valueToCode(block, 'VALUES', Blockly.JSON.ORDER_ATOMIC); - const values = Blockly.JSON.safeParse(valuesCode); + const valuesArray = processValuesInput(block); - let valuesArray = []; - if (Array.isArray(values)) { - valuesArray = values; - } else if (typeof values === 'number') { - valuesArray = [values]; - } else { - return JSON.stringify({ type: 'clamp', error: 'No values provided' }); - } + const number = valuesArray[0] !== undefined ? valuesArray[0] : 0; + const min = valuesArray[1] !== undefined ? valuesArray[1] : null; + const max = valuesArray[2] !== undefined ? valuesArray[2] : null; - const number = valuesArray[0]; - const min = valuesArray.length >= 2 ? valuesArray[1] : null; - const max = valuesArray.length >= 3 ? valuesArray[2] : null; + // Validate min and max + const validatedMin = typeof min === 'number' ? min : null; + const validatedMax = typeof max === 'number' ? max : null; - // Validate input number - if (typeof number !== 'number') { - return JSON.stringify({ type: 'clamp', error: 'Invalid number to clamp' }); - } - - let clampedValue = number; - - // Apply minimum if provided - if (typeof min === 'number') { - clampedValue = Math.max(min, clampedValue); - } - - // Apply maximum if provided - if (typeof max === 'number') { - clampedValue = Math.min(max, clampedValue); - } + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('clamp', { + number: number, + min: validatedMin, + max: validatedMax + }); // Include unused values if any - const outputValues = [clampedValue]; + const outputValues = [operationObject]; if (valuesArray.length > 3) { outputValues.push(...valuesArray.slice(3)); } - return JSON.stringify({ + // Output as dynamic_value + return { type: 'dynamic_value', values: outputValues - }); + }; }; /** * Generator for 'math_operation' Block - * - * Note: This block handles inputs differently, with two separate inputs. */ Blockly.JSON['math_operation'] = function(block) { - const leftCode = Blockly.JSON.valueToCode(block, 'LEFT', Blockly.JSON.ORDER_ATOMIC); - const rightCode = Blockly.JSON.valueToCode(block, 'RIGHT', Blockly.JSON.ORDER_ATOMIC); const operator = block.getFieldValue('operator'); // 'ADD', 'SUBTRACT', 'MULTIPLY', 'DIVIDE' + const valuesArray = processValuesInput(block); - const leftValues = Blockly.JSON.safeParse(leftCode); - const rightValues = Blockly.JSON.safeParse(rightCode); + const leftOperand = valuesArray[0] !== undefined ? valuesArray[0] : 0; + const rightOperand = valuesArray[1] !== undefined ? valuesArray[1] : 0; - let left; - if (Array.isArray(leftValues)) { - left = leftValues[0]; - } else if (typeof leftValues === 'number') { - left = leftValues; - } else { - left = 0; + // Validate operator + const validOperators = ['ADD', 'SUBTRACT', 'MULTIPLY', 'DIVIDE']; + if (!validOperators.includes(operator)) { + console.error(`Invalid operator '${operator}' in 'math_operation' block. Defaulting to 'ADD'.`); } + const validatedOperator = validOperators.includes(operator) ? operator : 'ADD'; - let right; - if (Array.isArray(rightValues)) { - right = rightValues[0]; - } else if (typeof rightValues === 'number') { - right = rightValues; - } else { - right = 0; - } - - // Validate inputs - if (typeof left !== 'number' || typeof right !== 'number') { - return JSON.stringify({ type: 'math_operation', error: 'Invalid operands' }); - } - - let result; - switch (operator) { - case 'ADD': - result = left + right; - break; - case 'SUBTRACT': - result = left - right; - break; - case 'MULTIPLY': - result = left * right; - break; - case 'DIVIDE': - if (right === 0) { - return JSON.stringify({ type: 'math_operation', error: 'Division by zero' }); - } - result = left / right; - break; - default: - return JSON.stringify({ type: 'math_operation', error: 'Invalid operator' }); - } - - return JSON.stringify({ - type: 'dynamic_value', - values: [result] + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('math_operation', { + operator: validatedOperator.toLowerCase(), // Assuming server expects lowercase operators + 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 + }; }; } diff --git a/src/static/blocks/generators/balances_generators.js b/src/static/blocks/generators/balances_generators.js index d092260..72273b1 100644 --- a/src/static/blocks/generators/balances_generators.js +++ b/src/static/blocks/generators/balances_generators.js @@ -1,131 +1,149 @@ -// balances_generators.js - /** * Balances Generators Definitions * * This file contains generator functions for each block defined in * balances_blocks.js. Each generator translates a Blockly block - * into a JSON object representing the balance-related operations. + * into a JSON object representing balance-related operations. */ export function defineBalancesGenerators() { + + // 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."); + 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); + + if (Array.isArray(valuesJson)) { + values = valuesJson; + } else if (valuesJson !== null && valuesJson !== undefined) { + values = [valuesJson]; + } + } + + return values; + } + /** * Generator for 'starting_balance' Block - * - * Retrieves the starting balance of the strategy. - * Generates a JSON object with type and next (if connected). */ Blockly.JSON['starting_balance'] = function(block) { - const json = { - type: 'starting_balance' + const valuesArray = processValuesInput(block); + + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('starting_balance', {}); + + // Include unused values if any + const outputValues = [operationObject, ...valuesArray]; + + // Output as dynamic_value + return { + type: 'dynamic_value', + values: outputValues }; - - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - Blockly.JSON.appendNext(json, nextCode); - - return JSON.stringify(json); }; /** * Generator for 'current_balance' Block - * - * Retrieves the current balance of the strategy. - * Generates a JSON object with type and next (if connected). */ Blockly.JSON['current_balance'] = function(block) { - const json = { - type: 'current_balance' + const valuesArray = processValuesInput(block); + + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('current_balance', {}); + + // Include unused values if any + const outputValues = [operationObject, ...valuesArray]; + + // Output as dynamic_value + return { + type: 'dynamic_value', + values: outputValues }; - - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - Blockly.JSON.appendNext(json, nextCode); - - return JSON.stringify(json); }; /** * Generator for 'available_balance' Block - * - * Retrieves the overall available (liquid) balance of the user. - * Generates a JSON object with type and next (if connected). */ Blockly.JSON['available_balance'] = function(block) { - const json = { - type: 'available_balance' + const valuesArray = processValuesInput(block); + + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('available_balance', {}); + + // Include unused values if any + const outputValues = [operationObject, ...valuesArray]; + + // Output as dynamic_value + return { + type: 'dynamic_value', + values: outputValues }; - - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - Blockly.JSON.appendNext(json, nextCode); - - return JSON.stringify(json); }; /** * Generator for 'available_strategy_balance' Block - * - * Retrieves the available balance allocated specifically to the strategy. - * Generates a JSON object with type and next (if connected). */ Blockly.JSON['available_strategy_balance'] = function(block) { - const json = { - type: 'available_strategy_balance' + const valuesArray = processValuesInput(block); + + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('available_strategy_balance', {}); + + // Include unused values if any + const outputValues = [operationObject, ...valuesArray]; + + // Output as dynamic_value + return { + type: 'dynamic_value', + values: outputValues }; - - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - Blockly.JSON.appendNext(json, nextCode); - - return JSON.stringify(json); }; /** * Generator for 'set_available_strategy_balance' Block * - * Sets the balance allocated to the strategy. - * Generates a JSON object with type, balance, and next (if connected). + * This is a statement block, so it generates an action object and handles 'NEXT' connections. */ Blockly.JSON['set_available_strategy_balance'] = function(block) { - try { - // Retrieve and parse the 'BALANCE' input - const balanceCode = Blockly.JSON.valueToCode(block, 'BALANCE', Blockly.JSON.ORDER_ATOMIC) || "0"; - let balance; + // Retrieve the 'BALANCE' input block + const balanceBlock = block.getInputTargetBlock('BALANCE'); + let balance = 0; - try { - const parsedBalance = Blockly.JSON.safeParse(balanceCode); - if (Array.isArray(parsedBalance)) { - // If balanceCode is a list, take the first value - balance = parseFloat(parsedBalance[0]); - } else { - // If balanceCode is a single value - balance = parseFloat(parsedBalance); - } - } catch (e) { - // If balanceCode is not a JSON string (e.g., a simple number) - balance = parseFloat(balanceCode); + 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."); } - - if (isNaN(balance)) { - throw new Error("Invalid balance value."); - } - - const json = { - type: 'set_available_strategy_balance', - balance: balance - }; - - // Optionally handle 'NEXT' if statement chaining is required - // For statement blocks, chaining is managed via Blockly's internal mechanisms - // Uncomment the lines below if you have a 'NEXT' input in the block definition - - /* - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - Blockly.JSON.appendNext(json, nextCode); - */ - - return JSON.stringify(json); - } catch (error) { - console.error(`Error in set_available_strategy_balance generator: ${error.message}`); - // Return a default JSON object or handle the error as needed - return JSON.stringify({ type: 'set_available_strategy_balance', balance: 0 }); + } else { + console.warn("No balance provided in 'set_available_strategy_balance' block. Using default value 0."); } + + const json = { + type: 'set_available_strategy_balance', + inputs: { + balance: balance + } + }; + + // No need to handle 'NEXT' input; sequencing is managed by _blockToJson + + return json; // Return the action object directly }; } diff --git a/src/static/blocks/generators/control_generators.js b/src/static/blocks/generators/control_generators.js index 63a01d2..e2af7fb 100644 --- a/src/static/blocks/generators/control_generators.js +++ b/src/static/blocks/generators/control_generators.js @@ -1,200 +1,184 @@ -// control_generators.js - /** * Control Generators Definitions * * This file contains generator functions for each block defined in * control_blocks.js. Each generator translates a Blockly block - * into a JSON object representing the control operation. - * - * Note: This file assumes that `json_base_generator.js` has been loaded - * before it, which initializes `Blockly.JSON` and attaches helper functions. + * into a JSON object representing control operations. */ export function defineControlGenerators() { + + // 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."); + return; + } + /** * Generator for 'pause_strategy' Block - * - * Description: - * Generates a JSON object to pause the current strategy. - * This block does not accept any inputs or forward any additional data. */ - Blockly.JSON['pause_strategy'] = function(block) { - const json = { - type: 'pause_strategy' - }; - - // Handle 'NEXT' input if connected (optional) - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - Blockly.JSON.appendNext(json, nextCode); - - return JSON.stringify(json); - }; +// Blockly.JSON['pause_strategy'] = function(block) { +// const json = { +// type: 'pause_strategy' +// }; +// +// return json; // Return the JSON object directly +// }; /** * Generator for 'strategy_resume' Block - * - * Description: - * Generates a JSON object to resume the strategy. - * This block does not accept any inputs or forward any additional data. */ - Blockly.JSON['strategy_resume'] = function(block) { - const json = { - type: 'strategy_resume' - }; - - // Handle 'NEXT' input if connected (optional) - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - Blockly.JSON.appendNext(json, nextCode); - - return JSON.stringify(json); - }; +// Blockly.JSON['strategy_resume'] = function(block) { +// const json = { +// type: 'strategy_resume' +// }; +// +// return json; // Return the JSON object directly +// }; /** * Generator for 'strategy_exit' Block - * - * Description: - * Generates a JSON object to exit the strategy and close trades based on the selected condition. - * Users can choose to close all trades, only profitable trades, or only losing trades. */ - Blockly.JSON['strategy_exit'] = function(block) { - const conditionValue = block.getFieldValue('condition'); // 'all', 'in_profit', 'in_loss' - - const json = { - type: 'strategy_exit', - condition: conditionValue - }; - - // Handle 'NEXT' input if connected (optional) - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - Blockly.JSON.appendNext(json, nextCode); - - return JSON.stringify(json); - }; +// Blockly.JSON['strategy_exit'] = function(block) { +// const conditionValue = block.getFieldValue('condition'); // 'all', 'in_profit', 'in_loss' +// +// const json = { +// type: 'strategy_exit', +// inputs: { +// condition: conditionValue +// } +// }; +// +// return json; // Return the JSON object directly +// }; /** * Generator for 'max_drawdown' Block - * - * Description: - * Generates a JSON object to set a maximum drawdown limit. If the drawdown exceeds the specified value, - * the strategy will either pause or exit based on user selection. */ Blockly.JSON['max_drawdown'] = function(block) { - const drawdownCode = Blockly.JSON.valueToCode(block, 'DRAWDOWN_VALUE', Blockly.JSON.ORDER_ATOMIC) || "0"; const action = block.getFieldValue('ACTION'); // 'PAUSE' or 'EXIT' - const drawdownValue = Blockly.JSON.getFirstValue(drawdownCode); + // Retrieve the drawdown value from 'DRAWDOWN_VALUE' input + let drawdownValue = 0; + const drawdownBlock = block.getInputTargetBlock('DRAWDOWN_VALUE'); + if (drawdownBlock) { + drawdownValue = Blockly.JSON.blockToCode(drawdownBlock); + if (typeof drawdownValue !== 'number') { + console.error("Invalid drawdown value in 'max_drawdown' block."); + drawdownValue = 0; + } + } else { + console.warn("No drawdown value provided in 'max_drawdown' block. Using default value 0."); + } const json = { type: 'max_drawdown', - drawdown: drawdownValue !== null ? drawdownValue : 0, - action: action + inputs: { + drawdown: drawdownValue, + action: action + } }; - // Handle 'NEXT' input if connected (optional) - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - Blockly.JSON.appendNext(json, nextCode); - - return JSON.stringify(json); + return json; // Return the JSON object directly }; /** * Generator for 'schedule_action' Block - * - * Description: - * Generates a JSON object to schedule a specific action after a defined time interval, - * with an option to repeat the action. */ Blockly.JSON['schedule_action'] = function(block) { - // Retrieve the nested statements from 'DO' input - const doCode = Blockly.JSON.statementToCode(block, 'DO') || ""; // Retrieve the time interval from dropdown const timeInterval = block.getFieldValue('TIME_INTERVAL'); // e.g., '1m', '5m', etc. // Retrieve the repeat option const repeatOption = block.getFieldValue('REPEAT'); // 'ONCE' or 'REPEAT' - // Convert time interval string to milliseconds - const timeMapping = { - '1m': 60000, - '5m': 300000, - '15m': 900000, - '30m': 1800000, - '1h': 3600000, - '4h': 14400000, - '6h': 21600000, - '12h': 43200000, - '1d': 86400000, - '3d': 259200000, - '1w': 604800000, - '1M': 2592000000 - }; - const timeIntervalMs = timeMapping[timeInterval] || 0; - - const json = { + // Create operation object + const operationObject = { type: 'schedule_action', - time_interval: timeIntervalMs, - repeat: repeatOption === 'REPEAT' // Boolean: true if 'REPEAT', false if 'ONCE' + inputs: { + time_interval: timeInterval, + repeat: repeatOption === 'REPEAT' // true if 'REPEAT', false if 'ONCE' + } }; - // If the 'DO' input is connected, include the nested statements - if (doCode.trim() !== "") { - // Parse the nested statements JSON - const doJson = Blockly.JSON.safeParse(doCode); - if (doJson) { - json.action = doJson; - } else { - console.error("Failed to parse nested 'DO' statements in 'schedule_action' block."); + // Process the 'DO' statements + const actionJsonArray = []; + let currentBlock = block.getInputTargetBlock('DO'); + while (currentBlock) { + const blockJson = Blockly.JSON.blockToCode(currentBlock); + if (blockJson) { + actionJsonArray.push(blockJson); } + currentBlock = currentBlock.getNextBlock(); } - // Handle 'NEXT' input if connected (optional) - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - Blockly.JSON.appendNext(json, nextCode); + // Include the nested statements if any + if (actionJsonArray.length > 0) { + operationObject.inputs.action = actionJsonArray; + } - return JSON.stringify(json); + return operationObject; // Return the JSON object directly }; /** - * Generator for 'execute_if' Block (New) - * - * Description: - * Generates a JSON object to execute a series of statements if a specified condition returns true. - * Allows users to implement conditional logic within their strategy. + * Generator for 'execute_if' Block */ Blockly.JSON['execute_if'] = function(block) { - const conditionCode = Blockly.JSON.valueToCode(block, 'CONDITION', Blockly.JSON.ORDER_ATOMIC) || "false"; - const doCode = Blockly.JSON.statementToCode(block, 'DO') || ""; - - const condition = Blockly.JSON.getFirstValue(conditionCode); - - // Validate the condition - let validCondition = false; - if (typeof condition === 'boolean') { - validCondition = condition; - } else { - // Implement additional validation or conversion if necessary - console.warn("Condition in 'execute_if' block is not a boolean. Defaulting to false."); - } - - const json = { - type: 'execute_if', - condition: validCondition - }; - - // If the 'DO' input is connected, include the nested statements - if (doCode.trim() !== "") { - const doJson = Blockly.JSON.safeParse(doCode); - if (doJson) { - json.do = doJson; + try { + // Process CONDITION input + let conditionJson = null; + const conditionBlock = block.getInputTargetBlock('CONDITION'); + if (conditionBlock) { + conditionJson = Blockly.JSON._blockToJson(conditionBlock, 1); + if (typeof conditionJson !== 'object' || conditionJson === null) { + console.error(`Invalid 'CONDITION' input in 'execute_if' block. Expected an object.`); + conditionJson = { type: 'comparison', operator: '==', inputs: { LEFT: 0, RIGHT: 0 } }; // Default condition + } } else { - console.error("Failed to parse nested 'DO' statements in 'execute_if' block."); + console.warn(`No block connected to 'CONDITION' input of 'execute_if' block. Using default condition.`); + conditionJson = { type: 'comparison', operator: '==', inputs: { LEFT: 0, RIGHT: 0 } }; // Default condition } + + // Process DO statements + const doStatements = []; + let currentBlock = block.getInputTargetBlock('DO'); + while (currentBlock) { + const blockJson = Blockly.JSON._blockToJson(currentBlock, 1); + if (blockJson) { + doStatements.push(blockJson); + } + currentBlock = currentBlock.getNextBlock(); + } + + // If no DO statements, exclude the block + if (doStatements.length === 0) { + console.warn(`'execute_if' block has no DO statements. Excluding from JSON.`); + return null; // Exclude the block + } + + // **Construct the JSON object** + const json = { + type: 'execute_if', + inputs: { + CONDITION: conditionJson + } + }; + + if (doStatements.length > 0) { + json.statements = { + DO: doStatements + }; + } + + // **Set the skipAdditionalParsing flag** + json.skipAdditionalParsing = true; + + console.log(`Generated JSON for 'execute_if' block:`, json); + return json; + } catch (error) { + console.error(`Error in generator for 'execute_if' block:`, error); + return null; // Return null to indicate failure } - - // Handle 'NEXT' input if connected (optional) - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - Blockly.JSON.appendNext(json, nextCode); - - return JSON.stringify(json); }; + + } diff --git a/src/static/blocks/generators/json_base_generator.js b/src/static/blocks/generators/json_base_generator.js index c3e33ac..2db8481 100644 --- a/src/static/blocks/generators/json_base_generator.js +++ b/src/static/blocks/generators/json_base_generator.js @@ -1,90 +1,267 @@ -// /generators/json_base_generator.js - -/** - * JSON Base Generator Definitions - * - * This file initializes the Blockly.JSON generator and attaches helper functions - * to it. These helpers are available to all other generator files, promoting code - * re-usability and consistency. - */ +// /blocks/generators/json_base_generator.js export function defineJsonBaseGenerator() { if (!Blockly.JSON) { Blockly.JSON = new Blockly.Generator('JSON'); - // Define operator precedence if necessary - Blockly.JSON.ORDER = { - 'NONE': 0, - 'ATOMIC': 0, - 'COMMA': 1, - 'ASSIGNMENT': 2, - // Add more as needed - }; + /** + * 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) + Blockly.JSON.ORDER_COMMA = 1; // , + Blockly.JSON.ORDER_ASSIGNMENT = 2; // = /** - * Helper Function: safeParse - * Safely parses a JSON string and returns the corresponding object. - * If parsing fails, it returns null. - * - * @param {string} jsonString - The JSON string to parse. - * @returns {object|null} - The parsed object or null if parsing fails. + * Override the default blockToCode method to generate JSON from an individual block. */ - Blockly.JSON.safeParse = function(jsonString) { - try { - return JSON.parse(jsonString); - } catch (e) { - console.error("JSON Parsing Error:", e); + Blockly.JSON.blockToCode = function(block) { + if (Blockly.JSON && Blockly.JSON[block.type]) { + const jsonObject = Blockly.JSON[block.type](block); + if (jsonObject && typeof jsonObject === 'object') { + return jsonObject; + } else { + console.error(`Generator for block type '${block.type}' did not return a valid JSON object.`); + return null; + } + } else { + console.error(`No generator function found for block type '${block.type}'.`); return null; } }; /** - * Helper Function: getFirstValue - * Extracts the first value from a dynamic_value input. - * If the input is a list, it returns the first element. - * If the input is a single value, it returns that value. - * If the input is undefined or invalid, it returns null. + * safeParse Function * - * @param {string} code - The JSON string representing the input. - * @returns {any|null} - The first value or null. + * Description: + * Safely parses a string into a JavaScript value. Attempts to parse as JSON, + * number, boolean, or defaults to the original string if parsing fails. + * + * @param {string} code - The string to parse. + * @returns {*} The parsed value, or the original string if parsing fails. */ - Blockly.JSON.getFirstValue = function(code) { - if (!code) return null; - const parsed = Blockly.JSON.safeParse(code); - if (!parsed) return null; - if (Array.isArray(parsed)) { - return parsed[0]; + Blockly.JSON.safeParse = function(code) { + // Attempt to parse as JSON + try { + return JSON.parse(code); + } catch (jsonError) { + // If JSON parsing fails, proceed to other types } - return parsed; + + // Trim the code to handle leading/trailing whitespace + const trimmedCode = code.trim(); + + // Attempt to parse as a number + const number = parseFloat(trimmedCode); + if (!isNaN(number)) { + return number; + } + + // Attempt to parse as a boolean + if (trimmedCode.toLowerCase() === 'true') { + return true; + } + if (trimmedCode.toLowerCase() === 'false') { + return false; + } + + // If all parsing attempts fail, return the original string + return trimmedCode; }; /** - * Helper Function: appendNext - * Appends the 'next' JSON object to the current JSON object if 'NEXT' is connected. + * Helper Function: createOperationObject + * Creates an operation object with the specified type and inputs. + * This function standardizes the creation of operation objects across generators. * - * @param {object} json - The current JSON object being constructed. - * @param {string} nextCode - The JSON string of the 'NEXT' input. + * @param {string} type - The type of the operation (e.g., 'power', 'sqrt'). + * @param {object} inputs - An object containing the inputs for the operation. + * @returns {object} - The operation object. */ - Blockly.JSON.appendNext = function(json, nextCode) { - if (nextCode) { - const nextJson = Blockly.JSON.safeParse(nextCode); - if (nextJson) { - if (nextJson.type) { - // If nextJson is a single block - json.next = nextJson; - } else if (Array.isArray(nextJson)) { - // If nextJson is already a list - json.next = nextJson; - } else { - // If nextJson is an object, wrap it in a list - json.next = [nextJson]; - } + Blockly.JSON.createOperationObject = function(type, inputs) { + return { + type: type, + inputs: inputs + }; + }; + + + /** + * Helper Function: _blockToJson + * Recursively converts a block and its connected blocks into JSON. + * + * @param {Blockly.Block} block - The block to convert. + * @param {number} currentDepth - The current recursion depth. + * @returns {Object} - The JSON representation of the block. + */ + Blockly.JSON._blockToJson = function(block, currentDepth = 0) { + if (!block) { + return null; + } + + // Calculate nesting depth and enforce limits + try { + Blockly.JSON._calculateNestingDepth(block, currentDepth); + } catch (error) { + console.error(error.message); + alert(error.message); + throw error; // Stop processing if limit exceeded + } + + // Use the block's JSON generator if it exists + if (Blockly.JSON && Blockly.JSON[block.type]) { + // Generate JSON object using the block's JSON generator + const 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** + 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: {} + }; + + // 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(); + } + }); + } + }); + + // 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; } }; - console.log('Blockly.JSON generator initialized with safeParse, getFirstValue, and appendNext.'); - } else { - console.log('Blockly.JSON was already initialized.'); + /** + * Helper Function: _calculateNestingDepth + * Calculates the nesting depth of a block and throws an error if it exceeds the maximum allowed depth. + * + * @param {Blockly.Block} block - The block to calculate depth for. + * @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.`); + } + // Add any additional depth calculations or checks if necessary + }; + + /** + * Helper Function: processNumericInput + * Processes a numeric input. + * + * @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. + */ + Blockly.JSON.processNumericInput = function (block, inputName, defaultValue) { + 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; + } + + 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) { + 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; + }; + + 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 892ba40..e4be697 100644 --- a/src/static/blocks/generators/logical_generators.js +++ b/src/static/blocks/generators/logical_generators.js @@ -1,5 +1,3 @@ -// logical_generators.js - /** * Logical Generators Definitions * @@ -7,166 +5,220 @@ * logical_blocks.js. Each generator translates a Blockly block into a JSON * object representing logical operations such as comparisons, logical AND/OR, * boolean condition checks, and flag evaluations. - * - * Note: This file assumes that `json_base_generator.js` has been loaded - * before it, which initializes `Blockly.JSON` and attaches helper functions. */ export function defineLogicalGenerators() { + + // 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."); + return; + } + /** - * Generator for 'comparison' Block + * Helper Function: processTwoValueInput + * Processes two value inputs ('LEFT' and 'RIGHT') and returns their values. * - * Description: - * Generates a JSON object representing a comparison operation between two values - * using operators like >, <, ==. Outputs a Boolean value based on the comparison result. + * @param {Blockly.Block} block - The block containing the inputs. + * @returns {Object} - An object containing 'left' and 'right' values. */ - Blockly.JSON['comparison'] = function(block) { - // Retrieve and parse the 'left' input - const leftCode = Blockly.JSON.valueToCode(block, 'left', Blockly.JSON.ORDER_ATOMIC) || "0"; - let left; - try { - left = Blockly.JSON.safeParse(leftCode); - } catch (e) { - console.warn(`Error parsing 'left' input in comparison block: ${e.message}. Defaulting to 0.`); - left = 0; + function processTwoValueInput(block) { + const leftBlock = block.getInputTargetBlock('LEFT'); + const rightBlock = block.getInputTargetBlock('RIGHT'); + + let left = null; + let right = null; + + if (leftBlock) { + const leftJson = Blockly.JSON.blockToCode(leftBlock); + left = typeof leftJson !== 'undefined' ? leftJson : null; + } else { + console.warn(`No block connected to 'LEFT' input of '${block.type}' block. Using default value.`); + left = null; // Assign a default value if necessary } - // Retrieve the selected operator - const operator = block.getFieldValue('operator'); // '>', '<', '==' - - // Retrieve and parse the 'right' input - const rightCode = Blockly.JSON.valueToCode(block, 'right', Blockly.JSON.ORDER_ATOMIC) || "0"; - let right; - try { - right = Blockly.JSON.safeParse(rightCode); - } catch (e) { - console.warn(`Error parsing 'right' input in comparison block: ${e.message}. Defaulting to 0.`); - right = 0; + if (rightBlock) { + const rightJson = Blockly.JSON.blockToCode(rightBlock); + right = typeof rightJson !== 'undefined' ? rightJson : null; + } else { + console.warn(`No block connected to 'RIGHT' input of '${block.type}' block. Using default value.`); + right = null; // Assign a default value if necessary } - const json = { - type: 'comparison', - left: left, - operator: operator, - right: right - }; + return { left, right }; + } - return JSON.stringify(json); +/** + * 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. + * + * @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; + + 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 + } + + 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 + } + + return { LEFT: left, RIGHT: right }; + } + + 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] } + } }; + return operationObject; +}; + + /** * Generator for 'logical_and' Block * - * Description: - * Generates a JSON object representing a logical AND operation between two Boolean conditions. - * Outputs true only if both conditions are true. + * Generates a JSON object representing a logical AND operation between two conditions. */ Blockly.JSON['logical_and'] = function(block) { - // Retrieve and parse the 'left' Boolean input - const leftCode = Blockly.JSON.valueToCode(block, 'left', Blockly.JSON.ORDER_ATOMIC) || "false"; - let left; - try { - left = Blockly.JSON.safeParse(leftCode); - if (typeof left !== 'boolean') { - throw new Error("Left input is not a Boolean."); - } - } catch (e) { - console.warn(`Error parsing 'left' input in logical_and block: ${e.message}. Defaulting to false.`); - left = false; - } + const { left, right } = processTwoValueInput(block); - // Retrieve and parse the 'right' Boolean input - const rightCode = Blockly.JSON.valueToCode(block, 'right', Blockly.JSON.ORDER_ATOMIC) || "false"; - let right; - try { - right = Blockly.JSON.safeParse(rightCode); - if (typeof right !== 'boolean') { - throw new Error("Right input is not a Boolean."); - } - } catch (e) { - console.warn(`Error parsing 'right' input in logical_and block: ${e.message}. Defaulting to false.`); - right = false; - } + // Default to false if any condition is null + const leftCondition = typeof left === 'boolean' ? left : false; + const rightCondition = typeof right === 'boolean' ? right : false; - const json = { - type: 'logical_and', - left: left, - right: right - }; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('logical_and', { + left: leftCondition, + right: rightCondition + }); - return JSON.stringify(json); + // Output as dynamic_value + return operationObject; }; /** * Generator for 'logical_or' Block * - * Description: - * Generates a JSON object representing a logical OR operation between two Boolean conditions. - * Outputs true if at least one of the conditions is true. + * Generates a JSON object representing a logical OR operation between two conditions. */ Blockly.JSON['logical_or'] = function(block) { - // Retrieve and parse the 'left' Boolean input - const leftCode = Blockly.JSON.valueToCode(block, 'left', Blockly.JSON.ORDER_ATOMIC) || "false"; - let left; - try { - left = Blockly.JSON.safeParse(leftCode); - if (typeof left !== 'boolean') { - throw new Error("Left input is not a Boolean."); - } - } catch (e) { - console.warn(`Error parsing 'left' input in logical_or block: ${e.message}. Defaulting to false.`); - left = false; - } + const { left, right } = processTwoValueInput(block); - // Retrieve and parse the 'right' Boolean input - const rightCode = Blockly.JSON.valueToCode(block, 'right', Blockly.JSON.ORDER_ATOMIC) || "false"; - let right; - try { - right = Blockly.JSON.safeParse(rightCode); - if (typeof right !== 'boolean') { - throw new Error("Right input is not a Boolean."); - } - } catch (e) { - console.warn(`Error parsing 'right' input in logical_or block: ${e.message}. Defaulting to false.`); - right = false; - } + // Default to false if any condition is null + const leftCondition = typeof left === 'boolean' ? left : false; + const rightCondition = typeof right === 'boolean' ? right : false; - const json = { - type: 'logical_or', - left: left, - right: right - }; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('logical_or', { + left: leftCondition, + right: rightCondition + }); - return JSON.stringify(json); + // Output as dynamic_value + return operationObject; }; /** * Generator for 'is_false' Block * - * Description: - * Generates a JSON object to check if a given Boolean condition is false. - * Outputs a Boolean value based on the condition's state. + * Generates a JSON object to check if a given condition is false. */ Blockly.JSON['is_false'] = function(block) { - // Retrieve and parse the 'condition' Boolean input - const conditionCode = Blockly.JSON.valueToCode(block, 'condition', Blockly.JSON.ORDER_ATOMIC) || "false"; - let condition; - try { - condition = Blockly.JSON.safeParse(conditionCode); - if (typeof condition !== 'boolean') { - throw new Error("Condition input is not a Boolean."); + // 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_false' block. Expected a Boolean."); } - } catch (e) { - console.warn(`Error parsing 'condition' input in is_false block: ${e.message}. Defaulting to false.`); - condition = false; + } else { + console.warn("No condition provided in 'is_false' block. Using default value 'false'."); } - const json = { - type: 'is_false', + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('is_false', { condition: condition - }; + }); - return JSON.stringify(json); + // 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."); + } + } 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; + }; } diff --git a/src/static/blocks/generators/market_data_generators.js b/src/static/blocks/generators/market_data_generators.js index d0bb11a..4211553 100644 --- a/src/static/blocks/generators/market_data_generators.js +++ b/src/static/blocks/generators/market_data_generators.js @@ -1,254 +1,137 @@ -// market_data_generators.js - /** * Market Data Generators Definitions * * This file contains generator functions for each block defined in * market_data_blocks.js. Each generator translates a Blockly block into a JSON - * object representing market data retrieval operations such as fetching current price, - * bid price, ask price, and specific candle values. - * - * Note: This file assumes that `json_base_generator.js` has been loaded - * before it, which initializes `Blockly.JSON` and attaches helper functions. + * object representing market data retrieval operations. */ export function defineMarketDataGenerators() { - /** - * Constants for default values - */ - const DEFAULT_TF = '5m'; - const DEFAULT_EX = 'Binance'; - const DEFAULT_SYM = 'BTCUSDT'; - /** - * Retrieves default source settings from the DOM. - */ - function getDefaultSource() { - const symbolElement = document.getElementById('symbol'); - const timeframeElement = document.getElementById('timeframe'); - const exchangeElement = document.getElementById('exchange_name'); - - if (symbolElement && timeframeElement && exchangeElement) { - return { - time_frame: validateTimeFrame(timeframeElement.value) || DEFAULT_TF, - exchange: validateExchange(exchangeElement.value) || DEFAULT_EX, - symbol: validateSymbol(symbolElement.value) || DEFAULT_SYM - }; - } else { - console.error('One or more source elements are missing in the DOM.'); - return { - time_frame: DEFAULT_TF, - exchange: DEFAULT_EX, - symbol: DEFAULT_SYM - }; - } + // 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."); + return; } /** - * Validation functions + * Helper Function: processSourceInput + * Processes the 'SOURCE' input and returns its value. + * + * @param {Blockly.Block} block - The block containing the 'SOURCE' input. + * @returns {Object} - The source object or a default empty object. */ - function validateTimeFrame(tf) { - const validTFs = bt_data && bt_data.intervals ? bt_data.intervals : []; - if (validTFs.includes(tf)) { - return tf; - } else { - console.warn(`Invalid Time Frame "${tf}". Defaulting to "${DEFAULT_TF}".`); - return DEFAULT_TF; - } - } + function processSourceInput(block) { + const sourceBlock = block.getInputTargetBlock('SOURCE'); + let source = {}; - function validateExchange(exc) { - const validExchanges = window.UI && window.UI.exchanges && window.UI.exchanges.connected_exchanges ? window.UI.exchanges.connected_exchanges : []; - if (validExchanges.includes(exc)) { - return exc; - } else { - console.warn(`Invalid Exchange "${exc}". Defaulting to "${DEFAULT_EX}".`); - return DEFAULT_EX; - } - } - - function validateSymbol(sym) { - const validSymbols = bt_data && bt_data.symbols ? bt_data.symbols : []; - if (validSymbols.includes(sym)) { - return sym; - } else { - console.warn(`Invalid Symbol "${sym}". Defaulting to "${DEFAULT_SYM}".`); - return DEFAULT_SYM; - } - } - - /** - * Helper function to determine the type of the input. - */ - function determineInputType(parsedInput) { - if (parsedInput === null) return 'invalid'; - if (typeof parsedInput === 'object' && !Array.isArray(parsedInput)) { - // Check if it's a source object (time_frame, exchange, symbol) - if ('time_frame' in parsedInput && 'exchange' in parsedInput && 'symbol' in parsedInput) { - return 'source_object'; - } - return 'object'; - } - if (Array.isArray(parsedInput)) { - return 'list'; - } - // Assume primitive types (number, string, etc.) - return 'primitive'; - } - - /** - * Generic generator function for market data blocks. - * Handles 'current_price', 'bid_price', 'ask_price', and 'last_candle_value'. - */ - function generateMarketData(blockType, additionalFields = {}) { - return function(block) { - // Retrieve the 'source' input - const providedSourceCode = Blockly.JSON.valueToCode(block, 'source', Blockly.JSON.ORDER_ATOMIC) || null; - - let definedSource = {}; - let json = {}; - let excessValues = []; - - if (providedSourceCode) { - // Attempt to parse the provided source - const parsedInput = Blockly.JSON.safeParse(providedSourceCode); - const inputType = determineInputType(parsedInput); - - switch (inputType) { - case 'source_object': - definedSource = parsedInput; - break; - case 'primitive': - // Assign to 'time_frame' and use defaults for others - definedSource.time_frame = validateTimeFrame(parsedInput) || DEFAULT_TF; - definedSource.exchange = validateExchange(DEFAULT_EX); - definedSource.symbol = validateSymbol(DEFAULT_SYM); - break; - case 'list': - // Assign values in order: [time_frame, exchange, symbol] - definedSource.time_frame = validateTimeFrame(parsedInput[0] || DEFAULT_TF); - definedSource.exchange = validateExchange(parsedInput[1] || DEFAULT_EX); - definedSource.symbol = validateSymbol(parsedInput[2] || DEFAULT_SYM); - if (parsedInput.length > 3) { - excessValues = parsedInput.slice(3); - } - break; - case 'object': - case 'invalid': - default: - console.warn(`Invalid 'source' input in ${blockType} block. Using defaults.`); - definedSource = getDefaultSource(); - break; - } + if (sourceBlock) { + const sourceJson = Blockly.JSON.blockToCode(sourceBlock); + if (typeof sourceJson === 'object' && sourceJson !== null) { + source = sourceJson; } else { - // Define source from interface settings - definedSource = getDefaultSource(); + 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.`); + } - // Additional fields for specific blocks (e.g., candle_part) - json = { - type: blockType, - time_frame: definedSource.time_frame, - exchange: definedSource.exchange, - symbol: definedSource.symbol, - ...additionalFields(block) - }; - - // If there are excess values, package them into a new dynamic_value list - if (excessValues.length > 0) { - // Combine current block's JSON with excess values - const combinedJson = [json, ...excessValues]; - return JSON.stringify(combinedJson); - } - - return JSON.stringify(json); - }; + return source; } /** * Generator for 'current_price' Block */ - Blockly.JSON['current_price'] = generateMarketData('current_price'); + 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; + }; /** * Generator for 'bid_price' Block */ - Blockly.JSON['bid_price'] = generateMarketData('bid_price'); + 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; + }; /** * Generator for 'ask_price' Block */ - Blockly.JSON['ask_price'] = generateMarketData('ask_price'); + 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; + }; /** * Generator for 'last_candle_value' Block */ - Blockly.JSON['last_candle_value'] = generateMarketData('last_candle_value', function(block) { - return { - candle_part: block.getFieldValue('candle_part') - }; - }); + 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 validatedCandlePart = validCandleParts.includes(candlePart) ? candlePart : 'close'; + + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('last_candle_value', { + source: source, + candle_part: validatedCandlePart + }); + + // Output as dynamic_value + return operationObject; + }; /** * Generator for 'source' Block * - * Description: - * Generates a JSON array defining the data source based on Time Frame (TF), - * Exchange (EXC), and Symbol (SYM). Additionally handles the 'NEXT' input for chaining. - * This source can be used with blocks that expect a 'dynamic_value' type input. + * 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 tf = block.getFieldValue('TF'); // Time Frame - const exc = block.getFieldValue('EXC'); // Exchange - const sym = block.getFieldValue('SYM'); // Symbol + 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 dropdown selections against available data - const validatedTF = validateTimeFrame(tf); - const validatedEx = validateExchange(exc); - const validatedSym = validateSymbol(sym); + // Validate fields if necessary + // Example: Ensure timeFrame matches expected patterns + // Skipping detailed validation for brevity - const sourceList = [ - validatedTF, - validatedEx, - validatedSym - ]; + // Create source object + const sourceObject = { + time_frame: timeFrame, + exchange: exchange, + symbol: symbol + }; - // Handle 'NEXT' input for chaining - const nextSourceCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC) || null; - - if (nextSourceCode) { - const parsedNextInput = Blockly.JSON.safeParse(nextSourceCode); - const nextInputType = determineInputType(parsedNextInput); - - let nextValues = []; - - switch (nextInputType) { - case 'source_object': - // If 'NEXT' is a source object, convert it to a list - nextValues = [parsedNextInput.time_frame, parsedNextInput.exchange, parsedNextInput.symbol]; - break; - case 'primitive': - // Assign single primitive as 'time_frame' - nextValues = [validateTimeFrame(parsedNextInput)]; - break; - case 'list': - // If 'NEXT' is a list, append all its items - nextValues = parsedNextInput; - break; - case 'object': - case 'invalid': - default: - console.warn(`Invalid 'NEXT' input in 'source' block. Ignoring excess values.`); - break; - } - - // Append the next values to the source list - if (Array.isArray(nextValues) && nextValues.length > 0) { - sourceList.push(...nextValues); - } - } - - return JSON.stringify(sourceList); + // Output as source object + return sourceObject; }; } diff --git a/src/static/blocks/generators/order_metrics_generators.js b/src/static/blocks/generators/order_metrics_generators.js index bb8e2db..d622b11 100644 --- a/src/static/blocks/generators/order_metrics_generators.js +++ b/src/static/blocks/generators/order_metrics_generators.js @@ -1,169 +1,102 @@ -// order_metrics_generators.js - /** * Order Metrics Generators Definitions * * This file contains generator functions for each block defined in * order_metrics_blocks.js. Each generator translates a Blockly block into a JSON - * object representing order metrics retrieval operations such as fetching order volume, - * filled orders, unfilled orders, and checking order status. - * - * Note: This file assumes that `json_base_generator.js` has been loaded - * before it, which initializes `Blockly.JSON` and attaches helper functions. + * object representing order metrics retrieval operations. */ export function defineOrderMetricsGenerators() { + + // 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."); + return; + } + + /** + * Helper Function: validateOrderType + * Validates the order type and returns a default if invalid. + * + * @param {string} orderType - The order type to validate. + * @returns {string} - Validated order type. + */ + function validateOrderType(orderType) { + const validOrderTypes = ['filled', 'unfilled']; + if (!validOrderTypes.includes(orderType)) { + console.error(`Invalid order type '${orderType}' in 'order_volume' block. Defaulting to 'filled'.`); + return 'filled'; + } + return orderType; + } + /** * Generator for 'order_volume' Block - * - * Description: - * Generates a JSON object representing the retrieval of the cumulative volume - * of filled or unfilled orders based on the specified order type. Supports - * chaining with the 'NEXT' input to facilitate sequences or lists of operations. */ Blockly.JSON['order_volume'] = function(block) { // Retrieve the 'ORDER_TYPE' from the dropdown const orderType = block.getFieldValue('ORDER_TYPE'); // 'filled' or 'unfilled' + const validatedOrderType = validateOrderType(orderType); - // Validate the order type - const validOrderTypes = ['filled', 'unfilled']; - if (!validOrderTypes.includes(orderType)) { - console.warn(`Invalid ORDER_TYPE "${orderType}" in order_volume block. Defaulting to 'filled'.`); - } - const validatedOrderType = validOrderTypes.includes(orderType) ? orderType : 'filled'; - - const json = { - type: 'order_volume', + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('order_volume', { order_type: validatedOrderType - }; + }); - // Handle 'NEXT' input for chaining - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - if (Array.isArray(nextJson)) { - // If NEXT is a list, concatenate - json.next = [json, ...nextJson]; - } else { - // If NEXT is a single value, create a list - json.next = [json, nextJson]; - } - } catch (e) { - console.warn(`Failed to parse 'NEXT' input in order_volume block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.next = [json, nextCode]; - } - } - - return JSON.stringify(json); + // Output as dynamic_value + return operationObject; }; /** * Generator for 'filled_orders' Block - * - * Description: - * Generates a JSON object representing the retrieval of the number of filled orders - * for the current strategy. Supports chaining with the 'NEXT' input to - * facilitate sequences or lists of operations. */ Blockly.JSON['filled_orders'] = function(block) { - // Since the block no longer specifies a symbol, assume current strategy - const json = { - type: 'filled_orders' - // Additional context like strategy can be handled globally or elsewhere - }; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('filled_orders', {}); - // Handle 'NEXT' input for chaining - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - if (Array.isArray(nextJson)) { - // If NEXT is a list, concatenate - json.next = [json, ...nextJson]; - } else { - // If NEXT is a single value, create a list - json.next = [json, nextJson]; - } - } catch (e) { - console.warn(`Failed to parse 'NEXT' input in filled_orders block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.next = [json, nextCode]; - } - } - - return JSON.stringify(json); + // Output as dynamic_value + return operationObject; }; /** * Generator for 'unfilled_orders' Block - * - * Description: - * Generates a JSON object representing the retrieval of the number of unfilled orders - * for the current strategy. Supports chaining with the 'NEXT' input to - * facilitate sequences or lists of operations. */ Blockly.JSON['unfilled_orders'] = function(block) { - // Since the block no longer specifies a symbol, assume current strategy - const json = { - type: 'unfilled_orders' - // Additional context like strategy can be handled globally or elsewhere - }; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('unfilled_orders', {}); - // Handle 'NEXT' input for chaining - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - if (Array.isArray(nextJson)) { - // If NEXT is a list, concatenate - json.next = [json, ...nextJson]; - } else { - // If NEXT is a single value, create a list - json.next = [json, nextJson]; - } - } catch (e) { - console.warn(`Failed to parse 'NEXT' input in unfilled_orders block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.next = [json, nextCode]; - } - } - - return JSON.stringify(json); + // Output as dynamic_value + return operationObject; }; /** * Generator for 'order_status' Block - * - * Description: - * Generates a JSON object to check if a specified order is in a particular status - * (Filled, Unfilled, Partial). Outputs a Boolean value indicating the result. */ Blockly.JSON['order_status'] = function(block) { // Retrieve the 'ORDER_NAME' field input - const orderName = block.getFieldValue('ORDER_NAME'); + const orderName = (block.getFieldValue('ORDER_NAME') || 'undefined_order').trim(); + + // Validate order name + if (!orderName) { + console.warn("Empty 'ORDER_NAME' in 'order_status' block. Using 'undefined_order'."); + } // Retrieve the selected order status from the dropdown - const orderStatus = block.getFieldValue('order_status'); // 'filled', 'unfilled', 'partial' - - // Validate the order name - if (!orderName || orderName.trim() === "") { - console.warn("Empty ORDER_NAME in order_status block. Defaulting to 'undefined_order'."); - } - - // Validate the order status + const orderStatus = block.getFieldValue('ORDER_STATUS'); // 'filled', 'unfilled', 'partial' const validStatuses = ['filled', 'unfilled', 'partial']; + const validatedStatus = validStatuses.includes(orderStatus) ? orderStatus : 'filled'; + if (!validStatuses.includes(orderStatus)) { - console.warn(`Invalid order_status "${orderStatus}" in order_status block. Defaulting to 'filled'.`); + console.error(`Invalid order status '${orderStatus}' in 'order_status' block. Defaulting to 'filled'.`); } - const json = { - type: 'order_status', - order_name: orderName && orderName.trim() !== "" ? orderName : 'undefined_order', - status: validStatuses.includes(orderStatus) ? orderStatus : 'filled' - }; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('order_status', { + order_name: orderName || 'undefined_order', + status: validatedStatus + }); - return JSON.stringify(json); + // Output as dynamic_value (since this produces a Boolean value) + return operationObject; }; } diff --git a/src/static/blocks/generators/risk_management_generators.js b/src/static/blocks/generators/risk_management_generators.js index 08ebd41..9343203 100644 --- a/src/static/blocks/generators/risk_management_generators.js +++ b/src/static/blocks/generators/risk_management_generators.js @@ -1,165 +1,108 @@ -// risk_management_generators.js - /** * Risk Management Generators Definitions * * This file contains generator functions for each block defined in * risk_management_blocks.js. Each generator translates a Blockly block into a JSON - * object representing risk management operations such as setting leverage, - * retrieving margin, calculating risk ratio, and setting maximum position size. - * - * Note: This file assumes that `json_base_generator.js` has been loaded - * before it, which initializes `Blockly.JSON` and attaches helper functions. + * object representing risk management operations. */ 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."); + return; + } + /** - * Generator for 'set_leverage' Block + * Helper Function: processNumericInput + * Processes a numeric input and ensures it is a valid number. * - * Description: - * Generates a JSON object representing the action of setting the leverage ratio - * for the trading strategy. Validates the leverage input to ensure it meets - * defined constraints. + * @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. */ - Blockly.JSON['set_leverage'] = function(block) { - // Retrieve and parse the 'LEVERAGE' input - const leverageCode = Blockly.JSON.valueToCode(block, 'LEVERAGE', Blockly.JSON.ORDER_ATOMIC) || "1"; - let leverage; - try { - leverage = Blockly.JSON.safeParse(leverageCode); - if (typeof leverage !== 'number') { - throw new Error("Leverage input is not a number."); + function processNumericInput(block, inputName, defaultValue, minValue = Number.NEGATIVE_INFINITY, maxValue = Number.POSITIVE_INFINITY) { + const inputBlock = block.getInputTargetBlock(inputName); + let value = defaultValue; + + if (inputBlock) { + const inputJson = Blockly.JSON.blockToCode(inputBlock); + if (typeof inputJson === 'number') { + value = inputJson; + } else { + console.error(`Invalid value for '${inputName}' in '${block.type}' block. Expected a number.`); } - // Optional: Enforce leverage limits (e.g., 1x to 100x) - if (leverage < 1 || leverage > 100) { - console.warn(`Leverage value ${leverage} is out of bounds. Clamping to [1, 100].`); - leverage = Math.min(Math.max(leverage, 1), 100); - } - } catch (e) { - console.warn(`Error parsing 'LEVERAGE' input in set_leverage block: ${e.message}. Defaulting to 1.`); - leverage = 1; + } else { + console.warn(`No block connected to '${inputName}' input of '${block.type}' block. Using default value ${defaultValue}.`); } - const json = { - type: 'set_leverage', + // 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 + + // Create operation object + const json = Blockly.JSON.createOperationObject('set_leverage', { leverage: leverage - }; + }); - // Handle chaining with 'NEXT' if applicable (optional) - // Since 'set_leverage' has previous and next statements, chaining is handled differently - // Typically, in statement blocks, 'next' is handled by Blockly itself, not manually in the generator - // Therefore, no need to handle 'NEXT' here unless implementing custom chaining logic - - return JSON.stringify(json); + // Output as dynamic_value + return json; }; /** * Generator for 'current_margin' Block - * - * Description: - * Generates a JSON object representing the retrieval of the current margin balance - * utilized by the trading strategy. Supports chaining with the 'NEXT' input. */ Blockly.JSON['current_margin'] = function(block) { - // Retrieve and parse the 'NEXT' input - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('current_margin', {}); - const json = { - type: 'current_margin' - }; - - // Handle 'NEXT' input for chaining - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - if (Array.isArray(nextJson)) { - // If NEXT is a list, concatenate - json.next = [json.type, ...nextJson]; - } else { - // If NEXT is a single value, create a list - json.next = [json.type, nextJson]; - } - } catch (e) { - console.warn(`Failed to parse 'NEXT' input in current_margin block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.next = [json.type, nextCode]; - } - } - - return JSON.stringify(json); + // Output as dynamic_value + return operationObject; }; /** * Generator for 'risk_ratio' Block - * - * Description: - * Generates a JSON object representing the calculation and retrieval of the current - * risk ratio of the trading strategy. Supports chaining with the 'NEXT' input. */ Blockly.JSON['risk_ratio'] = function(block) { - // Retrieve and parse the 'NEXT' input - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('risk_ratio', {}); - const json = { - type: 'risk_ratio' - }; - - // Handle 'NEXT' input for chaining - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - if (Array.isArray(nextJson)) { - // If NEXT is a list, concatenate - json.next = [json.type, ...nextJson]; - } else { - // If NEXT is a single value, create a list - json.next = [json.type, nextJson]; - } - } catch (e) { - console.warn(`Failed to parse 'NEXT' input in risk_ratio block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.next = [json.type, nextCode]; - } - } - - return JSON.stringify(json); + // Output as dynamic_value + return operationObject; }; /** * Generator for 'max_position_size' Block - * - * Description: - * Generates a JSON object representing the action of setting the maximum position - * size the trading strategy can hold. Validates the maximum size input to ensure - * it meets defined constraints. */ Blockly.JSON['max_position_size'] = function(block) { - // Retrieve and parse the 'MAX_SIZE' input - const maxSizeCode = Blockly.JSON.valueToCode(block, 'MAX_SIZE', Blockly.JSON.ORDER_ATOMIC) || "1"; - let maxSize; - try { - maxSize = Blockly.JSON.safeParse(maxSizeCode); - if (typeof maxSize !== 'number') { - throw new Error("Max Position Size input is not a number."); - } - // Optional: Enforce maximum position size limits (e.g., min 1) - if (maxSize < 1) { - console.warn(`Max Position Size value ${maxSize} is below minimum. Clamping to 1.`); - maxSize = 1; - } - } catch (e) { - console.warn(`Error parsing 'MAX_SIZE' input in max_position_size block: ${e.message}. Defaulting to 1.`); - maxSize = 1; - } + // Process the 'MAX_SIZE' input + const maxSize = processNumericInput(block, 'MAX_SIZE', 1, 1); // Minimum of 1 - const json = { - type: 'max_position_size', + // Create operation object + const json = Blockly.JSON.createOperationObject('max_position_size', { max_size: maxSize - }; + }); - // Handle chaining with 'NEXT' if applicable (optional) - // Similar to 'set_leverage', no need to manually handle 'NEXT' in statement blocks - - return JSON.stringify(json); + // Output as dynamic_value + return json; }; } diff --git a/src/static/blocks/generators/time_metrics_generators.js b/src/static/blocks/generators/time_metrics_generators.js index 9edf25a..f521226 100644 --- a/src/static/blocks/generators/time_metrics_generators.js +++ b/src/static/blocks/generators/time_metrics_generators.js @@ -1,63 +1,49 @@ -// time_metrics_generators.js - /** * Time Metrics Generators Definitions * * This file contains generator functions for each block defined in * time_metrics_blocks.js. Each generator translates a Blockly block into a JSON - * object representing time metrics retrieval operations such as fetching the - * time elapsed since the strategy started. - * - * Note: This file assumes that `json_base_generator.js` has been loaded - * before it, which initializes `Blockly.JSON` and attaches helper functions. + * object representing time metrics retrieval operations. */ export function defineTimeMetricsGenerators() { + + // 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."); + return; + } + + /** + * Helper Function: validateTimeUnit + * Validates the time unit and returns a default if invalid. + * + * @param {string} timeUnit - The time unit to validate. + * @returns {string} - Validated time unit. + */ + function validateTimeUnit(timeUnit) { + const validUnits = ['seconds', 'minutes', 'hours']; + if (!validUnits.includes(timeUnit)) { + console.error(`Invalid time unit '${timeUnit}' in 'time_since_start' block. Defaulting to 'seconds'.`); + return 'seconds'; + } + return timeUnit; + } + /** * Generator for 'time_since_start' Block - * - * Description: - * Generates a JSON object representing the retrieval of the time elapsed since - * the trading strategy started. Users can specify the unit of time (seconds, - * minutes, hours) through the 'TIME_UNIT' dropdown. Supports chaining with - * the 'NEXT' input to facilitate sequences or lists of operations. */ Blockly.JSON['time_since_start'] = function(block) { // Retrieve the selected time unit from the dropdown - const timeUnit = block.getFieldValue('TIME_UNIT'); // 'seconds', 'minutes', 'hours' + const timeUnit = block.getFieldValue('TIME_UNIT') || 'seconds'; + const validatedTimeUnit = validateTimeUnit(timeUnit); - // Validate the time unit - const validUnits = ['seconds', 'minutes', 'hours']; - if (!validUnits.includes(timeUnit)) { - console.warn(`Invalid TIME_UNIT "${timeUnit}" in time_since_start block. Defaulting to 'seconds'.`); - } + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('time_since_start', { + unit: validatedTimeUnit + }); - const unit = validUnits.includes(timeUnit) ? timeUnit : 'seconds'; - - const json = { - type: 'time_since_start', - unit: unit - }; - - // Handle 'NEXT' input for chaining - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - if (Array.isArray(nextJson)) { - // If NEXT is a list, concatenate - json.next = [json, ...nextJson]; - } else { - // If NEXT is a single value, create a list - json.next = [json, nextJson]; - } - } catch (e) { - console.warn(`Failed to parse 'NEXT' input in time_since_start block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.next = [json, nextCode]; - } - } - - return JSON.stringify(json); + // Output as dynamic_value + return operationObject; }; } diff --git a/src/static/blocks/generators/trade_metrics_generators.js b/src/static/blocks/generators/trade_metrics_generators.js index f3743b1..c7d68a6 100644 --- a/src/static/blocks/generators/trade_metrics_generators.js +++ b/src/static/blocks/generators/trade_metrics_generators.js @@ -1,81 +1,43 @@ -// trade_metrics_generators.js - /** * Trade Metrics Generators Definitions * * This file contains generator functions for each block defined in * trade_metrics_blocks.js. Each generator translates a Blockly block into a JSON - * object representing trade metrics retrieval operations such as fetching active trades, - * total trades, last trade details, average entry price, unrealized profit/loss, and user active trades. - * - * Note: This file assumes that `json_base_generator.js` has been loaded - * before it, which initializes `Blockly.JSON` and attaches helper functions. + * object representing trade metrics retrieval operations. */ export function defineTradeMetricsGenerators() { + + // 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."); + return; + } + /** * Generator for 'active_trades' Block - * - * Description: - * Generates a JSON object representing the retrieval of the number of active trades - * for the current strategy. Supports chaining with the 'NEXT' input to facilitate - * sequences or lists of operations. */ Blockly.JSON['active_trades'] = function(block) { - // Create JSON for 'active_trades' block - const json = { type: 'active_trades' }; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('active_trades', {}); - // Handle 'NEXT' input for chaining - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - json.next = nextJson; - } catch (e) { - console.warn(`Failed to parse 'NEXT' input in active_trades block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.next = nextCode; - } - } - - return JSON.stringify(json); + // Output as dynamic_value + return JSON.stringify(operationObject); }; /** * Generator for 'total_trades' Block - * - * Description: - * Generates a JSON object representing the retrieval of the total number of trades - * executed by the current strategy. Supports chaining with the 'NEXT' input to - * facilitate sequences or lists of operations. */ Blockly.JSON['total_trades'] = function(block) { - // Create JSON for 'total_trades' block - const json = { type: 'total_trades' }; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('total_trades', {}); - // Handle 'NEXT' input for chaining - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - json.next = nextJson; - } catch (e) { - console.warn(`Failed to parse 'NEXT' input in total_trades block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.next = nextCode; - } - } - - return JSON.stringify(json); + // Output as dynamic_value + return JSON.stringify(operationObject); }; /** * Generator for 'last_trade_details' Block - * - * Description: - * Generates a JSON object representing the retrieval of specific details - * (Price, Volume, Direction) of the most recent trade executed by the current strategy. - * Supports chaining with the 'NEXT' input to facilitate sequences or lists of operations. */ Blockly.JSON['last_trade_details'] = function(block) { // Retrieve the 'DETAILS' field input @@ -83,113 +45,47 @@ export function defineTradeMetricsGenerators() { // Validate the details selection const validDetails = ['price', 'volume', 'direction']; - if (!validDetails.includes(details)) { - console.warn(`Invalid DETAILS "${details}" in last_trade_details block. Defaulting to 'price'.`); - } const validatedDetails = validDetails.includes(details) ? details : 'price'; - const json = { - type: 'last_trade_details', + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('last_trade_details', { details: validatedDetails - }; + }); - // Handle 'NEXT' input for chaining - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - json.next = nextJson; - } catch (e) { - console.warn(`Failed to parse 'NEXT' input in last_trade_details block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.next = nextCode; - } - } - - return JSON.stringify(json); + // Output as dynamic_value + return JSON.stringify(operationObject); }; /** * Generator for 'average_entry_price' Block - * - * Description: - * Generates a JSON object representing the retrieval of the average entry price - * of all open positions within the current strategy. Supports chaining with the 'NEXT' input - * to facilitate sequences or lists of operations. */ Blockly.JSON['average_entry_price'] = function(block) { - // Create JSON for 'average_entry_price' block - const json = { type: 'average_entry_price' }; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('average_entry_price', {}); - // Handle 'NEXT' input for chaining - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - json.next = nextJson; - } catch (e) { - console.warn(`Failed to parse 'NEXT' input in average_entry_price block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.next = nextCode; - } - } - - return JSON.stringify(json); + // Output as dynamic_value + return JSON.stringify(operationObject); }; /** * Generator for 'unrealized_profit_loss' Block - * - * Description: - * Generates a JSON object representing the retrieval of the total unrealized profit - * or loss across all current positions of the current strategy. Supports chaining with the 'NEXT' input - * to facilitate sequences or lists of operations. */ Blockly.JSON['unrealized_profit_loss'] = function(block) { - // Create JSON for 'unrealized_profit_loss' block - const json = { type: 'unrealized_profit_loss' }; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('unrealized_profit_loss', {}); - // Handle 'NEXT' input for chaining - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - json.next = nextJson; - } catch (e) { - console.warn(`Failed to parse 'NEXT' input in unrealized_profit_loss block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.next = nextCode; - } - } - - return JSON.stringify(json); + // Output as dynamic_value + return JSON.stringify(operationObject); }; /** * Generator for 'user_active_trades' Block - * - * Description: - * Generates a JSON object representing the retrieval of the number of active trades - * across all strategies and symbols for the user. This provides an aggregate view - * of the user's active trading positions. Supports chaining with the 'NEXT' input. */ Blockly.JSON['user_active_trades'] = function(block) { - // Create JSON for 'user_active_trades' block - const json = { type: 'user_active_trades' }; + // Create operation object + const operationObject = Blockly.JSON.createOperationObject('user_active_trades', {}); - // Handle 'NEXT' input for chaining - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - json.next = nextJson; - } catch (e) { - console.warn(`Failed to parse 'NEXT' input in user_active_trades block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.next = nextCode; - } - } - - return JSON.stringify(json); + // Output as dynamic_value + return JSON.stringify(operationObject); }; } diff --git a/src/static/blocks/generators/trade_order_generators.js b/src/static/blocks/generators/trade_order_generators.js index 0ad2c25..a30fe3c 100644 --- a/src/static/blocks/generators/trade_order_generators.js +++ b/src/static/blocks/generators/trade_order_generators.js @@ -1,351 +1,291 @@ -// trade_order_generators.js - /** * Trade Order Generators Definitions * * This file contains generator functions for each block defined in * trade_order_blocks.js. Each generator translates a Blockly block into a JSON - * object representing trade order operations such as executing trade actions, - * setting time in force, stop loss, take profit, limit prices, trailing stops, - * defining target markets, and naming orders. - * - * Note: This file assumes that `json_base_generator.js` has been loaded - * before it, which initializes `Blockly.JSON` and attaches helper functions. + * object representing trade order operations. */ export function defineTradeOrderGenerators() { + + // 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."); + return; + } + /** * Generator for 'trade_action' Block - * - * Description: - * Generates a JSON object representing the execution of a trade action - * (Buy/Sell), specifying the amount and optional trade options. - * Does NOT support chaining via 'NEXT'; instead, it integrates within - * Blockly's statement sequencing. */ - Blockly.JSON['trade_action'] = function(block) { - // Retrieve the trade type (Buy/Sell) - const tradeType = block.getFieldValue('tradeType'); // 'buy' or 'sell' - const validTradeTypes = ['buy', 'sell']; - const type = validTradeTypes.includes(tradeType) ? tradeType : 'buy'; - - // Retrieve and parse the trade size - const sizeCode = Blockly.JSON.valueToCode(block, 'size', Blockly.JSON.ORDER_ATOMIC) || "0"; - let size; - try { - size = Blockly.JSON.safeParse(sizeCode); - if (typeof size !== 'number') { - throw new Error("Size input is not a number."); + Blockly.JSON['trade_action'] = function(block, argsCount = null, maxDepth = 10) { + /** + * Helper Function: collectTradeOptions + * Recursively collects all connected trade_options blocks. + * + * @param {Blockly.Block} block - The first trade_options block. + * @returns {Array} - Array of trade_options JSON objects. + */ + function collectTradeOptions(block) { + let options = []; + while (block) { + const optionJson = Blockly.JSON._blockToJson(block); + if (optionJson) { + delete optionJson.next; + options.push(optionJson); + } + block = block.getNextBlock(); } - if (size <= 0) { - console.warn(`Trade size ${size} is not positive. Defaulting to 1.`); - size = 1; + return options; + }; + /** + * Helper Function: validateTradeType + * Validates the trade type and returns a default if invalid. + * + * @param {string} tradeType - The trade type to validate. + * @returns {string} - Validated trade type. + */ + function validateTradeType(tradeType) { + const validTradeTypes = ['buy', 'sell']; + if (!validTradeTypes.includes(tradeType)) { + console.error(`Invalid trade type '${tradeType}' in 'trade_action' block. Defaulting to 'buy'.`); + return 'buy'; } - } catch (e) { - console.warn(`Error parsing 'size' input in trade_action block: ${e.message}. Defaulting to 1.`); - size = 1; + return tradeType; } - // Initialize the JSON object + // Retrieve and validate the trade type + const tradeType = block.getFieldValue('tradeType') || 'buy'; + const validatedTradeType = validateTradeType(tradeType); + + // Retrieve and process the trade size + const size = Blockly.JSON.processNumericInput(block, 'size', 1); // Default size is 1 + + // **Assign the processed 'size' to 'inputs.size'** const json = { type: 'trade_action', - trade_type: type, - size: size + trade_type: validatedTradeType, + inputs: { + size: size + } }; - // Handle 'trade_options' input (statement) - const optionsJson = Blockly.JSON.statementToCode(block, 'trade_options'); - if (optionsJson) { - try { - // Assume 'trade_options' generates a JSON object or array - const parsedOptions = JSON.parse(optionsJson); - json.trade_options = parsedOptions; - } catch (e) { - console.warn(`Failed to parse 'trade_options' in trade_action block: ${e.message}`); - // Optionally, handle the error or set trade_options to null/empty - json.trade_options = null; + // **Process 'trade_options' statement input within the generator** + const tradeOptionsFirstBlock = block.getInputTargetBlock('trade_options'); + if (tradeOptionsFirstBlock) { + const allTradeOptions = collectTradeOptions(tradeOptionsFirstBlock); + if (allTradeOptions && allTradeOptions.length > 0) { + json.trade_options = allTradeOptions; } } - // Handle previous and next statements via Blockly's statement connections - // No need to manually handle 'NEXT' input; Blockly manages the sequencing + // **Set the skipAdditionalParsing flag** + json.skipAdditionalParsing = true; - return JSON.stringify(json); + console.log(`Generated JSON for 'trade_action' block:`, json); + return json; }; /** * Generator for 'time_in_force' Block - * - * Description: - * Generates a JSON object representing the setting of the Time in Force parameter - * for an order. Integrates within Blockly's statement sequencing. */ Blockly.JSON['time_in_force'] = function(block) { - // Retrieve the Time in Force value - const tif = block.getFieldValue('tif'); // 'gtc', 'fok', 'ioc' + // Retrieve the Time in Force value from the dropdown + const tif = block.getFieldValue('tif') || 'gtc'; const validTIF = ['gtc', 'fok', 'ioc']; - const time_in_force = validTIF.includes(tif) ? tif : 'gtc'; + const validatedTIF = validTIF.includes(tif) ? tif : 'gtc'; + if (!validTIF.includes(tif)) { + console.error(`Invalid Time in Force '${tif}' in 'time_in_force' block. Defaulting to 'gtc'.`); + } + + // Create operation object const json = { type: 'time_in_force', - time_in_force: time_in_force + inputs: { + time_in_force: validatedTIF + } }; - // Handle previous and next statements via Blockly's statement connections - // No need to handle 'NEXT' input; Blockly manages the sequencing + // No need to handle 'NEXT' input; sequencing is managed by _blockToJson - return JSON.stringify(json); + return json; }; /** * Generator for 'stop_loss' Block - * - * Description: - * Generates a JSON object representing the setting of a Stop Loss parameter - * for a trade. Integrates within Blockly's statement sequencing. */ Blockly.JSON['stop_loss'] = function(block) { - // Retrieve and parse the stop loss input - const stopLossCode = Blockly.JSON.valueToCode(block, 'stop_loss_input', Blockly.JSON.ORDER_ATOMIC) || "0"; - let stop_loss; - try { - stop_loss = Blockly.JSON.safeParse(stopLossCode); - if (typeof stop_loss !== 'number') { - throw new Error("Stop Loss input is not a number."); - } - if (stop_loss <= 0) { - console.warn(`Stop Loss value ${stop_loss} is not positive. Defaulting to 1.`); - stop_loss = 1; - } - } catch (e) { - console.warn(`Error parsing 'stop_loss_input' in stop_loss block: ${e.message}. Defaulting to 1.`); - stop_loss = 1; + // Retrieve and process the 'stop_loss_input' numeric input + const stop_loss = Blockly.JSON.processNumericInput(block, 'stop_loss_input', 0); + + // Validate stop_loss + if (stop_loss <= 0) { + console.warn(`Invalid stop_loss value '${stop_loss}' in 'stop_loss' block. Using default value 0.`); } + // Create operation object const json = { type: 'stop_loss', - stop_loss: stop_loss + inputs: { + stop_loss: stop_loss + } }; - // Handle previous and next statements via Blockly's statement connections - // No need to handle 'NEXT' input; Blockly manages the sequencing + // No need to handle 'NEXT' input; sequencing is managed by _blockToJson - return JSON.stringify(json); - }; - - /** - * Generator for 'trailing_stop' Block - * - * Description: - * Generates a JSON object representing the setting of a Trailing Stop parameter - * for a trade. Integrates within Blockly's statement sequencing. - */ - Blockly.JSON['trailing_stop'] = function(block) { - // Retrieve and parse the trailing stop distance - const trailDistanceCode = Blockly.JSON.valueToCode(block, 'trail_distance', Blockly.JSON.ORDER_ATOMIC) || "0"; - let trail_distance; - try { - trail_distance = Blockly.JSON.safeParse(trailDistanceCode); - if (typeof trail_distance !== 'number') { - throw new Error("Trailing Stop distance is not a number."); - } - if (trail_distance <= 0) { - console.warn(`Trailing Stop distance ${trail_distance} is not positive. Defaulting to 1.`); - trail_distance = 1; - } - } catch (e) { - console.warn(`Error parsing 'trail_distance' in trailing_stop block: ${e.message}. Defaulting to 1.`); - trail_distance = 1; - } - - const json = { - type: 'trailing_stop', - trail_distance: trail_distance - }; - - // Handle previous and next statements via Blockly's statement connections - // No need to handle 'NEXT' input; Blockly manages the sequencing - - return JSON.stringify(json); + return json; }; /** * Generator for 'take_profit' Block - * - * Description: - * Generates a JSON object representing the setting of a Take Profit parameter - * for a trade. Integrates within Blockly's statement sequencing. */ Blockly.JSON['take_profit'] = function(block) { - // Retrieve and parse the take profit input - const takeProfitCode = Blockly.JSON.valueToCode(block, 'take_profit_input', Blockly.JSON.ORDER_ATOMIC) || "0"; - let take_profit; - try { - take_profit = Blockly.JSON.safeParse(takeProfitCode); - if (typeof take_profit !== 'number') { - throw new Error("Take Profit input is not a number."); - } - if (take_profit <= 0) { - console.warn(`Take Profit value ${take_profit} is not positive. Defaulting to 1.`); - take_profit = 1; - } - } catch (e) { - console.warn(`Error parsing 'take_profit_input' in take_profit block: ${e.message}. Defaulting to 1.`); - take_profit = 1; + // Retrieve and process the 'take_profit_input' numeric input + const take_profit = Blockly.JSON.processNumericInput(block, 'take_profit_input', 0); + + // Validate take_profit + if (take_profit <= 0) { + console.warn(`Invalid take_profit value '${take_profit}' in 'take_profit' block. Using default value 0.`); } + // Create operation object const json = { type: 'take_profit', - take_profit: take_profit + inputs: { + take_profit: take_profit + } }; - // Handle previous and next statements via Blockly's statement connections - // No need to handle 'NEXT' input; Blockly manages the sequencing + // No need to handle 'NEXT' input; sequencing is managed by _blockToJson - return JSON.stringify(json); + return json; }; /** * Generator for 'limit' Block - * - * Description: - * Generates a JSON object representing the setting of a Limit price for a trade order. - * Integrates within Blockly's statement sequencing. */ Blockly.JSON['limit'] = function(block) { - // Retrieve and parse the limit input - const limitCode = Blockly.JSON.valueToCode(block, 'limit_input', Blockly.JSON.ORDER_ATOMIC) || "0"; - let limit; - try { - limit = Blockly.JSON.safeParse(limitCode); - if (typeof limit !== 'number') { - throw new Error("Limit input is not a number."); - } - if (limit <= 0) { - console.warn(`Limit value ${limit} is not positive. Defaulting to 1.`); - limit = 1; - } - } catch (e) { - console.warn(`Error parsing 'limit_input' in limit block: ${e.message}. Defaulting to 1.`); - limit = 1; + // Retrieve and process the 'limit_input' numeric input + const limit = Blockly.JSON.processNumericInput(block, 'limit_input', 0); + + // Validate limit + if (limit <= 0) { + console.warn(`Invalid limit value '${limit}' in 'limit' block. Using default value 0.`); } + // Create operation object const json = { type: 'limit', - limit: limit + inputs: { + limit: limit + } }; - // Handle previous and next statements via Blockly's statement connections - // No need to handle 'NEXT' input; Blockly manages the sequencing + // No need to handle 'NEXT' input; sequencing is managed by _blockToJson - return JSON.stringify(json); + return json; + }; + + /** + * Generator for 'trailing_stop' Block + */ + Blockly.JSON['trailing_stop'] = function(block) { + // Retrieve and process the 'trail_distance' numeric input + const trail_distance = Blockly.JSON.processNumericInput(block, 'trail_distance', 0); + + // Validate trail_distance + if (trail_distance <= 0) { + console.warn(`Invalid trail_distance value '${trail_distance}' in 'trailing_stop' block. Using default value 0.`); + } + + // Create operation object + const json = { + type: 'trailing_stop', + inputs: { + trail_distance: trail_distance + } + }; + + // No need to handle 'NEXT' input; sequencing is managed by _blockToJson + + return json; }; /** * Generator for 'trailing_limit' Block - * - * Description: - * Generates a JSON object representing the setting of a Trailing Limit parameter - * for a trade order. Integrates within Blockly's statement sequencing. */ Blockly.JSON['trailing_limit'] = function(block) { - // Retrieve and parse the trailing limit distance - const trailLimitDistanceCode = Blockly.JSON.valueToCode(block, 'trail_limit_distance', Blockly.JSON.ORDER_ATOMIC) || "0"; - let trail_limit_distance; - try { - trail_limit_distance = Blockly.JSON.safeParse(trailLimitDistanceCode); - if (typeof trail_limit_distance !== 'number') { - throw new Error("Trailing Limit distance is not a number."); - } - if (trail_limit_distance <= 0) { - console.warn(`Trailing Limit distance ${trail_limit_distance} is not positive. Defaulting to 1.`); - trail_limit_distance = 1; - } - } catch (e) { - console.warn(`Error parsing 'trail_limit_distance' in trailing_limit block: ${e.message}. Defaulting to 1.`); - trail_limit_distance = 1; + // Retrieve and process the 'trail_limit_distance' numeric input + const trail_limit_distance = Blockly.JSON.processNumericInput(block, 'trail_limit_distance', 0); + + // Validate trail_limit_distance + if (trail_limit_distance <= 0) { + console.warn(`Invalid trail_limit_distance value '${trail_limit_distance}' in 'trailing_limit' block. Using default value 0.`); } + // Create operation object const json = { type: 'trailing_limit', - trail_limit_distance: trail_limit_distance + inputs: { + trail_limit_distance: trail_limit_distance + } }; - // Handle previous and next statements via Blockly's statement connections - // No need to handle 'NEXT' input; Blockly manages the sequencing + // No need to handle 'NEXT' input; sequencing is managed by _blockToJson - return JSON.stringify(json); + return json; }; /** * Generator for 'target_market' Block - * - * Description: - * Generates a JSON object representing the specification of the target market - * for executing trades, including Time Frame, Exchange, and Symbol. - * Integrates within Blockly's statement sequencing. */ Blockly.JSON['target_market'] = function(block) { // Retrieve the Time Frame, Exchange, and Symbol selections - const tf = block.getFieldValue('TF'); // Time Frame - const exc = block.getFieldValue('EXC'); // Exchange - const sym = block.getFieldValue('SYM'); // Symbol + const timeFrame = block.getFieldValue('TF') || '1m'; // Default to '1m' + const exchange = block.getFieldValue('EXC') || 'Binance'; // Default to 'Binance' + const symbol = block.getFieldValue('SYM') || 'BTCUSDT'; // Default to 'BTCUSDT' - // Validate selections - const timeFrameOption = timeframeOptions.find(option => option[1] === tf); - if (!timeFrameOption) { - console.warn(`Invalid Time Frame "${tf}" in target_market block. Defaulting to first available option.`); - } - const time_frame = timeFrameOption ? tf : timeframeOptions[0][1]; - - const exchangeOption = exchangeOptions.find(option => option[1] === exc); - if (!exchangeOption) { - console.warn(`Invalid Exchange "${exc}" in target_market block. Defaulting to first available option.`); - } - const exchange = exchangeOption ? exc : exchangeOptions[0][1]; - - const symbolOption = symbolOptions.find(option => option[1] === sym); - if (!symbolOption) { - console.warn(`Invalid Symbol "${sym}" in target_market block. Defaulting to first available option.`); - } - const symbol = symbolOption ? sym : symbolOptions[0][1]; + // Validate fields if necessary + // Example: Ensure timeFrame matches expected patterns + // Skipping detailed validation for brevity + // Create operation object const json = { type: 'target_market', - time_frame: time_frame, - exchange: exchange, - symbol: symbol + inputs: { + time_frame: timeFrame, + exchange: exchange, + symbol: symbol + } }; - // Handle previous and next statements via Blockly's statement connections - // No need to handle 'NEXT' input; Blockly manages the sequencing + // No need to handle 'NEXT' input; sequencing is managed by _blockToJson - return JSON.stringify(json); + return json; }; /** * Generator for 'name_order' Block - * - * Description: - * Generates a JSON object representing the assignment of a custom name to the current order. - * Integrates within Blockly's statement sequencing. */ Blockly.JSON['name_order'] = function(block) { // Retrieve the order name from the input field - const orderName = block.getFieldValue('order_name').trim(); + const orderName = (block.getFieldValue('order_name') || 'Unnamed Order').trim(); + // Validate order name if (!orderName) { - console.warn("Empty 'order_name' in name_order block. Defaulting to 'Unnamed Order'."); + console.warn("Empty 'order_name' in 'name_order' block. Using 'Unnamed Order'."); } + // Create operation object const json = { type: 'name_order', - order_name: orderName || 'Unnamed Order' + inputs: { + order_name: orderName || 'Unnamed Order' + } }; - // Handle previous and next statements via Blockly's statement connections - // No need to handle 'NEXT' input; Blockly manages the sequencing + // No need to handle 'NEXT' input; sequencing is managed by _blockToJson - return JSON.stringify(json); + return json; }; } diff --git a/src/static/blocks/generators/values_and_flags_generators.js b/src/static/blocks/generators/values_and_flags_generators.js index 5de3c58..a3ae0cb 100644 --- a/src/static/blocks/generators/values_and_flags_generators.js +++ b/src/static/blocks/generators/values_and_flags_generators.js @@ -33,178 +33,211 @@ export function defineVAFGenerators() { type: 'notify_user', message: message && message.trim() !== "" ? message : 'No message provided.' }; - - // Handle 'NEXT' input if connected (optional) - // Since 'notify_user' block does not have a 'NEXT' input, this section is optional - /* - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - json.next = nextJson; - } catch (e) { - console.warn(`Failed to parse NEXT input in notify_user block: ${e.message}`); - } - } - */ - - return JSON.stringify(json); + return json; }; /** * Generator for 'value_input' Block * - * Description: - * Generates a JSON object representing a numerical value input. - * Supports chaining by handling the 'NEXT' input if connected. + * 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) { - // Retrieve the 'VALUE' field input and parse it as a float - const valueStr = block.getFieldValue('VALUE'); - const value = parseFloat(valueStr); + const values = []; // Initialize the values array - if (isNaN(value)) { - console.warn(`Invalid VALUE "${valueStr}" in value_input block. Defaulting to 0.`); + /** + * Recursively processes each block in the chain. + * + * @param {Block} currentBlock - The current block to process. + * @param {number} currentDepth - The current depth in the recursion. + */ + function processBlock(currentBlock, currentDepth) { + if (!currentBlock || currentDepth > 10) { // Enforce max depth and skip if block is missing + if (currentDepth > 10) { + console.warn("Max depth exceeded in value_input chain. Stopping aggregation."); + } + return; + } + + // Process 'VALUE' and add to values + const valueJson = processConnectedBlock(currentBlock, 'VALUE'); + if (valueJson !== null) values.push(valueJson); + + // Process 'NEXT' block in the chain + const nextBlock = currentBlock.getInputTargetBlock('NEXT'); + if (nextBlock) processBlock(nextBlock, currentDepth + 1); } + /** + * Processes a connected block and retrieves its generated JSON or field value. + * + * @param {Block} block - The current block to process. + * @param {string} inputName - The input name to retrieve. + * @returns {*} The processed value or null if invalid. + */ + function processConnectedBlock(block, inputName) { + const connectedBlock = block.getInputTargetBlock(inputName); + if (connectedBlock) { + 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; + } + 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); + if (isNaN(fieldValue)) { + console.warn(`Invalid VALUE "${block.getFieldValue(inputName)}" in value_input block. Skipping this block.`); + return null; + } + return fieldValue; + } + + processBlock(block, 1); // Start processing from the initial block + const json = { - type: 'value_input', - value: isNaN(value) ? 0 : value + type: 'dynamic_value', + values: values.flat(), // Flatten values array to handle nested arrays + skipAdditionalParsing: true // Flag to skip further parsing }; - // Handle 'NEXT' input for chaining - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - if (Array.isArray(nextJson)) { - // If NEXT is a list, concatenate - json.values = [json.value, ...nextJson]; - } else { - // If NEXT is a single value, create a list - json.values = [json.value, nextJson]; - } - } catch (e) { - console.warn(`Failed to parse NEXT input in value_input block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.values = [json.value, nextCode]; - } - } else { - // If no NEXT, wrap value in a list for consistency - json.values = [json.value]; - } - - // Remove 'value' if 'values' exists to maintain consistency - if (json.values) { - delete json.value; - } - - return JSON.stringify(json); + console.log(`Generated JSON for 'value_input' block:`, json); + return json; }; + + + + /** * Generator for 'get_variable' Block * * Description: - * Generates a JSON object to retrieve the value of a specified variable. - * Supports chaining with the 'NEXT' input to facilitate lists or sequences. + * Generates a JSON object representing a list of variable names. + * Handles 'NEXT' inputs to support dynamic block chaining. + * + * @param {Block} block - The current block to generate JSON for. + * @param {number|null} argsCount - Number of required variable names. + * null: Aggregate all connected blocks. + * n (positive integer): Aggregate up to 'n' variable names. + * @param {number} maxDepth - A limit on the recursion depth to prevent infinite loops. + * @returns {Object} The generated JSON object. */ - Blockly.JSON['get_variable'] = function(block) { - // Retrieve the 'variable_name' field input - const variableName = block.getFieldValue('variable_name'); + Blockly.JSON['get_variable'] = function(block, argsCount = null, maxDepth = 10) { + // Initialize the variables array + const variables = []; - // Validate the variable name - if (!variableName || variableName.trim() === "") { - console.warn("Empty variable_name in get_variable block. Defaulting to 'undefined_var'."); - } - - const json = { - type: 'get_variable', - variable_name: variableName && variableName.trim() !== "" ? variableName : 'undefined_var' - }; - - // Handle 'NEXT' input for chaining - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - if (Array.isArray(nextJson)) { - // If NEXT is a list, concatenate - json.variables = [json.variable_name, ...nextJson]; - } else { - // If NEXT is a single value, create a list - json.variables = [json.variable_name, nextJson]; - } - } catch (e) { - console.warn(`Failed to parse NEXT input in get_variable block: ${e.message}`); - // If parsing fails, treat NEXT as a single value - json.variables = [json.variable_name, nextCode]; + /** + * Recursively processes each block in the chain. + * + * @param {Block} currentBlock - The current block to process. + * @param {number} currentDepth - The current depth in the recursion. + */ + function processBlock(currentBlock, currentDepth) { + if (!currentBlock) { + return; + } + + if (currentDepth > maxDepth) { + console.warn("Max depth exceeded in get_variable chain. Stopping aggregation."); + return; + } + + // Retrieve the 'variable_name' field input + const variableName = currentBlock.getFieldValue('variable_name'); + const trimmedName = variableName && variableName.trim() !== "" ? variableName.trim() : 'undefined_var'; + if (variableName.trim() === "") { + console.warn("Empty variable_name in get_variable block. Defaulting to 'undefined_var'."); + } + + variables.push({ 'variable': trimmedName }); + + // Process the 'NEXT' connection + const nextBlock = currentBlock.getInputTargetBlock('NEXT'); + if (nextBlock) { + const generator = Blockly.JSON[nextBlock.type]; + if (typeof generator === 'function') { + const generatedValue = generator(nextBlock, null, maxDepth - currentDepth); + if (generatedValue.type === 'dynamic_value' && Array.isArray(generatedValue.values)) { + variables.push(...generatedValue.values); + } else { + variables.push(generatedValue); + } + } else { + console.warn(`No generator found for block type "${nextBlock.type}". Skipping this block.`); + } } - } else { - // If no NEXT, wrap variable name in a list for consistency - json.variables = [json.variable_name]; } - // Remove 'variable_name' if 'variables' exists to maintain consistency - if (json.variables) { - delete json.variable_name; - } + // Start processing from the initial block + processBlock(block, 1); - return JSON.stringify(json); + // Construct the final JSON object as a dynamic_value + return { + type: 'dynamic_value', + values: variables + }; }; + + /** * Generator for 'set_variable' Block * * Description: - * Generates a JSON object to assign a specific value to a variable. + * Generates a JSON object to assign a specific value or list of values to a variable. * Supports numerical, string, or dynamic values, enabling versatile usage. */ - Blockly.JSON['set_variable'] = function(block) { - // Retrieve the 'VAR_NAME' field input - const varName = block.getFieldValue('VAR_NAME'); + Blockly.JSON['set_variable'] = function(block, argsCount = null, maxDepth = 10) { + // Retrieve and validate the 'VAR_NAME' field input + const varName = block.getFieldValue('VAR_NAME')?.trim() || 'undefined_var'; - // Validate the variable name - if (!varName || varName.trim() === "") { + if (varName === 'undefined_var') { console.warn("Empty VAR_NAME in set_variable block. Defaulting to 'undefined_var'."); } - // Retrieve the 'VALUE' input - const valueCode = Blockly.JSON.valueToCode(block, 'VALUE', Blockly.JSON.ORDER_ATOMIC) || "null"; - let value; + // Retrieve the connected block via 'VALUE' input + const valueBlock = block.getInputTargetBlock('VALUE'); + let values = []; - try { - // Attempt to parse the VALUE input - value = Blockly.JSON.safeParse(valueCode); - if (value === undefined) { - // If parsing returns undefined, treat it as null - value = null; - console.warn(`VALUE input in set_variable block could not be parsed. Defaulting to null.`); + if (valueBlock) { + const generator = Blockly.JSON[valueBlock.type]; + if (typeof generator === 'function') { + // Invoke the generator for the connected block + const generatedValue = generator(valueBlock, null, maxDepth - 1); + + // Handle different types of generated values + if (generatedValue.type === 'dynamic_value' && Array.isArray(generatedValue.values)) { + // Assign the entire list + values = generatedValue.values; + } else if (typeof generatedValue === 'object') { + // If it's an object, handle accordingly (customize as needed) + values.push(generatedValue); + } else { + // For primitive types (number, string, etc.) + values.push(generatedValue); + } + } else { + console.warn(`No generator found for block type "${valueBlock.type}". Skipping this block.`); } - } catch (e) { - console.warn(`Error parsing VALUE input in set_variable block: ${e.message}. Defaulting to null.`); - value = null; + } else { + console.warn("No connected block to 'VALUE' input in set_variable block. Defaulting to empty list."); } + // Construct the JSON object for 'set_variable' const json = { type: 'set_variable', - variable_name: varName && varName.trim() !== "" ? varName : 'undefined_var', - value: value + variable_name: varName, + values: values }; - // Handle 'NEXT' input if connected (optional) - // Since 'set_variable' block does not have a 'NEXT' input, this section is optional - /* - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - json.next = nextJson; - } catch (e) { - console.warn(`Failed to parse NEXT input in set_variable block: ${e.message}`); - } - } - */ - - return JSON.stringify(json); + return json; }; /** @@ -237,22 +270,7 @@ export function defineVAFGenerators() { flag_name: flagName && flagName.trim() !== "" ? flagName : 'undefined_flag', flag_value: validFlagValues.includes(flagValue) ? (flagValue === 'TRUE') : false }; - - // Handle 'NEXT' input if connected (optional) - // Since 'set_flag' block does not have a 'NEXT' input, this section is optional - /* - const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC); - if (nextCode) { - try { - const nextJson = JSON.parse(nextCode); - json.next = nextJson; - } catch (e) { - console.warn(`Failed to parse NEXT input in set_flag block: ${e.message}`); - } - } - */ - - return JSON.stringify(json); + return json; }; /** @@ -286,6 +304,6 @@ export function defineVAFGenerators() { flag_value: validFlagValues.includes(flagValue) ? (flagValue === 'TRUE') : true }; - return JSON.stringify(json); + return json; }; } diff --git a/src/static/blocks/indicator_blocks.js b/src/static/blocks/indicator_blocks.js new file mode 100644 index 0000000..ae4f9d7 --- /dev/null +++ b/src/static/blocks/indicator_blocks.js @@ -0,0 +1,66 @@ +// client/indicator_blocks.js + +// Define Blockly blocks and their JSON generators dynamically based on indicators +export function defineIndicatorBlocks() { + + // Ensure Blockly.JSON is available + if (!Blockly.JSON) { + console.error('Blockly.JSON is not defined. Ensure json_generators.js is loaded before indicator_blocks.js.'); + return; + } + + // Retrieve the indicator outputs configuration + const indicatorOutputs = window.UI.indicators.getIndicatorOutputs(); + const toolboxCategory = document.querySelector('#toolbox_advanced category[name="Indicators"]'); + + if (!toolboxCategory) { + console.error('Indicators category not found in the toolbox.'); + return; + } + + for (let indicatorName in indicatorOutputs) { + const outputs = indicatorOutputs[indicatorName]; + + // Create a unique block type for each indicator + const blockType = 'indicator_' + indicatorName; + + // Define the block for this indicator + Blockly.defineBlocksWithJsonArray([{ + "type": blockType, + "message0": `${indicatorName} Output %1`, + "args0": [ + { + "type": "field_dropdown", + "name": "OUTPUT", + "options": outputs.map(output => [output, output]) + } + ], + "output": "dynamic_value", + "colour": 230, + "tooltip": `Select the ${indicatorName} output`, + "helpUrl": "" + }]); + + // Define the JSON generator for this block + Blockly.JSON[blockType] = function(block) { + const selectedOutput = block.getFieldValue('OUTPUT'); + const json = { + type: 'indicator', + fields: { + NAME: indicatorName, + OUTPUT: selectedOutput + }, + inputs: {}, + statements: {} + }; + return json; + }; + + // Append the newly created block to the Indicators category in the toolbox + const blockElement = document.createElement('block'); + blockElement.setAttribute('type', blockType); + toolboxCategory.appendChild(blockElement); + } + + console.log('Indicator blocks and their JSON generators have been defined and inserted into the toolbox.'); +} diff --git a/src/static/brightertrades_favicon.ico b/src/static/brightertrades_favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..151ab950742bc4d53184224b01a322de4ba667d0 GIT binary patch literal 2522 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

rDV}_zO1a9rNm}D? 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