From c7b7a47129a929b5e073a2b742a2507ec5d2a61b Mon Sep 17 00:00:00 2001 From: Rob Date: Wed, 16 Oct 2024 00:21:43 -0300 Subject: [PATCH] I played around with the blocks and i think i have it correct now. --- src/BrighterTrades.py | 9 +- src/Strategies.py | 289 +++++-- src/static/Strategies.js | 126 ++- src/static/custom_blocks.js | 1066 +++++++++++++++---------- src/static/indicator_blocks.js | 26 +- src/static/json_generators.js | 770 +++++++++++------- src/templates/new_strategy_popup.html | 51 +- 7 files changed, 1503 insertions(+), 834 deletions(-) diff --git a/src/BrighterTrades.py b/src/BrighterTrades.py index dd34f2a..4d16056 100644 --- a/src/BrighterTrades.py +++ b/src/BrighterTrades.py @@ -294,7 +294,7 @@ class BrighterTrades: # i_updates = self.indicators.update_indicators() state_changes = self.signals.process_all_signals(self.indicators) trade_updates = self.trades.update(float(cdata['close'])) - stg_updates = self.strategies.update(self.signals) + # stg_updates = self.strategies.update() updates = {} # if i_updates: @@ -303,8 +303,8 @@ class BrighterTrades: updates['s_updates'] = state_changes if trade_updates: updates['trade_updts'] = trade_updates - if stg_updates: - updates['stg_updts'] = stg_updates + # if stg_updates: + # updates['stg_updts'] = stg_updates return updates def received_new_signal(self, data: dict) -> str | dict: @@ -706,7 +706,8 @@ class BrighterTrades: # Processing commands if msg_type == 'delete_signal': - self.delete_signal(msg_data) + pass + # self.delete_signal(msg_data) if msg_type == 'delete_strategy': result = self.delete_strategy(msg_data) diff --git a/src/Strategies.py b/src/Strategies.py index e2ba715..b4d1ddb 100644 --- a/src/Strategies.py +++ b/src/Strategies.py @@ -29,6 +29,12 @@ class Strategies: columns=["id", "creator", "name", "workspace", "code", "stats", "public", "fee", "tbl_key", "strategy_components"]) + # Initialize default settings (you can adjust these as needed) + self.default_timeframe = '5m' + self.default_exchange = 'Binance' + self.default_symbol = 'BTCUSD' + self.data_sources_used = set() + def new_strategy(self, data: dict) -> dict: """ Add a new strategy to the cache and database. @@ -328,19 +334,20 @@ class Strategies: if node_type == 'trade_action': code_lines.extend(self.handle_trade_action(node, indent_level)) - elif node_type in ['set_flag', 'notify_user']: + elif node_type in ['set_flag', 'notify_user', 'set_variable']: # Handle actions that generate code lines if node_type == 'set_flag': code_lines.extend(self.handle_set_flag(node, indent_level)) elif node_type == 'notify_user': code_lines.extend(self.handle_notify_user(node, indent_level)) - elif node_type == 'conditional': - # Handle conditional statements - condition_node = node.get('condition') - actions = node.get('actions', []) - condition_code = self.generate_condition_code(condition_node) + elif node_type == 'set_variable': + code_lines.extend(self.handle_set_variable(node, indent_level)) + elif node_type in ['comparison', 'logical_and', 'logical_or', 'math_operation']: + # These are conditions that might be at the top level + condition_code = self.generate_condition_code(node) code_lines.append(f"{indent}if {condition_code}:") # Generate code for actions inside the condition + actions = node.get('statements', {}).get('DO', []) code_lines.extend(self.generate_code_from_json(actions, indent_level + 1)) else: # Handle other node types as needed @@ -353,12 +360,13 @@ class Strategies: if not node_type: return 'False' # Default to False if node type is missing + # Allows for custom operator definitions represented in the JSON if node_type == 'comparison': - operator = condition_node.get('operator') - left = condition_node.get('left') - right = condition_node.get('right') - left_expr = self.generate_condition_code(left) - right_expr = self.generate_condition_code(right) + operator = condition_node['fields'].get('operator') + left_node = condition_node['inputs'].get('left') + right_node = condition_node['inputs'].get('right') + left_expr = self.generate_condition_code(left_node) + right_expr = self.generate_condition_code(right_node) operator_map = { '>': '>', '<': '<', @@ -368,38 +376,48 @@ class Strategies: '!=': '!=' } return f"({left_expr} {operator_map.get(operator, operator)} {right_expr})" + elif node_type == 'logical_and': conditions = condition_node.get('conditions', []) condition_exprs = [self.generate_condition_code(cond) for cond in conditions] return ' and '.join(condition_exprs) + elif node_type == 'logical_or': conditions = condition_node.get('conditions', []) condition_exprs = [self.generate_condition_code(cond) for cond in conditions] return ' or '.join(condition_exprs) + elif node_type == 'is_false': condition = condition_node.get('condition') condition_expr = self.generate_condition_code(condition) return f"not ({condition_expr})" + elif node_type == 'flag_is_set': flag_name = condition_node.get('flag_name') self.flags_used.add(flag_name) return f"self.flags.get('{flag_name}', False)" + elif node_type == 'strategy_profit_loss': metric = condition_node.get('metric') if metric == 'profit': return 'self.is_in_profit()' elif metric == 'loss': return 'self.is_in_loss()' + elif node_type == 'active_trades': return 'self.get_active_trades()' + elif node_type == 'current_balance': return 'self.get_current_balance()' + elif node_type == 'starting_balance': return 'self.get_starting_balance()' + elif node_type == 'value_input': value = condition_node.get('value', 0) return str(value) - elif node_type == 'arithmetic_operator': + + elif node_type == 'math_operation': operator = condition_node.get('operator') operands = condition_node.get('operands', []) if len(operands) == 2: @@ -412,11 +430,15 @@ class Strategies: 'DIVIDE': '/' } return f"({left_expr} {operator_map.get(operator, operator)} {right_expr})" + else: + return '0' # Default or raise an error + elif node_type == 'last_candle_value': candle_part = condition_node.get('candle_part') - source = condition_node.get('source', {}) - data_feed = self.get_data_feed(source) + source_node = condition_node.get('source', {}) + data_feed = self.get_data_feed(source_node) return f"{data_feed}.{candle_part}[0]" + elif node_type == 'indicator': indicator_name = condition_node.get('name') output_field = condition_node.get('output') @@ -427,13 +449,15 @@ class Strategies: }) # Generate code that calls process_indicator return f"self.process_indicator('{indicator_name}', '{output_field}')" + elif node_type == 'number': # Handle numeric values return str(condition_node.get('value', 0)) + elif node_type == 'string': # Handle string values return f"'{condition_node.get('value', '')}'" - # Handle other node types as needed + else: return 'False' # Default to False for unhandled types @@ -441,12 +465,95 @@ class Strategies: code_lines = [] indent = ' ' * indent_level - action = node.get('trade_type') + # Accessing fields and inputs + trade_type = node.get('trade_type', 'buy') # 'buy' or 'sell' condition_node = node.get('condition') size = node.get('size', 1) - stop_loss = node.get('stop_loss') - take_profit = node.get('take_profit') - trade_options = node.get('trade_options', []) + + # Initialize defaults + default_tif = 'gtc' # Good Till Canceled + default_timeframe = getattr(self, 'default_timeframe', '5m') # e.g., '5m' + default_exchange = getattr(self, 'default_exchange', 'Binance') # e.g., 'Binance' + default_symbol = getattr(self, 'default_symbol', 'BTCUSD') # e.g., 'BTCUSD' + + # Set initial default values + tif = default_tif + timeframe = default_timeframe + exchange = default_exchange + symbol = default_symbol + stop_loss_type = None + stop_loss_value = None + stop_loss_trigger_price = None + stop_loss_limit_price = None + take_profit_type = None + take_profit_value = None + take_profit_trigger_price = None + take_profit_limit_price = None + price_type = 'market' + price_value = None + + # Trade Options + trade_options_nodes = node.get('trade_options', []) + + # Process trade options + for option in trade_options_nodes: + option_type = option.get('type') + + if option_type == 'time_in_force': + # Extract Time in Force + tif = option.get('tif', default_tif) + + elif option_type == 'target_market': + # Extract Target Market details + timeframe = option.get('timeframe', default_timeframe) + exchange = option.get('exchange', default_exchange) + symbol = option.get('symbol', default_symbol) + self.data_sources_used.add((exchange, symbol, timeframe)) + + elif option_type == 'stop_loss': + # Extract Stop Loss parameters + sl_type = option.get('stop_loss_type') + if sl_type == 'market': + sl_value = option.get('stop_loss_value') + if sl_value is not None: + stop_loss_type = sl_type + stop_loss_value = sl_value + elif sl_type == 'limit': + sl_trigger_price = option.get('trigger_price') + sl_limit_price = option.get('limit_price') + if sl_trigger_price is not None and sl_limit_price is not None: + stop_loss_type = sl_type + stop_loss_trigger_price = sl_trigger_price + stop_loss_limit_price = sl_limit_price + + elif option_type == 'take_profit': + # Extract Take Profit parameters + tp_type = option.get('take_profit_type') + if tp_type == 'market': + tp_value = option.get('take_profit_value') + if tp_value is not None: + take_profit_type = tp_type + take_profit_value = tp_value + elif tp_type == 'limit': + tp_trigger_price = option.get('trigger_price') + tp_limit_price = option.get('limit_price') + if tp_trigger_price is not None and tp_limit_price is not None: + take_profit_type = tp_type + take_profit_trigger_price = tp_trigger_price + take_profit_limit_price = tp_limit_price + + elif option_type == 'price': + # Extract Price parameters + p_type = option.get('price_type', 'market') + p_value = option.get('price_value', None) + if p_type and p_value is not None: + price_type = p_type + price_value = p_value + + # Handle other trade options if added in the future + + # Apply defaults if certain options are not provided + # Time in Force and Target Market already have defaults set above # Generate code for the condition if condition_node: @@ -457,36 +564,96 @@ class Strategies: action_indent = indent # Prepare order parameters - order_params = [f"size={size}"] - if stop_loss is not None: - order_params.append(f"stop_loss={stop_loss}") - if take_profit is not None: - order_params.append(f"take_profit={take_profit}") - # Handle trade options - for option in trade_options: - if option.get('type') == 'order_type': - order_type = option.get('order_type', 'market') - order_params.append(f"order_type='{order_type}'") - if order_type == 'limit': - limit_price = option.get('limit_price') - if limit_price is not None: - order_params.append(f"price={limit_price}") - elif option.get('type') == 'time_in_force': - tif = option.get('tif') - if tif: - order_params.append(f"tif='{tif}'") - elif option.get('type') == 'target_market': - tf = option.get('timeframe') - exc = option.get('exchange') - sym = option.get('symbol') - order_params.append(f"timeframe='{tf}'") - order_params.append(f"exchange='{exc}'") - order_params.append(f"symbol='{sym}'") - self.data_sources_used.add((exc, sym, tf)) - # Handle other trade options + # Handle 'size' being a single value or a list + if isinstance(size, list): + # Iterate over each size and execute the trade + for idx, single_size in enumerate(size): + single_order_params = [f"size={single_size}", f"order_type='{trade_type}'"] - params_str = ', '.join(order_params) - code_lines.append(f"{action_indent}self.{action}({params_str})") + # Handle Price + if price_type == 'limit' and price_value is not None: + single_order_params.append(f"limit_price={price_value}") + else: + single_order_params.append(f"limit_price=None") + + # Handle Stop Loss + if stop_loss_type == 'market' and stop_loss_value is not None: + single_order_params.append(f"stop_loss_type='{stop_loss_type}'") + single_order_params.append(f"stop_loss={stop_loss_value}") + elif stop_loss_type == 'limit': + single_order_params.append(f"stop_loss_type='{stop_loss_type}'") + single_order_params.append(f"stop_loss_trigger_price={stop_loss_trigger_price}") + single_order_params.append(f"stop_loss_limit_price={stop_loss_limit_price}") + + # Handle Take Profit + if take_profit_type == 'market' and take_profit_value is not None: + single_order_params.append(f"take_profit_type='{take_profit_type}'") + single_order_params.append(f"take_profit={take_profit_value}") + elif take_profit_type == 'limit': + single_order_params.append(f"take_profit_type='{take_profit_type}'") + single_order_params.append(f"take_profit_trigger_price={take_profit_trigger_price}") + single_order_params.append(f"take_profit_limit_price={take_profit_limit_price}") + + # Always include Time in Force and Target Market parameters + single_order_params.append(f"tif='{tif}'") + single_order_params.append(f"timeframe='{timeframe}'") + single_order_params.append(f"exchange='{exchange}'") + single_order_params.append(f"symbol='{symbol}'") + + # Include Price parameters if applicable + if price_type and price_value is not None: + single_order_params.append(f"price_type='{price_type}'") + single_order_params.append(f"price_value={price_value}") + + # Convert parameters list to a comma-separated string + single_params_str = ', '.join(single_order_params) + + # Generate the trade execution line + code_lines.append(f"{action_indent}self.{trade_type}({single_params_str})") + else: + # Single size value + order_params = [f"size={size}", f"order_type='{trade_type}'"] + + # Handle Price + if price_type == 'limit' and price_value is not None: + order_params.append(f"limit_price={price_value}") + else: + order_params.append(f"limit_price=None") + + # Handle Stop Loss + if stop_loss_type == 'market' and stop_loss_value is not None: + order_params.append(f"stop_loss_type='{stop_loss_type}'") + order_params.append(f"stop_loss={stop_loss_value}") + elif stop_loss_type == 'limit': + order_params.append(f"stop_loss_type='{stop_loss_type}'") + order_params.append(f"stop_loss_trigger_price={stop_loss_trigger_price}") + order_params.append(f"stop_loss_limit_price={stop_loss_limit_price}") + + # Handle Take Profit + if take_profit_type == 'market' and take_profit_value is not None: + order_params.append(f"take_profit_type='{take_profit_type}'") + order_params.append(f"take_profit={take_profit_value}") + elif take_profit_type == 'limit': + order_params.append(f"take_profit_type='{take_profit_type}'") + order_params.append(f"take_profit_trigger_price={take_profit_trigger_price}") + order_params.append(f"take_profit_limit_price={take_profit_limit_price}") + + # Always include Time in Force and Target Market parameters + order_params.append(f"tif='{tif}'") + order_params.append(f"timeframe='{timeframe}'") + order_params.append(f"exchange='{exchange}'") + order_params.append(f"symbol='{symbol}'") + + # Include Price parameters if applicable + if price_type and price_value is not None: + order_params.append(f"price_type='{price_type}'") + order_params.append(f"price_value={price_value}") + + # Convert parameters list to a comma-separated string + params_str = ', '.join(order_params) + + # Generate the trade execution line + code_lines.append(f"{action_indent}self.{trade_type}({params_str})") return code_lines @@ -494,9 +661,13 @@ class Strategies: code_lines = [] indent = ' ' * indent_level + # Retrieve the condition directly from the node condition_node = node.get('condition') + if condition_node is None: + raise ValueError("Condition is missing in 'set_flag' node.") + flag_name = node.get('flag_name') - flag_value = node.get('flag_value', 'True') + flag_value = node.get('flag_value', 'True') # Default to 'True' if not provided condition_code = self.generate_condition_code(condition_node) code_lines.append(f"{indent}if {condition_code}:") @@ -513,10 +684,22 @@ class Strategies: return code_lines - def get_data_feed(self, source): - timeframe = source.get('timeframe', 'default') - exchange = source.get('exchange', 'default') - symbol = source.get('symbol', 'default') + def handle_set_variable(self, node, indent_level): + code_lines = [] + indent = ' ' * indent_level + + variable_name = node.get('variable_name') + value_node = node.get('value') + value_code = self.generate_condition_code(value_node) if value_node else '0' + + code_lines.append(f"{indent}{variable_name} = {value_code}") + + return code_lines + + def get_data_feed(self, source_node): + timeframe = source_node.get('timeframe', 'default') + exchange = source_node.get('exchange', 'default') + symbol = source_node.get('symbol', 'default') source_key = f"{exchange}_{symbol}_{timeframe}" self.data_sources_used.add((exchange, symbol, timeframe)) return f"self.datas['{source_key}']" diff --git a/src/static/Strategies.js b/src/static/Strategies.js index f82ea8e..e7f102b 100644 --- a/src/static/Strategies.js +++ b/src/static/Strategies.js @@ -307,7 +307,12 @@ class StratWorkspaceManager { console.error("toolbox is not loaded."); return; } - + /** + * @override the toolbox zoom + */ + Blockly.VerticalFlyout.prototype.getFlyoutScale = function() { + return 1; + }; this.workspace = Blockly.inject('blocklyDiv', { toolbox: toolboxElement, scrollbars: true, @@ -393,51 +398,84 @@ class StratWorkspaceManager { * @returns {Object} - The JSON representation of the block. */ _blockToJson(block) { - 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(); - } - }); - } - }); - - // Capture all connected blocks - block.inputList.forEach(input => { - if (input.connection && input.connection.targetBlock()) { - const targetBlock = input.connection.targetBlock(); - if (input.type === Blockly.INPUT_VALUE) { - json.inputs[input.name] = this._blockToJson(targetBlock); - } else if (input.type === Blockly.NEXT_STATEMENT) { - // Handle multiple statement connections if applicable - const connectedBlocks = []; - let currentBlock = targetBlock; - while (currentBlock) { - connectedBlocks.push(this._blockToJson(currentBlock)); - currentBlock = currentBlock.getNextBlock(); - } - json.statements[input.name] = connectedBlocks; - } - } - }); - - // Handle the next connected block at the same level - if (block.getNextBlock()) { - const nextBlock = this._blockToJson(block.getNextBlock()); - // Assuming only one 'next' block; adjust if multiple are possible - json.next = nextBlock; + if (!block) { + return null; } - return json; + // 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); + + // 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) { + 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; + } } /** diff --git a/src/static/custom_blocks.js b/src/static/custom_blocks.js index 187a1fb..c60ba8f 100644 --- a/src/static/custom_blocks.js +++ b/src/static/custom_blocks.js @@ -1,456 +1,696 @@ - // custom_blocks.js - // Define custom Blockly blocks and Python code generation - export function defineCustomBlocks() { - // Custom block for retrieving last candle values - Blockly.defineBlocksWithJsonArray([{ - "type": "last_candle_value", - "message0": "Last candle %1 value (Src): %2", - "args0": [ - { - "type": "field_dropdown", - "name": "CANDLE_PART", - "options": [ - ["Open", "open"], - ["High", "high"], - ["Low", "low"], - ["Close", "close"] - ] - }, - { - "type": "input_value", // Accept an optional source block connection - "name": "SOURCE", - "check": "source" // The connected block must be a source block - } - ], - "inputsInline": true, // Place the fields on the same line - "output": "Number", - "colour": 230, - "tooltip": "Get the value of the last candle from the specified or default source.", - "helpUrl": "" - }]); +// custom_blocks.js +// Define custom Blockly blocks and JSON code generation - // Comparison Block (for Candle Close > Candle Open, etc.) - Blockly.defineBlocksWithJsonArray([ - { - "type": "comparison", - "message0": "%1 %2 %3", - "args0": [ - { - "type": "input_value", - "name": "LEFT" - }, - { +export function defineCustomBlocks() { + + // Register a new type for dynamic values if not already defined + Blockly.Types = Blockly.Types || {}; + Blockly.Types.DYNAMIC_VALUE = 'dynamic_value'; + + // Generate dropdown options based on available data + const timeframeOptions = bt_data.intervals.map(interval => [interval, interval]); + const exchangeOptions = window.UI.exchanges.connected_exchanges.map(exchange => [exchange, exchange]); + const symbolOptions = bt_data.symbols.map(symbol => [symbol, symbol]); + + /************************************************ + * VALUE BLOCKS * + ************************************************/ + + /** + * last_candle_value + * Retrieves a specific part of the last candle (Open, High, Low, Close) from a given source. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "last_candle_value", + "message0": "Last candle %1 value (Src): %2", + "args0": [ + { "type": "field_dropdown", - "name": "OPERATOR", + "name": "candle_part", "options": [ - [">", ">"], - ["<", "<"], - ["==", "=="] + ["Open", "open"], + ["High", "high"], + ["Low", "low"], + ["Close", "close"] ] - }, - { + }, + { "type": "input_value", - "name": "RIGHT" - } - ], - "inputsInline": true, - "output": "Boolean", - "colour": 160, - "tooltip": "Compare two values.", - "helpUrl": "" - } - ]); + "name": "source", + "check": "source" + } + ], + "inputsInline": true, + "output": "dynamic_value", + "colour": 230, + "tooltip": "Retrieve the specified part of the last candle from the given source.", + "helpUrl": "" + }]); - Blockly.defineBlocksWithJsonArray([{ - "type": "trade_action", - "message0": "if %1 then %2 units %3 with Stop Loss %4 and Take Profit %5 (Options) %6", - "args0": [ - {"type": "input_value", "name": "CONDITION", "check": "Boolean"}, - {"type": "field_dropdown", "name": "TRADE_TYPE", "options": [["Buy", "buy"], ["Sell", "sell"]]}, - {"type": "input_value", "name": "SIZE", "check": "Number"}, - {"type": "input_value", "name": "STOP_LOSS", "check": "Number"}, - {"type": "input_value", "name": "TAKE_PROFIT", "check": "Number"}, - {"type": "input_statement", "name": "TRADE_OPTIONS", "check": "trade_option"} - ], - "previousStatement": null, - "nextStatement": null, - "colour": 230, - "tooltip": "Executes a trade with size, optional stop loss, take profit, and trade options", - "helpUrl": "" - }]); + /** + * strategy_profit_loss + * Evaluates the total strategy's profit or loss. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "strategy_profit_loss", + "message0": "Total Strategy %1 %2", + "args0": [ + { + "type": "field_dropdown", + "name": "METRIC", + "options": [ + ["profit", "profit"], + ["loss", "loss"] + ] + }, + { + "type": "input_value", + "name": "NEXT", + "check": "dynamic_value" + } + ], + "output": "dynamic_value", + "colour": 230, + "tooltip": "Choose to evaluate the strategy's profit or loss.", + "helpUrl": "" + }]); + /** + * current_balance + * 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" + } + ], + "output": "dynamic_value", + "colour": 230, + "tooltip": "Retrieve the current balance of the strategy.", + "helpUrl": "" + }]); - // Stop Loss Block - Blockly.defineBlocksWithJsonArray([{ - "type": "stop_loss", - "message0": "Stop Loss %1", - "args0": [ - { - "type": "input_value", - "name": "STOP_LOSS", - "check": "Number" - } - ], - "output": "Number", - "colour": 230, - "tooltip": "Sets a stop loss value", - "helpUrl": "" - }]); + /** + * starting_balance + * 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" + } + ], + "output": "dynamic_value", + "colour": 230, + "tooltip": "Retrieve the starting balance of the strategy.", + "helpUrl": "" + }]); - // Take Profit Block - Blockly.defineBlocksWithJsonArray([{ - "type": "take_profit", - "message0": "Take Profit %1", - "args0": [ - { - "type": "input_value", - "name": "TAKE_PROFIT", - "check": "Number" - } - ], - "output": "Number", - "colour": 230, - "tooltip": "Sets a take profit value", - "helpUrl": "" - }]); - // Logical AND Block - Blockly.defineBlocksWithJsonArray([{ - "type": "logical_and", - "message0": "%1 AND %2", - "args0": [ - { - "type": "input_value", - "name": "LEFT", - "check": "Boolean" - }, - { - "type": "input_value", - "name": "RIGHT", - "check": "Boolean" - } - ], - "inputsInline": true, - "output": "Boolean", - "colour": 210, - "tooltip": "Logical AND of two conditions", - "helpUrl": "" - }]); - // Logical OR Block - Blockly.defineBlocksWithJsonArray([{ - "type": "logical_or", - "message0": "%1 OR %2", - "args0": [ - { - "type": "input_value", - "name": "LEFT", - "check": "Boolean" - }, - { - "type": "input_value", - "name": "RIGHT", - "check": "Boolean" - } - ], - "inputsInline": true, - "output": "Boolean", - "colour": 210, - "tooltip": "Logical OR of two conditions", - "helpUrl": "" - }]); - // Block to check if a condition is false - Blockly.defineBlocksWithJsonArray([{ - "type": "is_false", - "message0": "%1 is false", - "args0": [ - { - "type": "input_value", - "name": "CONDITION", - "check": "Boolean" - } - ], - "output": "Boolean", - "colour": 160, - "tooltip": "Checks if the condition is false", - "helpUrl": "" - }]); - // Order Type Block with Limit Price - Blockly.defineBlocksWithJsonArray([{ - "type": "order_type", - "message0": "Order Type %1 %2", - "args0": [ - { - "type": "field_dropdown", - "name": "ORDER_TYPE", - "options": [ - ["Market", "market"], - ["Limit", "limit"] - ] - }, - { - "type": "input_value", - "name": "LIMIT_PRICE", // Input for limit price when Limit order is selected - "check": "Number" - } - ], - "previousStatement": "trade_option", - "nextStatement": "trade_option", - "colour": 230, - "tooltip": "Select order type (Market or Limit) with optional limit price", - "helpUrl": "" - }]); - Blockly.defineBlocksWithJsonArray([{ + /** + * active_trades + * Gets the number of active trades currently open. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "active_trades", + "message0": "Number of active trades %1", + "args0": [ + { + "type": "input_value", + "name": "NEXT", + "check": "dynamic_value" + } + ], + "output": "dynamic_value", + "colour": 230, + "tooltip": "Get the number of active trades currently open.", + "helpUrl": "" + }]); + + /** + * math_operation + * Performs basic arithmetic operations between two values. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "math_operation", + "message0": "%1 %2 %3", + "args0": [ + { + "type": "input_value", + "name": "left", + "check": ["number", "dynamic_value"] + }, + { + "type": "field_dropdown", + "name": "operator", + "options": [ + ["+", "ADD"], + ["-", "SUBTRACT"], + ["*", "MULTIPLY"], + ["/", "DIVIDE"] + ] + }, + { + "type": "input_value", + "name": "right", + "check": ["number", "dynamic_value"] + } + ], + "inputsInline": true, + "output": "dynamic_value", + "colour": 160, + "tooltip": "Perform basic arithmetic operations.", + "helpUrl": "" + }]); + + /** + * value_input + * Allows users to input numerical values and chain multiple values for list creation. + */ + Blockly.defineBlocksWithJsonArray([ + { "type": "value_input", - "message0": "Value %1", + "message0": "Value %1 %2", "args0": [ { "type": "field_number", "name": "VALUE", "value": 0, "min": 0 + }, + { + "type": "input_value", + "name": "NEXT", + "check": "dynamic_value" // Accepts single or list of values } ], - "output": "Number", - "colour": 230, - "tooltip": "Enter a numerical value", + "output": "dynamic_value", // Custom type to handle single or list + "colour": 330, + "tooltip": "Enter a numerical value. Chain multiple for a list.", "helpUrl": "" - }]); - // Time In Force (TIF) Block - Blockly.defineBlocksWithJsonArray([{ - "type": "time_in_force", - "message0": "Time in Force %1", + } + ]); + /** + * source + * Defines the data source with Time Frame (TF), Exchange (Ex), and Symbol (Sym). + * Use with last_candle_value or any block that returns exchange related values. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "source", + "message0": "src: TF %1 Ex %2 Sym %3", + "args0": [ + { + "type": "field_dropdown", + "name": "TF", + "options": timeframeOptions + }, + { + "type": "field_dropdown", + "name": "EXC", + "options": exchangeOptions + }, + { + "type": "field_dropdown", + "name": "SYM", + "options": symbolOptions + } + ], + "output": "source", // Allows connection to blocks expecting a 'source' type + "colour": 230, + "tooltip": "Choose the data feed source for the trade or value.", + "helpUrl": "" + }]); + + /************************************************ + * LOGICAL BLOCKS * + ************************************************/ + /** + * comparison + * Compares two numerical or dynamic values using operators like >, <, ==. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "comparison", + "message0": "%1 %2 %3", + "args0": [ + { + "type": "input_value", + "name": "left", + "check": ["number", "dynamic_value"] + }, + { + "type": "field_dropdown", + "name": "operator", + "options": [ + [">", ">"], + ["<", "<"], + ["==", "=="] + ] + }, + { + "type": "input_value", + "name": "right", + "check": ["number", "dynamic_value"] + } + ], + "inputsInline": true, + "output": "Boolean", + "colour": 160, + "tooltip": "Compare two values using operators like >, <, ==.", + "helpUrl": "" + }]); + /** + * logical_and + * Performs a logical AND operation between two Boolean conditions. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "logical_and", + "message0": "%1 AND %2", + "args0": [ + { + "type": "input_value", + "name": "left", + "check": "Boolean" + }, + { + "type": "input_value", + "name": "right", + "check": "Boolean" + } + ], + "inputsInline": true, + "output": "Boolean", + "colour": 210, + "tooltip": "Logical AND of two conditions.", + "helpUrl": "" + }]); + + /** + * logical_or + * Performs a logical OR operation between two Boolean conditions. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "logical_or", + "message0": "%1 OR %2", + "args0": [ + { + "type": "input_value", + "name": "left", + "check": "Boolean" + }, + { + "type": "input_value", + "name": "right", + "check": "Boolean" + } + ], + "inputsInline": true, + "output": "Boolean", + "colour": 210, + "tooltip": "Logical OR of two conditions.", + "helpUrl": "" + }]); + + /** + * is_false + * Checks if a given Boolean condition is false. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "is_false", + "message0": "%1 is false", + "args0": [ + { + "type": "input_value", + "name": "condition", + "check": "Boolean" + } + ], + "output": "Boolean", + "colour": 160, + "tooltip": "Checks if the condition is false.", + "helpUrl": "" + }]); + + /************************************************ + * TRADE ORDER BLOCKS * + ************************************************/ + + /** + * trade_action + * Executes a trade action (Buy/Sell) based on a Boolean condition, specifying the amount and optional trade options. + */ + Blockly.defineBlocksWithJsonArray([ + { + "type": "trade_action", + "message0": "if %1 then %2 amount %3 %4 (Options) %5", "args0": [ + { + "type": "input_value", + "name": "condition", + "check": "Boolean" + }, { "type": "field_dropdown", - "name": "TIF", + "name": "tradeType", "options": [ - ["GTC (Good Till Canceled)", "gtc"], - ["FOK (Fill or Kill)", "fok"], - ["IOC (Immediate or Cancel)", "ioc"] + ["Buy", "buy"], + ["Sell", "sell"] ] + }, + { + "type": "input_value", + "name": "size", + "check": "dynamic_value" // Accepts single value or list + }, + { + "type": "input_statement", + "name": "trade_options", + "check": "trade_option" + }, + { + "type": "field_dummy" // Placeholder for alignment + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": "Executes a trade with specified size and optional trade options.", + "helpUrl": "" + } + ]); + + /** + * time_in_force + * Sets the time in force for the order (GTC, FOK, IOC). + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "time_in_force", + "message0": "Time in Force %1", + "args0": [ + { + "type": "field_dropdown", + "name": "tif", + "options": [ + ["GTC (Good Till Canceled)", "gtc"], + ["FOK (Fill or Kill)", "fok"], + ["IOC (Immediate or Cancel)", "ioc"] + ] + } + ], + "previousStatement": "trade_option", + "nextStatement": "trade_option", + "colour": 230, + "tooltip": "Select time in force for the order.", + "helpUrl": "" + }]); + + /** + * stop_loss + * Sets the Stop Loss parameter for a trade. + */ + Blockly.defineBlocksWithJsonArray([ + { + "type": "stop_loss", + "message0": "Stop Loss %1", + "args0": [ + { + "type": "input_value", + "name": "stop_loss_input", + "check": ["number", "dynamic_value"] // Accepts numerical value or dynamic value } ], "previousStatement": "trade_option", "nextStatement": "trade_option", "colour": 230, - "tooltip": "Select time in force for the order", + "tooltip": "Set Stop Loss parameters.", "helpUrl": "" - }]); - // Before defining the block, fetch the required options - const timeframeOptions = bt_data.intervals.map(interval => [interval, interval]); - const exchangeOptions = window.UI.exchanges.connected_exchanges.map(exchange => [exchange, exchange]); - const symbolOptions = bt_data.symbols.map(symbol => [symbol, symbol]); + } + ]); - // Dynamically populate the block options using the available data - Blockly.defineBlocksWithJsonArray([{ - "type": "source", - "message0": "src: TF %1 Ex %2 Sym %3", - "args0": [ - { - "type": "field_dropdown", - "name": "TF", - "options": timeframeOptions - }, - { - "type": "field_dropdown", - "name": "EXC", - "options": exchangeOptions - }, - { - "type": "field_dropdown", - "name": "SYM", - "options": symbolOptions - } - ], - "output": "source", // This output allows it to be connected to other blocks expecting a 'source' - "colour": 230, - "tooltip": "Choose the data feed source for the trade or value.", - "helpUrl": "" - }]); - - // Similarly, define 'target_market' block - const targetMarketTFOptions = bt_data.intervals.map(interval => [interval, interval]); - const targetMarketEXCOptions = window.UI.exchanges.connected_exchanges.map(exchange => [exchange, exchange]); - const targetMarketSYMOptions = bt_data.symbols.map(symbol => [symbol, symbol]); - Blockly.defineBlocksWithJsonArray([{ - "type": "target_market", - "message0": "Target market: TF %1 Ex %2 Sym %3", - "args0": [ - { - "type": "field_dropdown", - "name": "TF", - "options": targetMarketTFOptions - }, - { - "type": "field_dropdown", - "name": "EXC", - "options": targetMarketEXCOptions - }, - { - "type": "field_dropdown", - "name": "SYM", - "options": targetMarketSYMOptions - } - ], - "previousStatement": "trade_option", // Allow it to be used as a trade option - "nextStatement": "trade_option", // Chain it with other trade options - "colour": 230, - "tooltip": "Choose the target market for executing trades.", - "helpUrl": "" - }]); - Blockly.defineBlocksWithJsonArray([{ - "type": "strategy_profit_loss", - "message0": "Strategy is %1", - "args0": [ - { - "type": "field_dropdown", - "name": "METRIC", - "options": [ - ["in profit", "profit"], - ["in loss", "loss"] - ] - } - ], - "output": "StrategyMetric", // Custom output type - "colour": 230, - "tooltip": "Choose to evaluate the strategy's profit or loss.", - "helpUrl": "" - }]); - Blockly.defineBlocksWithJsonArray([{ - "type": "current_balance", - "message0": "Current Balance", - "output": "Number", - "colour": 230, - "tooltip": "Retrieve the current balance of the strategy.", - "helpUrl": "" - }]); - Blockly.defineBlocksWithJsonArray([{ - "type": "starting_balance", - "message0": "Starting Balance", - "output": "Number", - "colour": 230, - "tooltip": "Retrieve the starting balance of the strategy.", - "helpUrl": "" - }]); - Blockly.defineBlocksWithJsonArray([{ - "type": "arithmetic_operator", - "message0": "%1 %2 %3", + /** + * take_profit + * Sets the Take Profit parameter for a trade. + */ + Blockly.defineBlocksWithJsonArray([ + { + "type": "take_profit", + "message0": "Take Profit %1", "args0": [ { "type": "input_value", - "name": "LEFT", - "check": "Number" - }, - { - "type": "field_dropdown", - "name": "OPERATOR", - "options": [ - ["+", "ADD"], - ["-", "SUBTRACT"], - ["*", "MULTIPLY"], - ["/", "DIVIDE"] - ] - }, + "name": "take_profit_input", + "check": ["number", "dynamic_value"] // Accepts numerical value or dynamic value + } + ], + "previousStatement": "trade_option", + "nextStatement": "trade_option", + "colour": 230, + "tooltip": "Set Take Profit parameters.", + "helpUrl": "" + } + ]); + + /** + * limit + * Sets the Price parameter for a trade order. + * Accepts numerical value or dynamic value if a single value is provided + * the limit order will buy/sell at market price if the price reaches the + * provided value. If two values are provided a limit order will be set for the + * second value if the price reaches the first value. + */ + Blockly.defineBlocksWithJsonArray([ + { + "type": "limit", + "message0": "Limit %1", + "args0": [ { "type": "input_value", - "name": "RIGHT", - "check": "Number" + "name": "limit_input", + "check": ["number", "dynamic_value"] } ], - "inputsInline": true, - "output": "Number", - "colour": 160, - "tooltip": "Perform basic arithmetic operations.", - "helpUrl": "" - }]); - // Block for tracking the number of trades currently in play - Blockly.defineBlocksWithJsonArray([{ - "type": "active_trades", - "message0": "Number of active trades", - "output": "Number", + "previousStatement": "trade_option", + "nextStatement": "trade_option", "colour": 230, - "tooltip": "Get the number of active trades currently open.", + "tooltip": "Set Limit parameters.", "helpUrl": "" - }]); - // Block for checking if a flag is set - Blockly.defineBlocksWithJsonArray([{ - "type": "flag_is_set", - "message0": "flag %1 is set", - "args0": [ - { - "type": "field_input", - "name": "FLAG_NAME", - "text": "flag_name" - } - ], - "output": "Boolean", - "colour": 160, - "tooltip": "Check if the specified flag is set to True.", - "helpUrl": "" - }]); - // Block for setting a flag based on a condition - Blockly.defineBlocksWithJsonArray([{ - "type": "set_flag", - "message0": "If %1 then set flag %2 to %3", - "args0": [ - { - "type": "input_value", // This will accept a Boolean condition (comparison or logical) - "name": "CONDITION", - "check": "Boolean" - }, - { - "type": "field_input", - "name": "FLAG_NAME", - "text": "flag_name" - }, - { - "type": "field_dropdown", - "name": "FLAG_VALUE", - "options": [["True", "True"], ["False", "False"]] - } - ], - "previousStatement": null, - "nextStatement": null, - "colour": 230, - "tooltip": "Set a flag to True or False if the condition is met.", - "helpUrl": "" - }]); - // Entry Point Block - Blockly.defineBlocksWithJsonArray([{ - "type": "entry_point", - "message0": "Entry Point", - "output": null, - "colour": 120, - "tooltip": "Marks the entry point of the strategy.", - "helpUrl": "" - }]); + } + ]); + /** + * target_market + * Defines the target market for executing trades with specified Time Frame, Exchange, and Symbol. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "target_market", + "message0": "Target market: TF %1 Ex %2 Sym %3", + "args0": [ + { + "type": "field_dropdown", + "name": "TF", + "options": timeframeOptions + }, + { + "type": "field_dropdown", + "name": "EXC", + "options": exchangeOptions + }, + { + "type": "field_dropdown", + "name": "SYM", + "options": symbolOptions + } + ], + "previousStatement": "trade_option", // Allows chaining with other trade options + "nextStatement": "trade_option", + "colour": 230, + "tooltip": "Choose the target market for executing trades.", + "helpUrl": "" + }]); - // Exit Point Block - Blockly.defineBlocksWithJsonArray([{ - "type": "exit_point", - "message0": "Exit Point", - "output": null, - "colour": 120, - "tooltip": "Marks the exit point of the strategy.", - "helpUrl": "" - }]); + /************************************************ + * CONTROL BLOCKS * + ************************************************/ - // Notify User Block - Blockly.defineBlocksWithJsonArray([{ - "type": "notify_user", - "message0": "Notify User with Message %1", - "args0": [ - { - "type": "field_input", - "name": "MESSAGE", - "text": "Your message here" - } - ], - "previousStatement": null, - "nextStatement": null, - "colour": 120, - "tooltip": "Sends a notification message to the user.", - "helpUrl": "" - }]); + /** + * Overrides all and pauses the strategy. + * No new orders will be placed while true. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "strategy_pause", + "message0": "If %1 halt strategy", + "args0": [ + { + "type": "input_value", + "name": "condition", + "check": "Boolean" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": "Pause the strategy if the condition is true.", + "helpUrl": "" + }]); - console.log('Custom blocks defined'); - } + /** + * Overrides all and pauses the strategy and exits trades. + * No new orders will be placed. The strategies open trades + * will be closed depending on the configuration. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "strategy_exit", + "message0": "If %1 exit strategy close %2 trades", + "args0": [ + { + "type": "input_value", + "name": "condition", + "check": "Boolean" + }, + { + "type": "field_dropdown", + "name": "option_value", + "options": [["all", "all"], ["in_profit", "in_profit"], ["in_loss", "in_loss"]] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": "Exit the strategy and close trades based on the condition.", + "helpUrl": "" + }]); + + /*************************************************** + * INFO (FLAGS, VARIABLE, AND NOTIFICATION BLOCKS) * + ***************************************************/ + /** + * notify_user + * Sends a notification message to the user. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "notify_user", + "message0": "Notify User with Message %1", + "args0": [ + { + "type": "field_input", + "name": "MESSAGE", + "text": "Your message here" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 120, + "tooltip": "Sends a notification message to the user.", + "helpUrl": "" + }]); + + /** + * get_variable + * Retrieves the value of a specified variable. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "get_variable", + "message0": "Get variable %1 %2", + "args0": [ + { + "type": "field_input", + "name": "variable_name", + "text": "my_var" + }, + { + "type": "input_value", + "name": "NEXT", + "check": "dynamic_value" + } + ], + "output": "dynamic_value", + "colour": 330, + "tooltip": "Get the value of a variable.", + "helpUrl": "" + }]); + + /** + * set_variable + * Sets a specified variable to a given value. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "set_variable", + "message0": "Set variable %1 to %2", + "args0": [ + { + "type": "field_input", + "name": "variable_name", + "text": "my_var" + }, + { + "type": "input_value", + "name": "value", + "check": ["number", "dynamic_value"] // Accepts numerical value or dynamic value + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 330, + "tooltip": "Set a variable to a value.", + "helpUrl": "" + }]); + /** + * flag_is_set + * Checks if a specified flag is set to True. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "flag_is_set", + "message0": "flag %1 is set", + "args0": [ + { + "type": "field_input", + "name": "flag_name", + "text": "flag_name" + } + ], + "output": "Boolean", + "colour": 160, + "tooltip": "Check if the specified flag is set to True.", + "helpUrl": "" + }]); + + /** + * set_flag + * Sets a specified flag to True or False based on a Boolean condition. + */ + Blockly.defineBlocksWithJsonArray([{ + "type": "set_flag", + "message0": "If %1 then set flag %2 to %3", + "args0": [ + { + "type": "input_value", // Boolean condition + "name": "condition", + "check": "Boolean" + }, + { + "type": "field_input", + "name": "flag_name", + "text": "flag_name" + }, + { + "type": "field_dropdown", + "name": "flag_value", + "options": [["True", "True"], ["False", "False"]] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": "Set a flag to True or False if the condition is met.", + "helpUrl": "" + }]); + + // Final log to confirm block definitions + console.log('Custom blocks defined with dynamic_value support and proper categorization'); +} diff --git a/src/static/indicator_blocks.js b/src/static/indicator_blocks.js index 3fc7672..79a3b46 100644 --- a/src/static/indicator_blocks.js +++ b/src/static/indicator_blocks.js @@ -2,6 +2,13 @@ // 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 category[name="Indicators"]'); @@ -14,9 +21,12 @@ export function defineIndicatorBlocks() { 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": indicatorName, + "type": blockType, "message0": `${indicatorName} Output %1`, "args0": [ { @@ -25,26 +35,30 @@ export function defineIndicatorBlocks() { "options": outputs.map(output => [output, output]) } ], - "output": "Number", + "output": "dynamic_value", "colour": 230, "tooltip": `Select the ${indicatorName} output`, "helpUrl": "" }]); // Define the JSON generator for this block - Blockly.JSON[indicatorName] = function(block) { + Blockly.JSON[blockType] = function(block) { const selectedOutput = block.getFieldValue('OUTPUT'); const json = { type: 'indicator', - name: indicatorName, - output: selectedOutput + fields: { + NAME: indicatorName, + OUTPUT: selectedOutput + }, + inputs: {}, + statements: {} }; return JSON.stringify(json); }; // Append the newly created block to the Indicators category in the toolbox const blockElement = document.createElement('block'); - blockElement.setAttribute('type', indicatorName); + blockElement.setAttribute('type', blockType); toolboxCategory.appendChild(blockElement); } diff --git a/src/static/json_generators.js b/src/static/json_generators.js index cb4f57c..d8761fe 100644 --- a/src/static/json_generators.js +++ b/src/static/json_generators.js @@ -6,9 +6,71 @@ export function defineJsonGenerators() { Blockly.JSON = new Blockly.Generator('JSON'); } + // Define order constants needed for the JSON generator + Blockly.JSON.ORDER_ATOMIC = 0; // Highest precedence + Blockly.JSON.ORDER_NONE = 99; // Lowest precedence + + /** + * Override the default blockToCode method to generate JSON. + */ + Blockly.JSON.blockToCode = function(block) { + if (!block) { + return ''; + } + var func = this[block.type]; + if (typeof func !== 'function') { + throw Error('JSON generator does not know how to generate code for block type "' + block.type + '".'); + } + var code = func.call(this, block); + return code; + }; + + /** + * Generate JSON for a value input. + * @param {Blockly.Block} block Block to generate code from. + * @param {string} name Name of the input. + * @param {number} outerOrder The current operation order. + * @returns {string} JSON string of the input value. + */ + Blockly.JSON.valueToCode = function(block, name, outerOrder) { + if (isNaN(outerOrder)) { + throw Error('Expecting valid order from block: ' + block); + } + var targetBlock = block.getInputTargetBlock(name); + if (!targetBlock) { + return ''; + } + var code = this.blockToCode(targetBlock); + if (code === '') { + return ''; + } + return code; + }; + + /** + * Generate JSON for a statement input. + * @param {Blockly.Block} block Block to generate code from. + * @param {string} name Name of the input. + * @returns {string} JSON string of the statement. + */ + Blockly.JSON.statementToCode = function(block, name) { + var targetBlock = block.getInputTargetBlock(name); + var code = ''; + while (targetBlock) { + var line = this.blockToCode(targetBlock); + if (line) { + code += line; + } + targetBlock = targetBlock.getNextBlock(); + } + return code; + }; + /** * Helper function to safely parse JSON strings. * Returns an empty object if parsing fails. + * @param {string} jsonString The JSON string to parse. + * @returns {Object} Parsed JSON object or empty object on failure. */ function safeParse(jsonString) { try { @@ -19,175 +81,18 @@ export function defineJsonGenerators() { } } - /** - * Trade Action Block JSON Generator - * Captures trade actions including conditions, trade types, sizes, stop loss, take profit, and trade options. - */ - Blockly.JSON['trade_action'] = function(block) { - const condition = Blockly.JSON.valueToCode(block, 'CONDITION', Blockly.JSON.ORDER_ATOMIC) || "False"; - const tradeType = block.getFieldValue('TRADE_TYPE'); // e.g., 'buy' or 'sell' - const size = Blockly.JSON.valueToCode(block, 'SIZE', Blockly.JSON.ORDER_ATOMIC) || 1; - const stopLoss = Blockly.JSON.valueToCode(block, 'STOP_LOSS', Blockly.JSON.ORDER_ATOMIC) || null; - const takeProfit = Blockly.JSON.valueToCode(block, 'TAKE_PROFIT', Blockly.JSON.ORDER_ATOMIC) || null; - const tradeOptionsCode = Blockly.JSON.statementToCode(block, 'TRADE_OPTIONS').trim(); - let tradeOptions = []; - - if (tradeOptionsCode) { - tradeOptions = safeParse(tradeOptionsCode); - // Ensure tradeOptions is an array - if (!Array.isArray(tradeOptions)) { - tradeOptions = [tradeOptions]; - } - } - - const json = { - type: 'trade_action', - condition: condition, - trade_type: tradeType, - size: parseFloat(size), - stop_loss: stopLoss !== null ? parseFloat(stopLoss) : null, - take_profit: takeProfit !== null ? parseFloat(takeProfit) : null, - trade_options: tradeOptions - }; - - return JSON.stringify(json); - }; + /************************************************ + * VALUE BLOCKS * + ************************************************/ /** - * Trade Option Block JSON Generator - * Captures trade options like order type, limit price, and time in force. - */ - Blockly.JSON['trade_option'] = function(block) { - const orderType = block.getFieldValue('ORDER_TYPE'); // e.g., 'market', 'limit' - const limitPrice = Blockly.JSON.valueToCode(block, 'LIMIT_PRICE', Blockly.JSON.ORDER_ATOMIC) || null; - const timeInForce = block.getFieldValue('TIF'); // e.g., 'gtc', 'ioc' - - const json = { - order_type: orderType, - limit_price: limitPrice !== null ? parseFloat(limitPrice) : null, - tif: timeInForce - }; - - return JSON.stringify(json); - }; - - /** - * comparison JSON Generator - * Compares two numerical values, where one can be a strategy_profit_loss block. - */ - Blockly.JSON['comparison'] = function(block) { - const operator = block.getFieldValue('OPERATOR'); - - // Generate JSON for left operand - const leftBlock = block.getInputTargetBlock('LEFT'); - let leftValue; - if (leftBlock && leftBlock.type === 'strategy_profit_loss') { - leftValue = JSON.parse(Blockly.JSON['strategy_profit_loss'](leftBlock)); - } else { - leftValue = Blockly.JSON.valueToCode(block, 'LEFT', Blockly.JSON.ORDER_ATOMIC) || 0; - try { - leftValue = JSON.parse(leftValue); - } catch (e) { - leftValue = parseFloat(leftValue); - } - } - - // Generate JSON for right operand - const rightBlock = block.getInputTargetBlock('RIGHT'); - let rightValue; - if (rightBlock && rightBlock.type === 'strategy_profit_loss') { - rightValue = JSON.parse(Blockly.JSON['strategy_profit_loss'](rightBlock)); - } else { - rightValue = Blockly.JSON.valueToCode(block, 'RIGHT', Blockly.JSON.ORDER_ATOMIC) || 0; - try { - rightValue = JSON.parse(rightValue); - } catch (e) { - rightValue = parseFloat(rightValue); - } - } - - const json = { - type: 'comparison', - operator: operator, - left: leftValue, - right: rightValue - }; - - return JSON.stringify(json); - }; - - /** - * Logical AND Block JSON Generator - * Captures logical AND operations between two conditions. - */ - Blockly.JSON['logical_and'] = function(block) { - const condition1 = Blockly.JSON.valueToCode(block, 'CONDITION1', Blockly.JSON.ORDER_ATOMIC) || "False"; - const condition2 = Blockly.JSON.valueToCode(block, 'CONDITION2', Blockly.JSON.ORDER_ATOMIC) || "False"; - - const json = { - type: 'logical_and', - conditions: [condition1, condition2] - }; - - return JSON.stringify(json); - }; - - /** - * Logical OR Block JSON Generator - * Captures logical OR operations between two conditions. - */ - Blockly.JSON['logical_or'] = function(block) { - const condition1 = Blockly.JSON.valueToCode(block, 'CONDITION1', Blockly.JSON.ORDER_ATOMIC) || "False"; - const condition2 = Blockly.JSON.valueToCode(block, 'CONDITION2', Blockly.JSON.ORDER_ATOMIC) || "False"; - - const json = { - type: 'logical_or', - conditions: [condition1, condition2] - }; - - return JSON.stringify(json); - }; - - /** - * Is False Block JSON Generator - * Captures a condition that checks if another condition is false. - */ - Blockly.JSON['is_false'] = function(block) { - const condition = Blockly.JSON.valueToCode(block, 'CONDITION', Blockly.JSON.ORDER_ATOMIC) || "False"; - - const json = { - type: 'is_false', - condition: condition - }; - - return JSON.stringify(json); - }; - - /** - * Arithmetic Operator Block JSON Generator - * Captures arithmetic operations between two numerical values. - */ - Blockly.JSON['arithmetic_operator'] = function(block) { - const operand1 = Blockly.JSON.valueToCode(block, 'OPERAND1', Blockly.JSON.ORDER_ATOMIC) || "0"; - const operand2 = Blockly.JSON.valueToCode(block, 'OPERAND2', Blockly.JSON.ORDER_ATOMIC) || "0"; - const operator = block.getFieldValue('OPERATOR'); // e.g., '+', '-', '*', '/' - - const json = { - type: 'arithmetic_operator', - operator: operator, - operands: [operand1, operand2] - }; - - return JSON.stringify(json); - }; - - /** - * Last Candle Value Block JSON Generator - * Captures the value part of the last candle from a specified data source. + * last_candle_value + * Retrieves a specific part of the last candle (Open, High, Low, Close) from a given source. + * Generates a JSON object with type, candle_part, and source. */ Blockly.JSON['last_candle_value'] = function(block) { - const candlePart = block.getFieldValue('CANDLE_PART'); // e.g., 'open', 'high', 'low', 'close', 'volume' - const sourceCode = Blockly.JSON.valueToCode(block, 'SOURCE', Blockly.JSON.ORDER_ATOMIC) || "{}"; + const candlePart = block.getFieldValue('candle_part'); // e.g., 'open', 'high', 'low', 'close' + const sourceCode = Blockly.JSON.valueToCode(block, 'source', Blockly.JSON.ORDER_ATOMIC) || "{}"; const source = safeParse(sourceCode); const json = { @@ -200,8 +105,149 @@ export function defineJsonGenerators() { }; /** - * Source Block JSON Generator - * Captures the data source details like timeframe, exchange, and symbol. + * strategy_profit_loss + * Evaluates the total strategy's profit or loss. + * Generates a JSON object with type and metric. + * If 'NEXT' is connected, includes additional metrics. + */ + Blockly.JSON['strategy_profit_loss'] = function(block) { + const metric = block.getFieldValue('METRIC'); // 'profit' or 'loss' + const json = { + type: 'strategy_profit_loss', + metric: metric + }; + + const nextBlock = block.getInputTargetBlock('NEXT'); + if (nextBlock) { + const nextCode = this.blockToCode(nextBlock); + const nextJson = safeParse(nextCode); + if (Array.isArray(json.metrics)) { + json.metrics.push(nextJson); + } else { + json.metrics = [json.metric, nextJson.metric]; + } + } + + return JSON.stringify(json); + }; + + /** + * current_balance + * Retrieves the current balance of the strategy. + * Generates a JSON object with type. + * If 'NEXT' is connected, includes additional balance information. + */ + Blockly.JSON['current_balance'] = function(block) { + const json = { + type: 'current_balance' + }; + + const nextBlock = block.getInputTargetBlock('NEXT'); + if (nextBlock) { + const nextCode = this.blockToCode(nextBlock); + const nextJson = safeParse(nextCode); + json.next = nextJson; + } + + return JSON.stringify(json); + }; + + /** + * starting_balance + * Retrieves the starting balance of the strategy. + * Generates a JSON object with type. + * If 'NEXT' is connected, includes additional starting balance information. + */ + Blockly.JSON['starting_balance'] = function(block) { + const json = { + type: 'starting_balance' + }; + + const nextBlock = block.getInputTargetBlock('NEXT'); + if (nextBlock) { + const nextCode = this.blockToCode(nextBlock); + const nextJson = safeParse(nextCode); + json.next = nextJson; + } + + return JSON.stringify(json); + }; + + /** + * active_trades + * Gets the number of active trades currently open. + * Generates a JSON object with type. + * If 'NEXT' is connected, includes additional active trades information. + */ + Blockly.JSON['active_trades'] = function(block) { + const json = { + type: 'active_trades' + }; + + const nextBlock = block.getInputTargetBlock('NEXT'); + if (nextBlock) { + const nextCode = this.blockToCode(nextBlock); + const nextJson = safeParse(nextCode); + json.next = nextJson; + } + + return JSON.stringify(json); + }; + + /** + * math_operation + * Performs basic arithmetic operations between two values. + * Generates a JSON object with type, operator, left_operand, and right_operand. + */ + Blockly.JSON['math_operation'] = function(block) { + const operator = block.getFieldValue('operator'); // '+', '-', '*', '/' + const leftCode = Blockly.JSON.valueToCode(block, 'left', Blockly.JSON.ORDER_ATOMIC) || "0"; + const rightCode = Blockly.JSON.valueToCode(block, 'right', Blockly.JSON.ORDER_ATOMIC) || "0"; + + const leftValue = safeParse(leftCode) || leftCode; + const rightValue = safeParse(rightCode) || rightCode; + + const json = { + type: 'math_operation', + operator: operator, + left_operand: leftValue, + right_operand: rightValue + }; + + return JSON.stringify(json); + }; + + /** + * value_input + * Allows users to input numerical values and chain multiple values for list creation. + * Generates a JSON object with type and value. + * If 'NEXT' is connected, includes additional values. + */ + Blockly.JSON['value_input'] = function(block) { + const value = parseFloat(block.getFieldValue('VALUE')) || 0; + const json = { + type: 'value_input', + value: value + }; + + const nextBlock = block.getInputTargetBlock('NEXT'); + if (nextBlock) { + const nextCode = this.blockToCode(nextBlock); + const nextJson = safeParse(nextCode); + if (Array.isArray(json.next_values)) { + json.next_values.push(nextJson); + } else { + json.next_values = [json.value, nextJson.value]; + } + } + + return JSON.stringify(json); + }; + + /** + * source + * Defines the data source with Time Frame (TF), Exchange (Ex), and Symbol (Sym). + * Generates a JSON object with timeframe, exchange, and symbol. */ Blockly.JSON['source'] = function(block) { const timeframe = block.getFieldValue('TF'); // e.g., '5m', '1h' @@ -217,126 +263,141 @@ export function defineJsonGenerators() { return JSON.stringify(json); }; + /************************************************ + * LOGICAL BLOCKS * + ************************************************/ + /** - * Target Market Block JSON Generator - * Captures target market parameters for trading. + * logical_and + * Performs a logical AND operation between two Boolean conditions. + * Generates a JSON object with type and conditions. */ - Blockly.JSON['target_market'] = function(block) { - const timeframe = block.getFieldValue('TIMEFRAME'); // e.g., '5m', '1h' - const exchange = block.getFieldValue('EXCHANGE'); // e.g., 'Binance' - const symbol = block.getFieldValue('SYMBOL'); // e.g., 'BTCUSD' + Blockly.JSON['logical_and'] = function(block) { + const condition1Code = Blockly.JSON.valueToCode(block, 'left', Blockly.JSON.ORDER_ATOMIC) || "false"; + const condition2Code = Blockly.JSON.valueToCode(block, 'right', Blockly.JSON.ORDER_ATOMIC) || "false"; + + const condition1 = safeParse(condition1Code) || condition1Code; + const condition2 = safeParse(condition2Code) || condition2Code; const json = { - timeframe: timeframe, - exchange: exchange, - symbol: symbol + type: 'logical_and', + conditions: [condition1, condition2] }; return JSON.stringify(json); }; /** - * strategy_profit_loss JSON Generator - * Outputs the metric (profit or loss) to evaluate. + * logical_or + * Performs a logical OR operation between two Boolean conditions. + * Generates a JSON object with type and conditions. */ - Blockly.JSON['strategy_profit_loss'] = function(block) { - const metric = block.getFieldValue('METRIC'); // 'profit' or 'loss' + Blockly.JSON['logical_or'] = function(block) { + const condition1Code = Blockly.JSON.valueToCode(block, 'left', Blockly.JSON.ORDER_ATOMIC) || "false"; + const condition2Code = Blockly.JSON.valueToCode(block, 'right', Blockly.JSON.ORDER_ATOMIC) || "false"; + + const condition1 = safeParse(condition1Code) || condition1Code; + const condition2 = safeParse(condition2Code) || condition2Code; const json = { - type: 'strategy_profit_loss', - metric: metric + type: 'logical_or', + conditions: [condition1, condition2] }; return JSON.stringify(json); }; /** - * Current Balance Block JSON Generator - * Captures the current balance of the account. + * is_false + * Checks if a given Boolean condition is false. + * Generates a JSON object with type and condition. */ - Blockly.JSON['current_balance'] = function(block) { + Blockly.JSON['is_false'] = function(block) { + const conditionCode = Blockly.JSON.valueToCode(block, 'condition', Blockly.JSON.ORDER_ATOMIC) || "false"; + const condition = safeParse(conditionCode) || conditionCode; + const json = { - type: 'current_balance' + type: 'is_false', + condition: condition }; return JSON.stringify(json); }; /** - * Starting Balance Block JSON Generator - * Captures the starting balance of the account. + * comparison + * Compares two numerical or dynamic values using operators like >, <, ==. + * Generates a JSON object with type, operator, left_operand, and right_operand. */ - Blockly.JSON['starting_balance'] = function(block) { + Blockly.JSON['comparison'] = function(block) { + const operator = block.getFieldValue('operator'); // '>', '<', '==' + const leftCode = Blockly.JSON.valueToCode(block, 'left', Blockly.JSON.ORDER_ATOMIC) || "0"; + const rightCode = Blockly.JSON.valueToCode(block, 'right', Blockly.JSON.ORDER_ATOMIC) || "0"; + + const leftValue = safeParse(leftCode) || leftCode; + const rightValue = safeParse(rightCode) || rightCode; + const json = { - type: 'starting_balance' + type: 'comparison', + operator: operator, + left_operand: leftValue, + right_operand: rightValue + }; + + return JSON.stringify(json); + }; + + /************************************************ + * TRADE ORDER BLOCKS * + ************************************************/ + + /** + * trade_action + * Executes a trade action (Buy/Sell) based on a Boolean condition, specifying the amount and optional trade options. + * Generates a JSON object with type, condition, trade_type, size, and trade_options. + */ + Blockly.JSON['trade_action'] = function(block) { + const conditionCode = Blockly.JSON.valueToCode(block, 'condition', Blockly.JSON.ORDER_ATOMIC) || "false"; + const condition = safeParse(conditionCode) || conditionCode; + + const tradeType = block.getFieldValue('tradeType'); // 'buy' or 'sell' + + const sizeCode = Blockly.JSON.valueToCode(block, 'size', Blockly.JSON.ORDER_ATOMIC) || "0"; + const size = safeParse(sizeCode) || parseFloat(sizeCode); + + const tradeOptionsCode = Blockly.JSON.statementToCode(block, 'trade_options').trim(); + let tradeOptions = []; + + if (tradeOptionsCode) { + try { + tradeOptions = JSON.parse(tradeOptionsCode); + if (!Array.isArray(tradeOptions)) { + tradeOptions = [tradeOptions]; + } + } catch (e) { + console.error("Trade Options Parsing Error:", e); + tradeOptions = []; + } + } + + const json = { + type: 'trade_action', + condition: condition, + trade_type: tradeType, + size: size, + trade_options: tradeOptions }; return JSON.stringify(json); }; /** - * Active Trades Block JSON Generator - * Captures the number of active trades. - */ - Blockly.JSON['active_trades'] = function(block) { - const json = { - type: 'active_trades' - }; - - return JSON.stringify(json); - }; - - /** - * Flag Is Set Block JSON Generator - * Captures whether a specific flag is set. - */ - Blockly.JSON['flag_is_set'] = function(block) { - const flagName = block.getFieldValue('FLAG_NAME'); // e.g., 'flag1' - - const json = { - type: 'flag_is_set', - flag_name: flagName - }; - - return JSON.stringify(json); - }; - - /** - * Set Flag Block JSON Generator - * Captures the action to set a specific flag. - */ - Blockly.JSON['set_flag'] = function(block) { - const flagName = block.getFieldValue('FLAG_NAME'); // e.g., 'flag1' - - const json = { - type: 'set_flag', - flag_name: flagName - }; - - return JSON.stringify(json); - }; - - /** - * Value Input Block JSON Generator - * Captures a numerical input value. - */ - Blockly.JSON['value_input'] = function(block) { - const value = Blockly.JSON.valueToCode(block, 'VALUE', Blockly.JSON.ORDER_ATOMIC) || "0"; - - const json = { - type: 'value_input', - value: parseFloat(value) - }; - - return JSON.stringify(json); - }; - - /** - * Time in Force Block JSON Generator - * Captures the time in force for an order. + * time_in_force + * Sets the time in force for the order (GTC, FOK, IOC). + * Generates a JSON object with type and tif. */ Blockly.JSON['time_in_force'] = function(block) { - const tif = block.getFieldValue('TIF'); // e.g., 'gtc', 'ioc' + const tif = block.getFieldValue('tif'); // 'gtc', 'fok', 'ioc' const json = { type: 'time_in_force', @@ -347,94 +408,129 @@ export function defineJsonGenerators() { }; /** - * Conditional Execution Block JSON Generator - * Captures conditional statements within the strategy. + * stop_loss + * Sets the Stop Loss parameter for a trade. + * Generates a JSON object with type and stop_loss. */ - Blockly.JSON['conditional_execution'] = function(block) { - const conditionCode = Blockly.JSON.valueToCode(block, 'CONDITION', Blockly.JSON.ORDER_ATOMIC) || "False"; - const actionsCode = Blockly.JSON.statementToCode(block, 'ACTIONS').trim(); - let actions = []; - - if (actionsCode) { - actions = safeParse(actionsCode); - // Ensure actions is an array - if (!Array.isArray(actions)) { - actions = [actions]; - } - } + Blockly.JSON['stop_loss'] = function(block) { + const stopLossCode = Blockly.JSON.valueToCode(block, 'stop_loss_input', Blockly.JSON.ORDER_ATOMIC) || "0"; + const stop_loss = safeParse(stopLossCode) || parseFloat(stopLossCode); const json = { - type: 'conditional_execution', - condition: conditionCode, - actions: actions + type: 'stop_loss', + stop_loss: stop_loss }; return JSON.stringify(json); }; /** - * Market Order Block JSON Generator - * Captures market order details. + * take_profit + * Sets the Take Profit parameter for a trade. + * Generates a JSON object with type and take_profit. */ - Blockly.JSON['market_order'] = function(block) { - const size = Blockly.JSON.valueToCode(block, 'SIZE', Blockly.JSON.ORDER_ATOMIC) || 0; + Blockly.JSON['take_profit'] = function(block) { + const takeProfitCode = Blockly.JSON.valueToCode(block, 'take_profit_input', Blockly.JSON.ORDER_ATOMIC) || "0"; + const take_profit = safeParse(takeProfitCode) || parseFloat(takeProfitCode); + const json = { - type: 'market_order', - size: parseFloat(size) + type: 'take_profit', + take_profit: take_profit }; return JSON.stringify(json); }; /** - * Limit Order Block JSON Generator - * Captures limit order details. + * limit + * Sets the Limit parameter for a trade order. + * Generates a JSON object with type and limit_price. */ - Blockly.JSON['limit_order'] = function(block) { - const size = Blockly.JSON.valueToCode(block, 'SIZE', Blockly.JSON.ORDER_ATOMIC) || 0; - const price = Blockly.JSON.valueToCode(block, 'PRICE', Blockly.JSON.ORDER_ATOMIC) || 0; - const tif = block.getFieldValue('TIF'); // e.g., 'gtc', 'ioc' + Blockly.JSON['limit'] = function(block) { + const limitCode = Blockly.JSON.valueToCode(block, 'limit_input', Blockly.JSON.ORDER_ATOMIC) || "0"; + const limit_price = safeParse(limitCode) || parseFloat(limitCode); const json = { - type: 'limit_order', - size: parseFloat(size), - price: parseFloat(price), - tif: tif + type: 'limit', + limit_price: limit_price }; return JSON.stringify(json); }; /** - * Entry Point Block JSON Generator - * Captures the entry point of the strategy. + * target_market + * Defines the target market for executing trades with specified Time Frame, Exchange, and Symbol. + * Generates a JSON object with type, timeframe, exchange, and symbol. */ - Blockly.JSON['entry_point'] = function(block) { + Blockly.JSON['target_market'] = function(block) { + const timeframe = block.getFieldValue('TF'); // e.g., '5m', '1h' + const exchange = block.getFieldValue('EXC'); // e.g., 'Binance' + const symbol = block.getFieldValue('SYM'); // e.g., 'BTCUSD' + const json = { - type: 'entry_point' + type: 'target_market', + timeframe: timeframe, + exchange: exchange, + symbol: symbol + }; + + return JSON.stringify(json); + }; + + /************************************************ + * CONTROL BLOCKS * + ************************************************/ + + /** + * strategy_pause + * Overrides all and pauses the strategy. No new orders will be placed while the condition is true. + * Generates a JSON object with type and pause_condition. + */ + Blockly.JSON['strategy_pause'] = function(block) { + const conditionCode = Blockly.JSON.valueToCode(block, 'condition', Blockly.JSON.ORDER_ATOMIC) || "false"; + const pause_condition = safeParse(conditionCode) || conditionCode; + + const json = { + type: 'strategy_pause', + pause_condition: pause_condition }; return JSON.stringify(json); }; /** - * Exit Point Block JSON Generator - * Captures the exit point of the strategy. + * strategy_exit + * Overrides all, pauses the strategy, and exits trades based on configuration. + * Generates a JSON object with type, exit_condition, and exit_option. */ - Blockly.JSON['exit_point'] = function(block) { + Blockly.JSON['strategy_exit'] = function(block) { + const conditionCode = Blockly.JSON.valueToCode(block, 'condition', Blockly.JSON.ORDER_ATOMIC) || "false"; + const exit_condition = safeParse(conditionCode) || conditionCode; + + const exit_option = block.getFieldValue('option_value'); // 'all', 'in_profit', 'in_loss' + const json = { - type: 'exit_point' + type: 'strategy_exit', + exit_condition: exit_condition, + exit_option: exit_option }; return JSON.stringify(json); }; + /************************************************ + * INFO BLOCKS * + ************************************************/ + /** - * Notify User Block JSON Generator - * Captures a message to notify the user. + * notify_user + * Sends a notification message to the user. + * Generates a JSON object with type and message. */ Blockly.JSON['notify_user'] = function(block) { - const message = Blockly.JSON.valueToCode(block, 'MESSAGE', Blockly.JSON.ORDER_ATOMIC) || "No message provided."; + const messageCode = Blockly.JSON.valueToCode(block, 'MESSAGE', Blockly.JSON.ORDER_ATOMIC) || '"Your message here"'; + const message = safeParse(messageCode) || messageCode; const json = { type: 'notify_user', @@ -444,5 +540,89 @@ export function defineJsonGenerators() { return JSON.stringify(json); }; - console.log('All JSON generators have been defined successfully.'); + /** + * get_variable + * Retrieves the value of a specified variable. + * Generates a JSON object with type, variable_name, and next_value. + */ + Blockly.JSON['get_variable'] = function(block) { + const variableName = block.getFieldValue('variable_name'); // e.g., 'my_var' + const json = { + type: 'get_variable', + variable_name: variableName + }; + + const nextBlock = block.getInputTargetBlock('NEXT'); + if (nextBlock) { + const nextCode = this.blockToCode(nextBlock); + const nextJson = safeParse(nextCode); + json.next_value = nextJson; + } + + return JSON.stringify(json); + }; + + /** + * set_variable + * Sets a specified variable to a given value. + * Generates a JSON object with type, variable_name, and value. + */ + Blockly.JSON['set_variable'] = function(block) { + const variableName = block.getFieldValue('variable_name'); // e.g., 'my_var' + const valueCode = Blockly.JSON.valueToCode(block, 'value', Blockly.JSON.ORDER_ATOMIC) || "0"; + const value = safeParse(valueCode) || parseFloat(valueCode); + + const json = { + type: 'set_variable', + variable_name: variableName, + value: value + }; + + return JSON.stringify(json); + }; + + /** + * flag_is_set + * Checks if a specified flag is set to True. + * Generates a JSON object with type and flag_name. + */ + Blockly.JSON['flag_is_set'] = function(block) { + const flagName = block.getFieldValue('flag_name'); // e.g., 'flag1' + + const json = { + type: 'flag_is_set', + flag_name: flagName + }; + + return JSON.stringify(json); + }; + + /** + * set_flag + * Sets a specified flag to True or False based on a Boolean condition. + * Generates a JSON object with type, condition, flag_name, and flag_value. + */ + Blockly.JSON['set_flag'] = function(block) { + const conditionCode = Blockly.JSON.valueToCode(block, 'condition', Blockly.JSON.ORDER_ATOMIC) || "false"; + const condition = safeParse(conditionCode) || conditionCode; + + const flagName = block.getFieldValue('flag_name'); // e.g., 'flag1' + const flagValue = block.getFieldValue('flag_value'); // 'True' or 'False' + const flag_value_bool = flagValue === 'True' ? true : false; + + const json = { + type: 'set_flag', + condition: condition, + flag_name: flagName, + flag_value: flag_value_bool + }; + + return JSON.stringify(json); + }; + + /************************************************ + * END OF json_generators.js * + ************************************************/ + + console.log('Custom JSON generators defined successfully.'); } diff --git a/src/templates/new_strategy_popup.html b/src/templates/new_strategy_popup.html index fc4f6ba..bfca2dd 100644 --- a/src/templates/new_strategy_popup.html +++ b/src/templates/new_strategy_popup.html @@ -105,46 +105,59 @@ + + + +