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 0000000..151ab95 Binary files /dev/null and b/src/static/brightertrades_favicon.ico differ diff --git a/src/static/trading_strategy_icon.webp b/src/static/trading_strategy_icon.webp new file mode 100644 index 0000000..0e829bd Binary files /dev/null and b/src/static/trading_strategy_icon.webp differ