I played around with the blocks and i think i have it correct now.

This commit is contained in:
Rob 2024-10-16 00:21:43 -03:00
parent 232d479827
commit c7b7a47129
7 changed files with 1503 additions and 834 deletions

View File

@ -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)

View File

@ -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}']"

View File

@ -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;
}
}
/**

File diff suppressed because it is too large Load Diff

View File

@ -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);
}

View File

@ -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.');
}

View File

@ -105,46 +105,59 @@
<xml id="toolbox" style="display: none">
<!-- Indicators Category -->
<category name="Indicators" colour="230">
<!-- Indicator blocks go here -->
<!-- Indicator blocks will be added here -->
</category>
<category name="Values" colour="230">
<!-- Value Blocks Category -->
<category name="Value" colour="#E69500">
<block type="last_candle_value"></block>
<block type="value_input"></block>
<block type="source"></block>
<block type="strategy_profit_loss"></block>
<block type="current_balance"></block>
<block type="starting_balance"></block>
<block type="strategy_profit_loss"></block>
<block type="active_trades"></block>
<block type="math_operation"></block>
<block type="value_input"></block>
<block type="source"></block>
</category>
<category name="Logic" colour="210">
<block type="comparison"></block>
<!-- Logical Blocks Category -->
<category name="Logical" colour="#5C81A6">
<block type="logical_and"></block>
<block type="logical_or"></block>
<block type="arithmetic_operator"></block>
<block type="is_false"></block>
<block type="flag_is_set"></block>
<block type="set_flag"></block>
<block type="comparison"></block> <!-- Added Comparison Block -->
</category>
<!-- New category for Trading Actions -->
<category name="Trading" colour="230">
<!-- Trade Order Blocks Category -->
<category name="Trade Order" colour="#3366CC">
<block type="trade_action"></block>
<block type="order_type"></block>
<block type="time_in_force"></block>
<block type="stop_loss"></block>
<block type="take_profit"></block>
<block type="limit"></block>
<block type="target_market"></block>
</category>
<!-- New category for Control Blocks -->
<category name="Control" colour="120">
<block type="entry_point"></block>
<block type="exit_point"></block>
<!-- Control Blocks Category -->
<category name="Control" colour="#FF9E00">
<block type="strategy_pause"></block>
<block type="strategy_exit"></block>
<!-- Removed entry_point block as per your request -->
</category>
<!-- Info Blocks Category -->
<category name="Info" colour="#6C8EBF">
<block type="notify_user"></block>
<block type="get_variable"></block>
<block type="set_variable"></block>
<block type="flag_is_set"></block>
<block type="set_flag"></block>
</category>
</xml>