The generator definitions are still being tested but progress has been made.

This commit is contained in:
Rob 2024-10-27 16:51:10 -03:00
parent 5110c8b434
commit bb7fdbf8f9
20 changed files with 1835 additions and 2313 deletions

View File

@ -1,228 +1,27 @@
from DataCache import DataCache
from ExchangeInterface import ExchangeInterface
import unittest
import pandas as pd
import datetime as dt
import os
from Database import SQLite, Database
from shared_utilities import unix_time_millis
"""
{
"type": "strategy",
"statements": [
{
"type": "trade_action",
"trade_type": "buy",
"inputs": {
"size": {
"type": "sqrt",
"inputs": {
"number": {
"type": "power",
"inputs": {
"base": 2,
"exponent": 3
}
}
}
}
}
}
]
}
class TestDataCache(unittest.TestCase):
def setUp(self):
# Set the database connection here
self.exchanges = ExchangeInterface()
self.exchanges.connect_exchange(exchange_name='binance', user_name='test_guy', api_keys=None)
# This object maintains all the cached data. Pass it connection to the exchanges.
self.db_file = 'test_db.sqlite'
self.database = Database(db_file=self.db_file)
# Create necessary tables
with SQLite(db_file=self.db_file) as con:
cursor = con.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS exchange (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS markets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT,
exchange_id INTEGER,
FOREIGN KEY (exchange_id) REFERENCES exchange(id)
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS test_table (
id INTEGER PRIMARY KEY,
market_id INTEGER,
time INTEGER UNIQUE,
open REAL NOT NULL,
high REAL NOT NULL,
low REAL NOT NULL,
close REAL NOT NULL,
volume REAL NOT NULL,
FOREIGN KEY (market_id) REFERENCES markets(id)
)
""")
self.data = DataCache(self.exchanges)
self.data.db = self.database
asset, timeframe, exchange = 'BTC/USD', '2h', 'binance'
self.key1 = f'{asset}_{timeframe}_{exchange}'
asset, timeframe, exchange = 'ETH/USD', '2h', 'binance'
self.key2 = f'{asset}_{timeframe}_{exchange}'
def tearDown(self):
if os.path.exists(self.db_file):
os.remove(self.db_file)
def test_set_cache(self):
print('Testing set_cache flag not set:')
self.data.set_cache(data='data', key=self.key1)
attr = self.data.__getattribute__('cached_data')
self.assertEqual(attr[self.key1], 'data')
self.data.set_cache(data='more_data', key=self.key1)
attr = self.data.__getattribute__('cached_data')
self.assertEqual(attr[self.key1], 'more_data')
print('Testing set_cache no-overwrite flag set:')
self.data.set_cache(data='even_more_data', key=self.key1, do_not_overwrite=True)
attr = self.data.__getattribute__('cached_data')
self.assertEqual(attr[self.key1], 'more_data')
def test_cache_exists(self):
print('Testing cache_exists() method:')
self.assertFalse(self.data.cache_exists(key=self.key2))
self.data.set_cache(data='data', key=self.key1)
self.assertTrue(self.data.cache_exists(key=self.key1))
def test_update_candle_cache(self):
print('Testing update_candle_cache() method:')
df_initial = pd.DataFrame({
'time': [1, 2, 3],
'open': [100, 101, 102],
'high': [110, 111, 112],
'low': [90, 91, 92],
'close': [105, 106, 107],
'volume': [1000, 1001, 1002]
})
df_new = pd.DataFrame({
'time': [3, 4, 5],
'open': [102, 103, 104],
'high': [112, 113, 114],
'low': [92, 93, 94],
'close': [107, 108, 109],
'volume': [1002, 1003, 1004]
})
self.data.set_cache(data=df_initial, key=self.key1)
self.data.update_candle_cache(more_records=df_new, key=self.key1)
result = self.data.get_cache(key=self.key1)
expected = pd.DataFrame({
'time': [1, 2, 3, 4, 5],
'open': [100, 101, 102, 103, 104],
'high': [110, 111, 112, 113, 114],
'low': [90, 91, 92, 93, 94],
'close': [105, 106, 107, 108, 109],
'volume': [1000, 1001, 1002, 1003, 1004]
})
pd.testing.assert_frame_equal(result, expected)
def test_update_cached_dict(self):
print('Testing update_cached_dict() method:')
self.data.set_cache(data={}, key=self.key1)
self.data.update_cached_dict(cache_key=self.key1, dict_key='sub_key', data='value')
cache = self.data.get_cache(key=self.key1)
self.assertEqual(cache['sub_key'], 'value')
def test_get_cache(self):
print('Testing get_cache() method:')
self.data.set_cache(data='data', key=self.key1)
result = self.data.get_cache(key=self.key1)
self.assertEqual(result, 'data')
def test_get_records_since(self):
print('Testing get_records_since() method:')
df_initial = pd.DataFrame({
'time': [unix_time_millis(dt.datetime.utcnow() - dt.timedelta(minutes=i)) for i in range(3)],
'open': [100, 101, 102],
'high': [110, 111, 112],
'low': [90, 91, 92],
'close': [105, 106, 107],
'volume': [1000, 1001, 1002]
})
self.data.set_cache(data=df_initial, key=self.key1)
start_datetime = dt.datetime.utcnow() - dt.timedelta(minutes=2)
result = self.data.get_records_since(key=self.key1, start_datetime=start_datetime, record_length=60,
ex_details=['BTC/USD', '2h', 'binance'])
expected = pd.DataFrame({
'time': df_initial['time'][:2].values,
'open': [100, 101],
'high': [110, 111],
'low': [90, 91],
'close': [105, 106],
'volume': [1000, 1001]
})
pd.testing.assert_frame_equal(result, expected)
def test_get_records_since_from_db(self):
print('Testing get_records_since_from_db() method:')
df_initial = pd.DataFrame({
'market_id': [None],
'time': [unix_time_millis(dt.datetime.utcnow())],
'open': [1.0],
'high': [1.0],
'low': [1.0],
'close': [1.0],
'volume': [1.0]
})
with SQLite(self.db_file) as con:
df_initial.to_sql('test_table', con, if_exists='append', index=False)
start_datetime = dt.datetime.utcnow() - dt.timedelta(minutes=1)
end_datetime = dt.datetime.utcnow()
result = self.data.get_records_since_from_db(table_name='test_table', st=start_datetime, et=end_datetime,
rl=1, ex_details=['BTC/USD', '2h', 'binance']).sort_values(
by='time').reset_index(drop=True)
print("Columns in the result DataFrame:", result.columns)
print("Result DataFrame:\n", result)
# Remove 'id' column from the result DataFrame if it exists
if 'id' in result.columns:
result = result.drop(columns=['id'])
expected = pd.DataFrame({
'market_id': [None],
'time': [unix_time_millis(dt.datetime.utcnow())],
'open': [1.0],
'high': [1.0],
'low': [1.0],
'close': [1.0],
'volume': [1.0]
})
print("Expected DataFrame:\n", expected)
pd.testing.assert_frame_equal(result, expected)
def test_populate_db(self):
print('Testing _populate_db() method:')
start_time = dt.datetime.utcnow() - dt.timedelta(days=1)
end_time = dt.datetime.utcnow()
result = self.data._populate_db(table_name='test_table', start_time=start_time,
end_time=end_time, ex_details=['BTC/USD', '2h', 'binance', 'test_guy'])
self.assertIsInstance(result, pd.DataFrame)
self.assertFalse(result.empty)
def test_fetch_candles_from_exchange(self):
print('Testing _fetch_candles_from_exchange() method:')
start_time = dt.datetime.utcnow() - dt.timedelta(days=1)
end_time = dt.datetime.utcnow()
result = self.data._fetch_candles_from_exchange(symbol='BTC/USD', interval='2h', exchange_name='binance',
user_name='test_guy', start_datetime=start_time,
end_datetime=end_time)
self.assertIsInstance(result, pd.DataFrame)
self.assertFalse(result.empty)
if __name__ == '__main__':
unittest.main()
"""

View File

@ -258,6 +258,8 @@ class StratWorkspaceManager {
constructor() {
this.workspace = null;
this.blocksDefined = false;
this.MAX_TOP_LEVEL_BLOCKS = 10; // Set your desired limit
this.DEFAULT_MAX_NESTING_DEPTH = 5; // Set your desired default max depth
}
/**
@ -287,10 +289,10 @@ class StratWorkspaceManager {
// Separate json_base_generator as it needs to be loaded first
const jsonBaseModule = await import('./blocks/generators/json_base_generator.js');
jsonBaseModule.defineJsonBaseGenerator();
console.log('Defined defineJsonBaseGenerator from json_base_generator.js');
// Define lists of generator and block files with their corresponding define functions
const generatorModules = [
{ file: 'json_base_generator.js', defineFunc: 'defineJsonBaseGenerator' },
{ file: 'balances_generators.js', defineFunc: 'defineBalancesGenerators' },
{ file: 'order_metrics_generators.js', defineFunc: 'defineOrderMetricsGenerators' },
{ file: 'trade_metrics_generators.js', defineFunc: 'defineTradeMetricsGenerators' },
@ -398,28 +400,33 @@ class StratWorkspaceManager {
}
/**
* Generates the strategy data including Python code, JSON representation, and workspace XML.
* @returns {string} - A JSON string containing the strategy data.
* Generates the strategy data including, JSON representation, and workspace XML.
* @returns {string|null} - A JSON string containing the strategy data or null if failed.
*/
compileStrategyJson() {
if (!this.workspace) {
console.error("Workspace is not available.");
return;
return null;
}
const nameElement = document.getElementById('name_box');
if (!nameElement) {
console.error("Name input element (name_box) is not available.");
return;
return null;
}
const strategyName = nameElement.value;
// Initialize code generators
Blockly.JSON.init(this.workspace);
// Remove or comment out the following line as 'init' is undefined
// Blockly.JSON.init(this.workspace);
// Generate code and data representations
const strategyJson = this._generateStrategyJsonFromWorkspace();
if (!strategyJson) {
console.error("Failed to generate strategy JSON.");
return null;
}
// Generate workspace XML for restoration when editing
const workspaceXml = Blockly.Xml.workspaceToDom(this.workspace);
const workspaceXmlText = Blockly.Xml.domToText(workspaceXml);
@ -431,101 +438,99 @@ class StratWorkspaceManager {
});
}
/**
* Generates a JSON representation of the strategy from the workspace.
* @private
* @returns {Object[]} - An array of JSON objects representing the top-level blocks.
*/
_generateStrategyJsonFromWorkspace() {
const topBlocks = this.workspace.getTopBlocks(true);
return topBlocks.map(block => this._blockToJson(block));
const { initializationBlocks, actionBlocks } = this._categorizeOrphanedBlocks(this.workspace);
const totalTopLevelBlocks = initializationBlocks.length + actionBlocks.length;
if (totalTopLevelBlocks > this.MAX_TOP_LEVEL_BLOCKS) {
console.error(`Too many top-level blocks. Maximum allowed is ${this.MAX_TOP_LEVEL_BLOCKS}.`);
alert(`Your strategy has too many top-level blocks. Please reduce the number of top-level blocks to ${this.MAX_TOP_LEVEL_BLOCKS} or fewer.`);
return null;
}
// Proceed with strategy JSON creation
const strategyJson = this._createStrategyJson(initializationBlocks, actionBlocks);
return strategyJson;
}
/**
* Recursively converts a block and its connected blocks into JSON.
/**
* Identify all orphaned blocks on the workspace. Categorize them into initialization blocks and action blocks.
* @private
* @param {Blockly.Block} block - The block to convert.
* @returns {Object} - The JSON representation of the block.
* @returns {Object[]} - an object containing an array of initializationBlocks and actionBlocks.
*/
_blockToJson(block) {
if (!block) {
return null;
_categorizeOrphanedBlocks(workspace) {
const blocks = workspace.getTopBlocks(true);
const orphanedBlocks = blocks.filter(block => !block.getParent());
const initializationBlocks = [];
const actionBlocks = [];
orphanedBlocks.forEach(block => {
// Only consider specified block types
switch (block.type) {
// Initialization block types (run first)
case 'set_available_strategy_balance':
case 'set_variable':
case 'set_flag':
case 'set_leverage':
case 'pause_strategy':
case 'strategy_resume':
case 'strategy_exit':
case 'notify_user':
initializationBlocks.push(block);
break;
// Action block types (run last)
case 'trade_action':
case 'schedule_action':
case 'execute_if':
actionBlocks.push(block);
break;
// Ignore other blocks or log a warning
default:
console.warn(`Block type '${block.type}' is not allowed as a top-level block and will be ignored.`);
break;
}
});
// Use the block's JSON generator if it exists
if (Blockly.JSON && Blockly.JSON[block.type]) {
// Generate JSON string using the block's JSON generator
const jsonString = Blockly.JSON[block.type](block);
// Parse the JSON string into an object
const json = JSON.parse(jsonString);
return { initializationBlocks, actionBlocks };
}
// Process inputs
block.inputList.forEach(input => {
if (input.connection && input.connection.targetBlock()) {
const targetBlock = input.connection.targetBlock();
const targetJson = this._blockToJson(targetBlock);
_createStrategyJson(initializationBlocks, actionBlocks) {
const statements = [];
if (input.type === Blockly.INPUT_VALUE) {
if (!json.inputs) json.inputs = {};
json.inputs[input.name] = targetJson;
} else if (input.type === Blockly.NEXT_STATEMENT) {
if (!json.statements) json.statements = {};
if (!json.statements[input.name]) json.statements[input.name] = [];
json.statements[input.name].push(targetJson);
}
}
});
// Handle the next connected block at the same level
if (block.getNextBlock()) {
const nextBlock = this._blockToJson(block.getNextBlock());
json.next = nextBlock;
}
return json;
} else {
// Fallback: Manually construct JSON if no JSON generator exists
const json = {
type: block.type,
fields: {},
inputs: {},
statements: {}
};
// Capture all fields in the block
block.inputList.forEach(input => {
if (input.fieldRow) {
input.fieldRow.forEach(field => {
if (field.name && field.getValue) {
json.fields[field.name] = field.getValue();
}
});
}
});
// Process inputs
block.inputList.forEach(input => {
if (input.connection && input.connection.targetBlock()) {
const targetBlock = input.connection.targetBlock();
const targetJson = this._blockToJson(targetBlock);
if (input.type === Blockly.INPUT_VALUE) {
json.inputs[input.name] = targetJson;
} else if (input.type === Blockly.NEXT_STATEMENT) {
if (!json.statements[input.name]) json.statements[input.name] = [];
json.statements[input.name].push(targetJson);
}
}
});
// Handle the next connected block at the same level
if (block.getNextBlock()) {
const nextBlock = this._blockToJson(block.getNextBlock());
json.next = nextBlock;
}
return json;
// Process initialization blocks (run first)
initializationBlocks.forEach(block => {
const blockJson = Blockly.JSON._blockToJson(block);
if (blockJson) {
statements.push(blockJson);
}
});
// Process action blocks (run last)
actionBlocks.forEach(block => {
const blockJson = Blockly.JSON._blockToJson(block);
if (blockJson) {
statements.push(blockJson);
}
});
// Create the root strategy block
const strategyJson = {
type: 'strategy',
statements: statements
};
return strategyJson;
}
/**
@ -751,6 +756,12 @@ class Strategies {
const compiledStrategy = this.generateStrategyJson(); // Returns JSON string
const parsedStrategy = JSON.parse(compiledStrategy); // Object with 'name', 'strategy_json', 'workspace'
// Check for an incomplete strategy (no root blocks)
if (!parsedStrategy.strategy_json.statements || parsedStrategy.strategy_json.statements.length === 0) {
alert("Your strategy is incomplete. Please add actions or conditions before submitting.");
return;
}
// Prepare the strategy data to send
strategyData = {
code: parsedStrategy.strategy_json, // The compiled strategy JSON string

View File

@ -27,6 +27,7 @@ export function defineAdvancedMathBlocks() {
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Raise a connected number to a power. Attach a second value to specify the exponent.",
"helpUrl": ""
@ -48,6 +49,7 @@ export function defineAdvancedMathBlocks() {
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Find the remainder after division. Attach a second value to specify the divisor.",
"helpUrl": ""
@ -64,11 +66,12 @@ export function defineAdvancedMathBlocks() {
"args0": [
{
"type": "input_value",
"name": "NUMBER",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Calculate the square root of a connected number.",
"helpUrl": ""
@ -85,11 +88,12 @@ export function defineAdvancedMathBlocks() {
"args0": [
{
"type": "input_value",
"name": "NUMBER",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Get the absolute value of a connected number.",
"helpUrl": ""
@ -106,11 +110,12 @@ export function defineAdvancedMathBlocks() {
"args0": [
{
"type": "input_value",
"name": "NUMBERS",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Determine the maximum value among connected numbers.",
"helpUrl": ""
@ -127,11 +132,12 @@ export function defineAdvancedMathBlocks() {
"args0": [
{
"type": "input_value",
"name": "NUMBERS",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Determine the minimum value among connected numbers.",
"helpUrl": ""
@ -148,11 +154,12 @@ export function defineAdvancedMathBlocks() {
"args0": [
{
"type": "input_value",
"name": "NUMBER",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Calculate the factorial of a connected number.",
"helpUrl": ""
@ -169,11 +176,12 @@ export function defineAdvancedMathBlocks() {
"args0": [
{
"type": "input_value",
"name": "NUMBER",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Calculate the base 10 logarithm of a connected number.",
"helpUrl": ""
@ -190,11 +198,12 @@ export function defineAdvancedMathBlocks() {
"args0": [
{
"type": "input_value",
"name": "NUMBER",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Calculate the natural logarithm of a connected number.",
"helpUrl": ""
@ -220,11 +229,12 @@ export function defineAdvancedMathBlocks() {
},
{
"type": "input_value",
"name": "ANGLE",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Calculate the sine, cosine, or tangent of a connected angle (in radians).",
"helpUrl": ""
@ -241,11 +251,12 @@ export function defineAdvancedMathBlocks() {
"args0": [
{
"type": "input_value",
"name": "NUMBERS",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Calculate the mean of connected numbers.",
"helpUrl": ""
@ -262,11 +273,12 @@ export function defineAdvancedMathBlocks() {
"args0": [
{
"type": "input_value",
"name": "NUMBERS",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Calculate the median of connected numbers.",
"helpUrl": ""
@ -283,11 +295,12 @@ export function defineAdvancedMathBlocks() {
"args0": [
{
"type": "input_value",
"name": "NUMBERS",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Calculate the standard deviation of connected numbers.",
"helpUrl": ""
@ -309,6 +322,7 @@ export function defineAdvancedMathBlocks() {
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Round a connected number. Attach a second value to specify the decimal places.",
"helpUrl": ""
@ -325,11 +339,12 @@ export function defineAdvancedMathBlocks() {
"args0": [
{
"type": "input_value",
"name": "NUMBER",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Round down a connected number to the nearest integer.",
"helpUrl": ""
@ -346,11 +361,12 @@ export function defineAdvancedMathBlocks() {
"args0": [
{
"type": "input_value",
"name": "NUMBER",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Round up a connected number to the nearest integer.",
"helpUrl": ""
@ -372,6 +388,7 @@ export function defineAdvancedMathBlocks() {
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Generate a random number. Attach one value to specify the maximum, or two value to specify minimum(first value) and maximum(second value).",
"helpUrl": ""
@ -393,6 +410,7 @@ export function defineAdvancedMathBlocks() {
}
],
"output": "dynamic_value",
"maxNestingDepth": 3,
"colour": 60,
"tooltip": "Clamp a connected number. Attach a second value for minimum and a third for maximum.",
"helpUrl": ""
@ -430,6 +448,7 @@ export function defineAdvancedMathBlocks() {
],
"inputsInline": true,
"output": "dynamic_value",
"maxNestingDepth": 5,
"colour": 160,
"tooltip": "Perform basic arithmetic operations between two connected numbers.",
"helpUrl": ""

View File

@ -1,108 +1,107 @@
// balances_blocks.js
/**
* Balances Subcategory Blocks Definitions
*
* Note:
* - All blocks except 'set_available_strategy_balance' accept a 'dynamic_value' input named 'VALUES'.
* - These blocks output a 'dynamic_value' containing the result and any unused input values.
* - The 'set_available_strategy_balance' block is a statement block that accepts a 'dynamic_value' input or a number.
*/
export function defineBalanceBlocks() {
/* Register a new type for dynamic value. A block that returns a value may
* require an input of 0 to n values. If the block receives a dynamic value, the dynamic value may
* be a single value or a list of values. If the block expects n values it will pop n values from
* the beginning of the list and use them. The blocks result(s) will be returned inside a list
* appended by the remaining values from the original list.
*/
// Register the 'dynamic_value' type if not already registered
Blockly.Types = Blockly.Types || {};
Blockly.Types.DYNAMIC_VALUE = 'dynamic_value';
/*
* Returns the balance the strategy had when it first started. Returns a
* single value or a list of values where this first value is the output
* of this block and the following are forwarded values from other blocks.
*/
/**
* Starting Balance Block
*
* Retrieves the starting balance of the strategy.
*/
Blockly.defineBlocksWithJsonArray([{
"type": "starting_balance",
"message0": "Starting Balance %1",
"args0": [
{
"type": "input_value",
"name": "NEXT",
"check": "dynamic_value"
}
],
{
"type": "input_value",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"colour": 230,
"tooltip": "Retrieve the starting balance of the strategy.",
"tooltip": "Retrieve the starting balance of the strategy. Attach additional values to forward them.",
"helpUrl": ""
}]);
/*
* Returns the current balance of the user. Returns a single value
* or a list of values where this first value is the output of this
* block and the following are forwarded values from other blocks.
*/
/**
* Current Balance Block
*
* Retrieves the current balance of the strategy.
*/
Blockly.defineBlocksWithJsonArray([{
"type": "current_balance",
"message0": "Current Balance %1",
"args0": [
{
"type": "input_value",
"name": "NEXT",
"check": "dynamic_value"
}
],
{
"type": "input_value",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"colour": 230,
"tooltip": "Retrieve the current balance of the strategy.",
"tooltip": "Retrieve the current balance of the strategy. Attach additional values to forward them.",
"helpUrl": ""
}]);
/*
* Returns the available balance of the user. Returns a single value
* or a list of values where this first value is the output of this
* block and the following are forwarded values from other blocks.
*/
/**
* Available Balance Block
*
* Retrieves the overall available (liquid) balance of the user.
*/
Blockly.defineBlocksWithJsonArray([{
"type": "available_balance",
"message0": "Available Balance %1",
"args0": [
{
"type": "input_value",
"name": "NEXT",
"check": "dynamic_value" // Accepts single or list of values
}
],
{
"type": "input_value",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"colour": 230,
"tooltip": "Retrieve the overall available (liquid) balance of the user.",
"tooltip": "Retrieve the overall available (liquid) balance. Attach additional values to forward them.",
"helpUrl": ""
}]);
/*
* Returns the available balance of the strategy. Returns a single value
* or a list of values where this first value is the output of this
* block and the following are forwarded values from other blocks.
*/
/**
* Available Strategy Balance Block
*
* Retrieves the available balance allocated specifically to the strategy.
*/
Blockly.defineBlocksWithJsonArray([{
"type": "available_strategy_balance",
"message0": "Available Strategy Balance %1",
"args0": [
{
"type": "input_value",
"name": "NEXT",
"check": "dynamic_value"
}
],
{
"type": "input_value",
"name": "VALUES",
"check": "dynamic_value"
}
],
"output": "dynamic_value",
"colour": 230,
"tooltip": "Retrieve the available balance allocated specifically to the strategy.",
"tooltip": "Retrieve the available balance allocated to the strategy. Attach additional values to forward them.",
"helpUrl": ""
}]);
/*
* Sets the available balance of the strategy. Receives a single value
* or a list of values where this first value is the only value that is
* considered and all others values are ignored.
*/
/**
* Set Available Strategy Balance Block
*
* Sets the balance allocated to the strategy.
*/
Blockly.defineBlocksWithJsonArray([{
"type": "set_available_strategy_balance",
"message0": "Set Available Strategy Balance to %1",
@ -116,7 +115,7 @@ export function defineBalanceBlocks() {
"previousStatement": null,
"nextStatement": null,
"colour": 230,
"tooltip": "Set the balance allocated to the strategy.",
"tooltip": "Set the balance allocated to the strategy. Accepts a number or the first value from a dynamic list.",
"helpUrl": ""
}]);
}
}

View File

@ -27,13 +27,13 @@ export function defineLogicalBlocks() {
"args0": [
{
"type": "input_value",
"name": "left",
"name": "LEFT",
"check": ["number", "dynamic_value"],
"align": "RIGHT"
},
{
"type": "field_dropdown",
"name": "operator",
"name": "OPERATOR",
"options": [
[">", ">"],
["<", "<"],
@ -42,7 +42,7 @@ export function defineLogicalBlocks() {
},
{
"type": "input_value",
"name": "right",
"name": "RIGHT",
"check": ["number", "dynamic_value"],
"align": "LEFT"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,131 +1,149 @@
// balances_generators.js
/**
* Balances Generators Definitions
*
* This file contains generator functions for each block defined in
* balances_blocks.js. Each generator translates a Blockly block
* into a JSON object representing the balance-related operations.
* into a JSON object representing balance-related operations.
*/
export function defineBalancesGenerators() {
// Ensure the base generator is initialized and 'createOperationObject' is available
if (!Blockly.JSON || !Blockly.JSON.createOperationObject) {
console.error("Blockly.JSON or createOperationObject not initialized. Please ensure 'json_base_generator.js' is loaded first.");
return;
}
/**
* Helper Function: processValuesInput
* Processes the 'VALUES' input and returns an array of values.
*
* @param {Blockly.Block} block - The block containing the 'VALUES' input.
* @returns {Array} - An array of values from the 'VALUES' input.
*/
function processValuesInput(block) {
const valuesInputBlock = block.getInputTargetBlock('VALUES');
let values = [];
if (valuesInputBlock) {
const valuesJson = Blockly.JSON.blockToCode(valuesInputBlock);
if (Array.isArray(valuesJson)) {
values = valuesJson;
} else if (valuesJson !== null && valuesJson !== undefined) {
values = [valuesJson];
}
}
return values;
}
/**
* Generator for 'starting_balance' Block
*
* Retrieves the starting balance of the strategy.
* Generates a JSON object with type and next (if connected).
*/
Blockly.JSON['starting_balance'] = function(block) {
const json = {
type: 'starting_balance'
const valuesArray = processValuesInput(block);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('starting_balance', {});
// Include unused values if any
const outputValues = [operationObject, ...valuesArray];
// Output as dynamic_value
return {
type: 'dynamic_value',
values: outputValues
};
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
Blockly.JSON.appendNext(json, nextCode);
return JSON.stringify(json);
};
/**
* Generator for 'current_balance' Block
*
* Retrieves the current balance of the strategy.
* Generates a JSON object with type and next (if connected).
*/
Blockly.JSON['current_balance'] = function(block) {
const json = {
type: 'current_balance'
const valuesArray = processValuesInput(block);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('current_balance', {});
// Include unused values if any
const outputValues = [operationObject, ...valuesArray];
// Output as dynamic_value
return {
type: 'dynamic_value',
values: outputValues
};
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
Blockly.JSON.appendNext(json, nextCode);
return JSON.stringify(json);
};
/**
* Generator for 'available_balance' Block
*
* Retrieves the overall available (liquid) balance of the user.
* Generates a JSON object with type and next (if connected).
*/
Blockly.JSON['available_balance'] = function(block) {
const json = {
type: 'available_balance'
const valuesArray = processValuesInput(block);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('available_balance', {});
// Include unused values if any
const outputValues = [operationObject, ...valuesArray];
// Output as dynamic_value
return {
type: 'dynamic_value',
values: outputValues
};
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
Blockly.JSON.appendNext(json, nextCode);
return JSON.stringify(json);
};
/**
* Generator for 'available_strategy_balance' Block
*
* Retrieves the available balance allocated specifically to the strategy.
* Generates a JSON object with type and next (if connected).
*/
Blockly.JSON['available_strategy_balance'] = function(block) {
const json = {
type: 'available_strategy_balance'
const valuesArray = processValuesInput(block);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('available_strategy_balance', {});
// Include unused values if any
const outputValues = [operationObject, ...valuesArray];
// Output as dynamic_value
return {
type: 'dynamic_value',
values: outputValues
};
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
Blockly.JSON.appendNext(json, nextCode);
return JSON.stringify(json);
};
/**
* Generator for 'set_available_strategy_balance' Block
*
* Sets the balance allocated to the strategy.
* Generates a JSON object with type, balance, and next (if connected).
* This is a statement block, so it generates an action object and handles 'NEXT' connections.
*/
Blockly.JSON['set_available_strategy_balance'] = function(block) {
try {
// Retrieve and parse the 'BALANCE' input
const balanceCode = Blockly.JSON.valueToCode(block, 'BALANCE', Blockly.JSON.ORDER_ATOMIC) || "0";
let balance;
// Retrieve the 'BALANCE' input block
const balanceBlock = block.getInputTargetBlock('BALANCE');
let balance = 0;
try {
const parsedBalance = Blockly.JSON.safeParse(balanceCode);
if (Array.isArray(parsedBalance)) {
// If balanceCode is a list, take the first value
balance = parseFloat(parsedBalance[0]);
} else {
// If balanceCode is a single value
balance = parseFloat(parsedBalance);
}
} catch (e) {
// If balanceCode is not a JSON string (e.g., a simple number)
balance = parseFloat(balanceCode);
if (balanceBlock) {
const balanceJson = Blockly.JSON.blockToCode(balanceBlock);
if (typeof balanceJson === 'number') {
balance = balanceJson;
} else {
console.error("Invalid balance value in 'set_available_strategy_balance' block. Expected a number.");
}
if (isNaN(balance)) {
throw new Error("Invalid balance value.");
}
const json = {
type: 'set_available_strategy_balance',
balance: balance
};
// Optionally handle 'NEXT' if statement chaining is required
// For statement blocks, chaining is managed via Blockly's internal mechanisms
// Uncomment the lines below if you have a 'NEXT' input in the block definition
/*
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
Blockly.JSON.appendNext(json, nextCode);
*/
return JSON.stringify(json);
} catch (error) {
console.error(`Error in set_available_strategy_balance generator: ${error.message}`);
// Return a default JSON object or handle the error as needed
return JSON.stringify({ type: 'set_available_strategy_balance', balance: 0 });
} else {
console.warn("No balance provided in 'set_available_strategy_balance' block. Using default value 0.");
}
const json = {
type: 'set_available_strategy_balance',
inputs: {
balance: balance
}
};
// No need to handle 'NEXT' input; sequencing is managed by _blockToJson
return json; // Return the action object directly
};
}

View File

@ -1,200 +1,184 @@
// control_generators.js
/**
* Control Generators Definitions
*
* This file contains generator functions for each block defined in
* control_blocks.js. Each generator translates a Blockly block
* into a JSON object representing the control operation.
*
* Note: This file assumes that `json_base_generator.js` has been loaded
* before it, which initializes `Blockly.JSON` and attaches helper functions.
* into a JSON object representing control operations.
*/
export function defineControlGenerators() {
// Ensure the base generator is initialized and 'createOperationObject' is available
if (!Blockly.JSON || !Blockly.JSON.createOperationObject) {
console.error("Blockly.JSON or createOperationObject not initialized. Please ensure 'json_base_generator.js' is loaded first.");
return;
}
/**
* Generator for 'pause_strategy' Block
*
* Description:
* Generates a JSON object to pause the current strategy.
* This block does not accept any inputs or forward any additional data.
*/
Blockly.JSON['pause_strategy'] = function(block) {
const json = {
type: 'pause_strategy'
};
// Handle 'NEXT' input if connected (optional)
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
Blockly.JSON.appendNext(json, nextCode);
return JSON.stringify(json);
};
// Blockly.JSON['pause_strategy'] = function(block) {
// const json = {
// type: 'pause_strategy'
// };
//
// return json; // Return the JSON object directly
// };
/**
* Generator for 'strategy_resume' Block
*
* Description:
* Generates a JSON object to resume the strategy.
* This block does not accept any inputs or forward any additional data.
*/
Blockly.JSON['strategy_resume'] = function(block) {
const json = {
type: 'strategy_resume'
};
// Handle 'NEXT' input if connected (optional)
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
Blockly.JSON.appendNext(json, nextCode);
return JSON.stringify(json);
};
// Blockly.JSON['strategy_resume'] = function(block) {
// const json = {
// type: 'strategy_resume'
// };
//
// return json; // Return the JSON object directly
// };
/**
* Generator for 'strategy_exit' Block
*
* Description:
* Generates a JSON object to exit the strategy and close trades based on the selected condition.
* Users can choose to close all trades, only profitable trades, or only losing trades.
*/
Blockly.JSON['strategy_exit'] = function(block) {
const conditionValue = block.getFieldValue('condition'); // 'all', 'in_profit', 'in_loss'
const json = {
type: 'strategy_exit',
condition: conditionValue
};
// Handle 'NEXT' input if connected (optional)
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
Blockly.JSON.appendNext(json, nextCode);
return JSON.stringify(json);
};
// Blockly.JSON['strategy_exit'] = function(block) {
// const conditionValue = block.getFieldValue('condition'); // 'all', 'in_profit', 'in_loss'
//
// const json = {
// type: 'strategy_exit',
// inputs: {
// condition: conditionValue
// }
// };
//
// return json; // Return the JSON object directly
// };
/**
* Generator for 'max_drawdown' Block
*
* Description:
* Generates a JSON object to set a maximum drawdown limit. If the drawdown exceeds the specified value,
* the strategy will either pause or exit based on user selection.
*/
Blockly.JSON['max_drawdown'] = function(block) {
const drawdownCode = Blockly.JSON.valueToCode(block, 'DRAWDOWN_VALUE', Blockly.JSON.ORDER_ATOMIC) || "0";
const action = block.getFieldValue('ACTION'); // 'PAUSE' or 'EXIT'
const drawdownValue = Blockly.JSON.getFirstValue(drawdownCode);
// Retrieve the drawdown value from 'DRAWDOWN_VALUE' input
let drawdownValue = 0;
const drawdownBlock = block.getInputTargetBlock('DRAWDOWN_VALUE');
if (drawdownBlock) {
drawdownValue = Blockly.JSON.blockToCode(drawdownBlock);
if (typeof drawdownValue !== 'number') {
console.error("Invalid drawdown value in 'max_drawdown' block.");
drawdownValue = 0;
}
} else {
console.warn("No drawdown value provided in 'max_drawdown' block. Using default value 0.");
}
const json = {
type: 'max_drawdown',
drawdown: drawdownValue !== null ? drawdownValue : 0,
action: action
inputs: {
drawdown: drawdownValue,
action: action
}
};
// Handle 'NEXT' input if connected (optional)
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
Blockly.JSON.appendNext(json, nextCode);
return JSON.stringify(json);
return json; // Return the JSON object directly
};
/**
* Generator for 'schedule_action' Block
*
* Description:
* Generates a JSON object to schedule a specific action after a defined time interval,
* with an option to repeat the action.
*/
Blockly.JSON['schedule_action'] = function(block) {
// Retrieve the nested statements from 'DO' input
const doCode = Blockly.JSON.statementToCode(block, 'DO') || "";
// Retrieve the time interval from dropdown
const timeInterval = block.getFieldValue('TIME_INTERVAL'); // e.g., '1m', '5m', etc.
// Retrieve the repeat option
const repeatOption = block.getFieldValue('REPEAT'); // 'ONCE' or 'REPEAT'
// Convert time interval string to milliseconds
const timeMapping = {
'1m': 60000,
'5m': 300000,
'15m': 900000,
'30m': 1800000,
'1h': 3600000,
'4h': 14400000,
'6h': 21600000,
'12h': 43200000,
'1d': 86400000,
'3d': 259200000,
'1w': 604800000,
'1M': 2592000000
};
const timeIntervalMs = timeMapping[timeInterval] || 0;
const json = {
// Create operation object
const operationObject = {
type: 'schedule_action',
time_interval: timeIntervalMs,
repeat: repeatOption === 'REPEAT' // Boolean: true if 'REPEAT', false if 'ONCE'
inputs: {
time_interval: timeInterval,
repeat: repeatOption === 'REPEAT' // true if 'REPEAT', false if 'ONCE'
}
};
// If the 'DO' input is connected, include the nested statements
if (doCode.trim() !== "") {
// Parse the nested statements JSON
const doJson = Blockly.JSON.safeParse(doCode);
if (doJson) {
json.action = doJson;
} else {
console.error("Failed to parse nested 'DO' statements in 'schedule_action' block.");
// Process the 'DO' statements
const actionJsonArray = [];
let currentBlock = block.getInputTargetBlock('DO');
while (currentBlock) {
const blockJson = Blockly.JSON.blockToCode(currentBlock);
if (blockJson) {
actionJsonArray.push(blockJson);
}
currentBlock = currentBlock.getNextBlock();
}
// Handle 'NEXT' input if connected (optional)
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
Blockly.JSON.appendNext(json, nextCode);
// Include the nested statements if any
if (actionJsonArray.length > 0) {
operationObject.inputs.action = actionJsonArray;
}
return JSON.stringify(json);
return operationObject; // Return the JSON object directly
};
/**
* Generator for 'execute_if' Block (New)
*
* Description:
* Generates a JSON object to execute a series of statements if a specified condition returns true.
* Allows users to implement conditional logic within their strategy.
* Generator for 'execute_if' Block
*/
Blockly.JSON['execute_if'] = function(block) {
const conditionCode = Blockly.JSON.valueToCode(block, 'CONDITION', Blockly.JSON.ORDER_ATOMIC) || "false";
const doCode = Blockly.JSON.statementToCode(block, 'DO') || "";
const condition = Blockly.JSON.getFirstValue(conditionCode);
// Validate the condition
let validCondition = false;
if (typeof condition === 'boolean') {
validCondition = condition;
} else {
// Implement additional validation or conversion if necessary
console.warn("Condition in 'execute_if' block is not a boolean. Defaulting to false.");
}
const json = {
type: 'execute_if',
condition: validCondition
};
// If the 'DO' input is connected, include the nested statements
if (doCode.trim() !== "") {
const doJson = Blockly.JSON.safeParse(doCode);
if (doJson) {
json.do = doJson;
try {
// Process CONDITION input
let conditionJson = null;
const conditionBlock = block.getInputTargetBlock('CONDITION');
if (conditionBlock) {
conditionJson = Blockly.JSON._blockToJson(conditionBlock, 1);
if (typeof conditionJson !== 'object' || conditionJson === null) {
console.error(`Invalid 'CONDITION' input in 'execute_if' block. Expected an object.`);
conditionJson = { type: 'comparison', operator: '==', inputs: { LEFT: 0, RIGHT: 0 } }; // Default condition
}
} else {
console.error("Failed to parse nested 'DO' statements in 'execute_if' block.");
console.warn(`No block connected to 'CONDITION' input of 'execute_if' block. Using default condition.`);
conditionJson = { type: 'comparison', operator: '==', inputs: { LEFT: 0, RIGHT: 0 } }; // Default condition
}
// Process DO statements
const doStatements = [];
let currentBlock = block.getInputTargetBlock('DO');
while (currentBlock) {
const blockJson = Blockly.JSON._blockToJson(currentBlock, 1);
if (blockJson) {
doStatements.push(blockJson);
}
currentBlock = currentBlock.getNextBlock();
}
// If no DO statements, exclude the block
if (doStatements.length === 0) {
console.warn(`'execute_if' block has no DO statements. Excluding from JSON.`);
return null; // Exclude the block
}
// **Construct the JSON object**
const json = {
type: 'execute_if',
inputs: {
CONDITION: conditionJson
}
};
if (doStatements.length > 0) {
json.statements = {
DO: doStatements
};
}
// **Set the skipAdditionalParsing flag**
json.skipAdditionalParsing = true;
console.log(`Generated JSON for 'execute_if' block:`, json);
return json;
} catch (error) {
console.error(`Error in generator for 'execute_if' block:`, error);
return null; // Return null to indicate failure
}
// Handle 'NEXT' input if connected (optional)
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
Blockly.JSON.appendNext(json, nextCode);
return JSON.stringify(json);
};
}

View File

@ -1,90 +1,267 @@
// /generators/json_base_generator.js
/**
* JSON Base Generator Definitions
*
* This file initializes the Blockly.JSON generator and attaches helper functions
* to it. These helpers are available to all other generator files, promoting code
* re-usability and consistency.
*/
// /blocks/generators/json_base_generator.js
export function defineJsonBaseGenerator() {
if (!Blockly.JSON) {
Blockly.JSON = new Blockly.Generator('JSON');
// Define operator precedence if necessary
Blockly.JSON.ORDER = {
'NONE': 0,
'ATOMIC': 0,
'COMMA': 1,
'ASSIGNMENT': 2,
// Add more as needed
};
/**
* List of trade_option block types to exclude 'next' processing.
*/
const TRADE_OPTION_TYPES = ['time_in_force', 'limit', "trailing_limit", 'target_market',
"stop_loss", "trailing_stop", "take_profit", "name_order"];
// Define operator precedence constants
Blockly.JSON.ORDER_ATOMIC = 0; // 0 "" ...
Blockly.JSON.ORDER_NONE = 99; // (no precedence)
Blockly.JSON.ORDER_COMMA = 1; // ,
Blockly.JSON.ORDER_ASSIGNMENT = 2; // =
/**
* Helper Function: safeParse
* Safely parses a JSON string and returns the corresponding object.
* If parsing fails, it returns null.
*
* @param {string} jsonString - The JSON string to parse.
* @returns {object|null} - The parsed object or null if parsing fails.
* Override the default blockToCode method to generate JSON from an individual block.
*/
Blockly.JSON.safeParse = function(jsonString) {
try {
return JSON.parse(jsonString);
} catch (e) {
console.error("JSON Parsing Error:", e);
Blockly.JSON.blockToCode = function(block) {
if (Blockly.JSON && Blockly.JSON[block.type]) {
const jsonObject = Blockly.JSON[block.type](block);
if (jsonObject && typeof jsonObject === 'object') {
return jsonObject;
} else {
console.error(`Generator for block type '${block.type}' did not return a valid JSON object.`);
return null;
}
} else {
console.error(`No generator function found for block type '${block.type}'.`);
return null;
}
};
/**
* Helper Function: getFirstValue
* Extracts the first value from a dynamic_value input.
* If the input is a list, it returns the first element.
* If the input is a single value, it returns that value.
* If the input is undefined or invalid, it returns null.
* safeParse Function
*
* @param {string} code - The JSON string representing the input.
* @returns {any|null} - The first value or null.
* Description:
* Safely parses a string into a JavaScript value. Attempts to parse as JSON,
* number, boolean, or defaults to the original string if parsing fails.
*
* @param {string} code - The string to parse.
* @returns {*} The parsed value, or the original string if parsing fails.
*/
Blockly.JSON.getFirstValue = function(code) {
if (!code) return null;
const parsed = Blockly.JSON.safeParse(code);
if (!parsed) return null;
if (Array.isArray(parsed)) {
return parsed[0];
Blockly.JSON.safeParse = function(code) {
// Attempt to parse as JSON
try {
return JSON.parse(code);
} catch (jsonError) {
// If JSON parsing fails, proceed to other types
}
return parsed;
// Trim the code to handle leading/trailing whitespace
const trimmedCode = code.trim();
// Attempt to parse as a number
const number = parseFloat(trimmedCode);
if (!isNaN(number)) {
return number;
}
// Attempt to parse as a boolean
if (trimmedCode.toLowerCase() === 'true') {
return true;
}
if (trimmedCode.toLowerCase() === 'false') {
return false;
}
// If all parsing attempts fail, return the original string
return trimmedCode;
};
/**
* Helper Function: appendNext
* Appends the 'next' JSON object to the current JSON object if 'NEXT' is connected.
* Helper Function: createOperationObject
* Creates an operation object with the specified type and inputs.
* This function standardizes the creation of operation objects across generators.
*
* @param {object} json - The current JSON object being constructed.
* @param {string} nextCode - The JSON string of the 'NEXT' input.
* @param {string} type - The type of the operation (e.g., 'power', 'sqrt').
* @param {object} inputs - An object containing the inputs for the operation.
* @returns {object} - The operation object.
*/
Blockly.JSON.appendNext = function(json, nextCode) {
if (nextCode) {
const nextJson = Blockly.JSON.safeParse(nextCode);
if (nextJson) {
if (nextJson.type) {
// If nextJson is a single block
json.next = nextJson;
} else if (Array.isArray(nextJson)) {
// If nextJson is already a list
json.next = nextJson;
} else {
// If nextJson is an object, wrap it in a list
json.next = [nextJson];
}
Blockly.JSON.createOperationObject = function(type, inputs) {
return {
type: type,
inputs: inputs
};
};
/**
* Helper Function: _blockToJson
* Recursively converts a block and its connected blocks into JSON.
*
* @param {Blockly.Block} block - The block to convert.
* @param {number} currentDepth - The current recursion depth.
* @returns {Object} - The JSON representation of the block.
*/
Blockly.JSON._blockToJson = function(block, currentDepth = 0) {
if (!block) {
return null;
}
// Calculate nesting depth and enforce limits
try {
Blockly.JSON._calculateNestingDepth(block, currentDepth);
} catch (error) {
console.error(error.message);
alert(error.message);
throw error; // Stop processing if limit exceeded
}
// Use the block's JSON generator if it exists
if (Blockly.JSON && Blockly.JSON[block.type]) {
// Generate JSON object using the block's JSON generator
const json = Blockly.JSON[block.type](block);
if (!json || typeof json !== 'object') {
console.error(`Generator for block type '${block.type}' did not return a valid JSON object.`);
return null;
}
// **Check for the skipAdditionalParsing flag**
if (json.skipAdditionalParsing) {
// Remove the flag to prevent it from being included in the final JSON
delete json.skipAdditionalParsing;
// **Decide whether to include the block based on the presence of meaningful content**
if (!json.type) {
// If 'type' is undefined or null, exclude the block
return null;
}
// Return the JSON as it has been fully handled by the generator
return json;
}
// **Proceed with existing parsing logic only if skipAdditionalParsing is not set**
block.inputList.forEach(input => {
if (input.type === Blockly.INPUT_VALUE && input.connection && input.connection.targetBlock()) {
const targetBlock = input.connection.targetBlock();
const targetJson = Blockly.JSON._blockToJson(targetBlock, currentDepth + 1); // Increment currentDepth
if (!json.inputs) json.inputs = {};
json.inputs[input.name] = targetJson;
}
});
// **Process Statement Inputs**
block.inputList.forEach(input => {
if (input.type === Blockly.NEXT_STATEMENT && input.connection && input.connection.targetBlock()) {
const targetBlock = input.connection.targetBlock();
const targetJson = Blockly.JSON._blockToJson(targetBlock, currentDepth + 1); // Increment currentDepth
if (!json.statements) json.statements = {};
if (!json.statements[input.name]) json.statements[input.name] = [];
json.statements[input.name].push(targetJson);
}
});
// **Handle 'next' connections**
if (block.getNextBlock()) {
const nextBlock = Blockly.JSON._blockToJson(block.getNextBlock(), currentDepth); // Same depth
json.next = nextBlock;
}
return json;
} else {
// Fallback: Manually construct JSON if no JSON generator exists
const json = {
type: block.type,
fields: {},
inputs: {},
statements: {}
};
// Capture all fields in the block
block.inputList.forEach(input => {
if (input.fieldRow) {
input.fieldRow.forEach(field => {
if (field.name && field.getValue) {
json.fields[field.name] = field.getValue();
}
});
}
});
// Process inputs
block.inputList.forEach(input => {
if (input.type === Blockly.INPUT_VALUE && input.connection && input.connection.targetBlock()) {
const targetBlock = input.connection.targetBlock();
const targetJson = Blockly.JSON._blockToJson(targetBlock, currentDepth + 1);
json.inputs[input.name] = targetJson;
} else if (input.type === Blockly.NEXT_STATEMENT && input.connection && input.connection.targetBlock()) {
const targetBlock = input.connection.targetBlock();
const targetJson = Blockly.JSON._blockToJson(targetBlock, currentDepth + 1);
if (!json.statements) json.statements = {};
if (!json.statements[input.name]) json.statements[input.name] = [];
json.statements[input.name].push(targetJson);
}
});
// **Handle 'next' connections**
if (block.getNextBlock()) {
const nextBlock = Blockly.JSON._blockToJson(block.getNextBlock(), currentDepth);
json.next = nextBlock;
}
return json;
}
};
console.log('Blockly.JSON generator initialized with safeParse, getFirstValue, and appendNext.');
} else {
console.log('Blockly.JSON was already initialized.');
/**
* Helper Function: _calculateNestingDepth
* Calculates the nesting depth of a block and throws an error if it exceeds the maximum allowed depth.
*
* @param {Blockly.Block} block - The block to calculate depth for.
* @param {number} currentDepth - The current recursion depth.
*/
Blockly.JSON._calculateNestingDepth = function(block, currentDepth) {
const MAX_DEPTH = 10;
if (currentDepth > MAX_DEPTH) {
throw new Error(`Maximum block nesting depth of ${MAX_DEPTH} exceeded.`);
}
// Add any additional depth calculations or checks if necessary
};
/**
* Helper Function: processNumericInput
* Processes a numeric input.
*
* @param {Blockly.Block} block - The block containing the input.
* @param {string} inputName - The name of the input field.
* @param {number} defaultValue - The default value to use if input is invalid.
* @returns {number} - The validated input.
*/
Blockly.JSON.processNumericInput = function (block, inputName, defaultValue) {
const inputBlock = block.getInputTargetBlock(inputName);
// Return default value if input block is missing
if (!inputBlock) {
console.warn(`No block connected to '${inputName}' input of '${block.type}' block. Using default value ${defaultValue}.`);
return defaultValue;
}
const inputJson = Blockly.JSON.blockToCode(inputBlock);
// Check if inputJson is correctly structured
if (!inputJson || inputJson.type !== 'dynamic_value' || !Array.isArray(inputJson.values) || inputJson.values.length === 0) {
console.error(`Invalid JSON structure for '${inputName}' in '${block.type}' block. Expected a 'dynamic_value' with at least one value.`);
return defaultValue;
}
const extractedValue = inputJson.values[0];
return extractedValue;
};
console.log('Blockly.JSON generator initialized with helper functions: createOperationObject, _blockToJson, collectTradeOptions.');
}
}

View File

@ -1,5 +1,3 @@
// logical_generators.js
/**
* Logical Generators Definitions
*
@ -7,166 +5,220 @@
* logical_blocks.js. Each generator translates a Blockly block into a JSON
* object representing logical operations such as comparisons, logical AND/OR,
* boolean condition checks, and flag evaluations.
*
* Note: This file assumes that `json_base_generator.js` has been loaded
* before it, which initializes `Blockly.JSON` and attaches helper functions.
*/
export function defineLogicalGenerators() {
// Ensure the base generator is initialized and 'createOperationObject' is available
if (!Blockly.JSON || !Blockly.JSON.createOperationObject) {
console.error("Blockly.JSON or createOperationObject not initialized. Please ensure 'json_base_generator.js' is loaded first.");
return;
}
/**
* Generator for 'comparison' Block
* Helper Function: processTwoValueInput
* Processes two value inputs ('LEFT' and 'RIGHT') and returns their values.
*
* Description:
* Generates a JSON object representing a comparison operation between two values
* using operators like >, <, ==. Outputs a Boolean value based on the comparison result.
* @param {Blockly.Block} block - The block containing the inputs.
* @returns {Object} - An object containing 'left' and 'right' values.
*/
Blockly.JSON['comparison'] = function(block) {
// Retrieve and parse the 'left' input
const leftCode = Blockly.JSON.valueToCode(block, 'left', Blockly.JSON.ORDER_ATOMIC) || "0";
let left;
try {
left = Blockly.JSON.safeParse(leftCode);
} catch (e) {
console.warn(`Error parsing 'left' input in comparison block: ${e.message}. Defaulting to 0.`);
left = 0;
function processTwoValueInput(block) {
const leftBlock = block.getInputTargetBlock('LEFT');
const rightBlock = block.getInputTargetBlock('RIGHT');
let left = null;
let right = null;
if (leftBlock) {
const leftJson = Blockly.JSON.blockToCode(leftBlock);
left = typeof leftJson !== 'undefined' ? leftJson : null;
} else {
console.warn(`No block connected to 'LEFT' input of '${block.type}' block. Using default value.`);
left = null; // Assign a default value if necessary
}
// Retrieve the selected operator
const operator = block.getFieldValue('operator'); // '>', '<', '=='
// Retrieve and parse the 'right' input
const rightCode = Blockly.JSON.valueToCode(block, 'right', Blockly.JSON.ORDER_ATOMIC) || "0";
let right;
try {
right = Blockly.JSON.safeParse(rightCode);
} catch (e) {
console.warn(`Error parsing 'right' input in comparison block: ${e.message}. Defaulting to 0.`);
right = 0;
if (rightBlock) {
const rightJson = Blockly.JSON.blockToCode(rightBlock);
right = typeof rightJson !== 'undefined' ? rightJson : null;
} else {
console.warn(`No block connected to 'RIGHT' input of '${block.type}' block. Using default value.`);
right = null; // Assign a default value if necessary
}
const json = {
type: 'comparison',
left: left,
operator: operator,
right: right
};
return { left, right };
}
return JSON.stringify(json);
/**
* Generator for 'comparison' Block
*
* Generates a JSON object representing a comparison operation between two values
* using operators like '>', '<', '=='. Outputs a Boolean value based on the comparison result.
*/
Blockly.JSON['comparison'] = function(block) {
/**
* Helper Function: processTwoValueInput
* Processes 'LEFT' and 'RIGHT' inputs and returns their JSON representations.
*
* @param {Blockly.Block} block - The block containing the 'LEFT' and 'RIGHT' inputs.
* @returns {Object} - An object with 'LEFT' and 'RIGHT' properties.
*/
function processTwoValueInput(block) {
const leftBlock = block.getInputTargetBlock('LEFT');
const rightBlock = block.getInputTargetBlock('RIGHT');
let left = null;
let right = null;
if (leftBlock) {
left = Blockly.JSON.blockToCode(leftBlock);
if (typeof left !== 'object' || left === null) {
console.error(`Invalid 'LEFT' input in 'comparison' block. Expected an object.`);
left = 0;
}
} else {
console.warn(`No block connected to 'LEFT' input of 'comparison' block. Using default value 0.`);
left = 0; // Assign a default value if necessary
}
if (rightBlock) {
right = Blockly.JSON.blockToCode(rightBlock);
if (typeof right !== 'object' || right === null) {
console.error(`Invalid 'RIGHT' input in 'comparison' block. Expected an object.`);
right = 0;
}
} else {
console.warn(`No block connected to 'RIGHT' input of 'comparison' block. Using default value 0.`);
right = 0; // Assign a default value if necessary
}
return { LEFT: left, RIGHT: right };
}
const { LEFT, RIGHT } = processTwoValueInput(block);
// Retrieve the selected operator
const operator = block.getFieldValue('OPERATOR'); // Must match the block definition
const validOperators = ['>', '<', '==', '!=', '>=', '<='];
if (!validOperators.includes(operator)) {
console.error(`Invalid operator '${operator}' in 'comparison' block. Defaulting to '=='`);
}
const validatedOperator = validOperators.includes(operator) ? operator : '==';
// Create operation object with 'inputs'
const operationObject = {
type: 'comparison',
operator: validatedOperator,
inputs: {
LEFT: LEFT !== null ? LEFT : { type: 'value_input', values: [0] },
RIGHT: RIGHT !== null ? RIGHT : { type: 'value_input', values: [0] }
}
};
return operationObject;
};
/**
* Generator for 'logical_and' Block
*
* Description:
* Generates a JSON object representing a logical AND operation between two Boolean conditions.
* Outputs true only if both conditions are true.
* Generates a JSON object representing a logical AND operation between two conditions.
*/
Blockly.JSON['logical_and'] = function(block) {
// Retrieve and parse the 'left' Boolean input
const leftCode = Blockly.JSON.valueToCode(block, 'left', Blockly.JSON.ORDER_ATOMIC) || "false";
let left;
try {
left = Blockly.JSON.safeParse(leftCode);
if (typeof left !== 'boolean') {
throw new Error("Left input is not a Boolean.");
}
} catch (e) {
console.warn(`Error parsing 'left' input in logical_and block: ${e.message}. Defaulting to false.`);
left = false;
}
const { left, right } = processTwoValueInput(block);
// Retrieve and parse the 'right' Boolean input
const rightCode = Blockly.JSON.valueToCode(block, 'right', Blockly.JSON.ORDER_ATOMIC) || "false";
let right;
try {
right = Blockly.JSON.safeParse(rightCode);
if (typeof right !== 'boolean') {
throw new Error("Right input is not a Boolean.");
}
} catch (e) {
console.warn(`Error parsing 'right' input in logical_and block: ${e.message}. Defaulting to false.`);
right = false;
}
// Default to false if any condition is null
const leftCondition = typeof left === 'boolean' ? left : false;
const rightCondition = typeof right === 'boolean' ? right : false;
const json = {
type: 'logical_and',
left: left,
right: right
};
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('logical_and', {
left: leftCondition,
right: rightCondition
});
return JSON.stringify(json);
// Output as dynamic_value
return operationObject;
};
/**
* Generator for 'logical_or' Block
*
* Description:
* Generates a JSON object representing a logical OR operation between two Boolean conditions.
* Outputs true if at least one of the conditions is true.
* Generates a JSON object representing a logical OR operation between two conditions.
*/
Blockly.JSON['logical_or'] = function(block) {
// Retrieve and parse the 'left' Boolean input
const leftCode = Blockly.JSON.valueToCode(block, 'left', Blockly.JSON.ORDER_ATOMIC) || "false";
let left;
try {
left = Blockly.JSON.safeParse(leftCode);
if (typeof left !== 'boolean') {
throw new Error("Left input is not a Boolean.");
}
} catch (e) {
console.warn(`Error parsing 'left' input in logical_or block: ${e.message}. Defaulting to false.`);
left = false;
}
const { left, right } = processTwoValueInput(block);
// Retrieve and parse the 'right' Boolean input
const rightCode = Blockly.JSON.valueToCode(block, 'right', Blockly.JSON.ORDER_ATOMIC) || "false";
let right;
try {
right = Blockly.JSON.safeParse(rightCode);
if (typeof right !== 'boolean') {
throw new Error("Right input is not a Boolean.");
}
} catch (e) {
console.warn(`Error parsing 'right' input in logical_or block: ${e.message}. Defaulting to false.`);
right = false;
}
// Default to false if any condition is null
const leftCondition = typeof left === 'boolean' ? left : false;
const rightCondition = typeof right === 'boolean' ? right : false;
const json = {
type: 'logical_or',
left: left,
right: right
};
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('logical_or', {
left: leftCondition,
right: rightCondition
});
return JSON.stringify(json);
// Output as dynamic_value
return operationObject;
};
/**
* Generator for 'is_false' Block
*
* Description:
* Generates a JSON object to check if a given Boolean condition is false.
* Outputs a Boolean value based on the condition's state.
* Generates a JSON object to check if a given condition is false.
*/
Blockly.JSON['is_false'] = function(block) {
// Retrieve and parse the 'condition' Boolean input
const conditionCode = Blockly.JSON.valueToCode(block, 'condition', Blockly.JSON.ORDER_ATOMIC) || "false";
let condition;
try {
condition = Blockly.JSON.safeParse(conditionCode);
if (typeof condition !== 'boolean') {
throw new Error("Condition input is not a Boolean.");
// Retrieve and parse the 'CONDITION' input
const conditionBlock = block.getInputTargetBlock('CONDITION');
let condition = false;
if (conditionBlock) {
const conditionJson = Blockly.JSON.blockToCode(conditionBlock);
if (typeof conditionJson === 'boolean') {
condition = conditionJson;
} else {
console.error("Invalid condition value in 'is_false' block. Expected a Boolean.");
}
} catch (e) {
console.warn(`Error parsing 'condition' input in is_false block: ${e.message}. Defaulting to false.`);
condition = false;
} else {
console.warn("No condition provided in 'is_false' block. Using default value 'false'.");
}
const json = {
type: 'is_false',
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('is_false', {
condition: condition
};
});
return JSON.stringify(json);
// Output as dynamic_value
return operationObject;
};
/**
* Generator for 'is_true' Block
*
* Generates a JSON object to check if a given condition is true.
*/
Blockly.JSON['is_true'] = function(block) {
// Retrieve and parse the 'CONDITION' input
const conditionBlock = block.getInputTargetBlock('CONDITION');
let condition = false;
if (conditionBlock) {
const conditionJson = Blockly.JSON.blockToCode(conditionBlock);
if (typeof conditionJson === 'boolean') {
condition = conditionJson;
} else {
console.error("Invalid condition value in 'is_true' block. Expected a Boolean.");
}
} else {
console.warn("No condition provided in 'is_true' block. Using default value 'false'.");
}
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('is_true', {
condition: condition
});
// Output as dynamic_value
return operationObject;
};
}

View File

@ -1,254 +1,137 @@
// market_data_generators.js
/**
* Market Data Generators Definitions
*
* This file contains generator functions for each block defined in
* market_data_blocks.js. Each generator translates a Blockly block into a JSON
* object representing market data retrieval operations such as fetching current price,
* bid price, ask price, and specific candle values.
*
* Note: This file assumes that `json_base_generator.js` has been loaded
* before it, which initializes `Blockly.JSON` and attaches helper functions.
* object representing market data retrieval operations.
*/
export function defineMarketDataGenerators() {
/**
* Constants for default values
*/
const DEFAULT_TF = '5m';
const DEFAULT_EX = 'Binance';
const DEFAULT_SYM = 'BTCUSDT';
/**
* Retrieves default source settings from the DOM.
*/
function getDefaultSource() {
const symbolElement = document.getElementById('symbol');
const timeframeElement = document.getElementById('timeframe');
const exchangeElement = document.getElementById('exchange_name');
if (symbolElement && timeframeElement && exchangeElement) {
return {
time_frame: validateTimeFrame(timeframeElement.value) || DEFAULT_TF,
exchange: validateExchange(exchangeElement.value) || DEFAULT_EX,
symbol: validateSymbol(symbolElement.value) || DEFAULT_SYM
};
} else {
console.error('One or more source elements are missing in the DOM.');
return {
time_frame: DEFAULT_TF,
exchange: DEFAULT_EX,
symbol: DEFAULT_SYM
};
}
// Ensure the base generator is initialized and 'createOperationObject' is available
if (!Blockly.JSON || !Blockly.JSON.createOperationObject) {
console.error("Blockly.JSON or createOperationObject not initialized. Please ensure 'json_base_generator.js' is loaded first.");
return;
}
/**
* Validation functions
* Helper Function: processSourceInput
* Processes the 'SOURCE' input and returns its value.
*
* @param {Blockly.Block} block - The block containing the 'SOURCE' input.
* @returns {Object} - The source object or a default empty object.
*/
function validateTimeFrame(tf) {
const validTFs = bt_data && bt_data.intervals ? bt_data.intervals : [];
if (validTFs.includes(tf)) {
return tf;
} else {
console.warn(`Invalid Time Frame "${tf}". Defaulting to "${DEFAULT_TF}".`);
return DEFAULT_TF;
}
}
function processSourceInput(block) {
const sourceBlock = block.getInputTargetBlock('SOURCE');
let source = {};
function validateExchange(exc) {
const validExchanges = window.UI && window.UI.exchanges && window.UI.exchanges.connected_exchanges ? window.UI.exchanges.connected_exchanges : [];
if (validExchanges.includes(exc)) {
return exc;
} else {
console.warn(`Invalid Exchange "${exc}". Defaulting to "${DEFAULT_EX}".`);
return DEFAULT_EX;
}
}
function validateSymbol(sym) {
const validSymbols = bt_data && bt_data.symbols ? bt_data.symbols : [];
if (validSymbols.includes(sym)) {
return sym;
} else {
console.warn(`Invalid Symbol "${sym}". Defaulting to "${DEFAULT_SYM}".`);
return DEFAULT_SYM;
}
}
/**
* Helper function to determine the type of the input.
*/
function determineInputType(parsedInput) {
if (parsedInput === null) return 'invalid';
if (typeof parsedInput === 'object' && !Array.isArray(parsedInput)) {
// Check if it's a source object (time_frame, exchange, symbol)
if ('time_frame' in parsedInput && 'exchange' in parsedInput && 'symbol' in parsedInput) {
return 'source_object';
}
return 'object';
}
if (Array.isArray(parsedInput)) {
return 'list';
}
// Assume primitive types (number, string, etc.)
return 'primitive';
}
/**
* Generic generator function for market data blocks.
* Handles 'current_price', 'bid_price', 'ask_price', and 'last_candle_value'.
*/
function generateMarketData(blockType, additionalFields = {}) {
return function(block) {
// Retrieve the 'source' input
const providedSourceCode = Blockly.JSON.valueToCode(block, 'source', Blockly.JSON.ORDER_ATOMIC) || null;
let definedSource = {};
let json = {};
let excessValues = [];
if (providedSourceCode) {
// Attempt to parse the provided source
const parsedInput = Blockly.JSON.safeParse(providedSourceCode);
const inputType = determineInputType(parsedInput);
switch (inputType) {
case 'source_object':
definedSource = parsedInput;
break;
case 'primitive':
// Assign to 'time_frame' and use defaults for others
definedSource.time_frame = validateTimeFrame(parsedInput) || DEFAULT_TF;
definedSource.exchange = validateExchange(DEFAULT_EX);
definedSource.symbol = validateSymbol(DEFAULT_SYM);
break;
case 'list':
// Assign values in order: [time_frame, exchange, symbol]
definedSource.time_frame = validateTimeFrame(parsedInput[0] || DEFAULT_TF);
definedSource.exchange = validateExchange(parsedInput[1] || DEFAULT_EX);
definedSource.symbol = validateSymbol(parsedInput[2] || DEFAULT_SYM);
if (parsedInput.length > 3) {
excessValues = parsedInput.slice(3);
}
break;
case 'object':
case 'invalid':
default:
console.warn(`Invalid 'source' input in ${blockType} block. Using defaults.`);
definedSource = getDefaultSource();
break;
}
if (sourceBlock) {
const sourceJson = Blockly.JSON.blockToCode(sourceBlock);
if (typeof sourceJson === 'object' && sourceJson !== null) {
source = sourceJson;
} else {
// Define source from interface settings
definedSource = getDefaultSource();
console.error(`Invalid source object in '${block.type}' block. Expected an object.`);
}
} else {
console.warn(`No source connected to 'SOURCE' input of '${block.type}' block. Using default empty object.`);
}
// Additional fields for specific blocks (e.g., candle_part)
json = {
type: blockType,
time_frame: definedSource.time_frame,
exchange: definedSource.exchange,
symbol: definedSource.symbol,
...additionalFields(block)
};
// If there are excess values, package them into a new dynamic_value list
if (excessValues.length > 0) {
// Combine current block's JSON with excess values
const combinedJson = [json, ...excessValues];
return JSON.stringify(combinedJson);
}
return JSON.stringify(json);
};
return source;
}
/**
* Generator for 'current_price' Block
*/
Blockly.JSON['current_price'] = generateMarketData('current_price');
Blockly.JSON['current_price'] = function(block) {
const source = processSourceInput(block);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('current_price', {
source: source
});
// Output as dynamic_value
return operationObject;
};
/**
* Generator for 'bid_price' Block
*/
Blockly.JSON['bid_price'] = generateMarketData('bid_price');
Blockly.JSON['bid_price'] = function(block) {
const source = processSourceInput(block);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('bid_price', {
source: source
});
// Output as dynamic_value
return operationObject;
};
/**
* Generator for 'ask_price' Block
*/
Blockly.JSON['ask_price'] = generateMarketData('ask_price');
Blockly.JSON['ask_price'] = function(block) {
const source = processSourceInput(block);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('ask_price', {
source: source
});
// Output as dynamic_value
return operationObject;
};
/**
* Generator for 'last_candle_value' Block
*/
Blockly.JSON['last_candle_value'] = generateMarketData('last_candle_value', function(block) {
return {
candle_part: block.getFieldValue('candle_part')
};
});
Blockly.JSON['last_candle_value'] = function(block) {
const source = processSourceInput(block);
// Retrieve the 'CANDLE_PART' field
const candlePart = block.getFieldValue('CANDLE_PART') || 'close';
const validCandleParts = ['open', 'high', 'low', 'close', 'volume'];
if (!validCandleParts.includes(candlePart)) {
console.error(`Invalid candle part '${candlePart}' in 'last_candle_value' block. Defaulting to 'close'.`);
}
const validatedCandlePart = validCandleParts.includes(candlePart) ? candlePart : 'close';
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('last_candle_value', {
source: source,
candle_part: validatedCandlePart
});
// Output as dynamic_value
return operationObject;
};
/**
* Generator for 'source' Block
*
* Description:
* Generates a JSON array defining the data source based on Time Frame (TF),
* Exchange (EXC), and Symbol (SYM). Additionally handles the 'NEXT' input for chaining.
* This source can be used with blocks that expect a 'dynamic_value' type input.
* Generates a source object defining the data source based on Time Frame (TF),
* Exchange (EXC), and Symbol (SYM).
*/
Blockly.JSON['source'] = function(block) {
// Retrieve dropdown values
const tf = block.getFieldValue('TF'); // Time Frame
const exc = block.getFieldValue('EXC'); // Exchange
const sym = block.getFieldValue('SYM'); // Symbol
const timeFrame = block.getFieldValue('TF') || '1m'; // Default to '1m' if not set
const exchange = block.getFieldValue('EXC') || 'Binance'; // Default to 'Binance'
const symbol = block.getFieldValue('SYM') || 'BTCUSDT'; // Default to 'BTCUSDT'
// Validate dropdown selections against available data
const validatedTF = validateTimeFrame(tf);
const validatedEx = validateExchange(exc);
const validatedSym = validateSymbol(sym);
// Validate fields if necessary
// Example: Ensure timeFrame matches expected patterns
// Skipping detailed validation for brevity
const sourceList = [
validatedTF,
validatedEx,
validatedSym
];
// Create source object
const sourceObject = {
time_frame: timeFrame,
exchange: exchange,
symbol: symbol
};
// Handle 'NEXT' input for chaining
const nextSourceCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC) || null;
if (nextSourceCode) {
const parsedNextInput = Blockly.JSON.safeParse(nextSourceCode);
const nextInputType = determineInputType(parsedNextInput);
let nextValues = [];
switch (nextInputType) {
case 'source_object':
// If 'NEXT' is a source object, convert it to a list
nextValues = [parsedNextInput.time_frame, parsedNextInput.exchange, parsedNextInput.symbol];
break;
case 'primitive':
// Assign single primitive as 'time_frame'
nextValues = [validateTimeFrame(parsedNextInput)];
break;
case 'list':
// If 'NEXT' is a list, append all its items
nextValues = parsedNextInput;
break;
case 'object':
case 'invalid':
default:
console.warn(`Invalid 'NEXT' input in 'source' block. Ignoring excess values.`);
break;
}
// Append the next values to the source list
if (Array.isArray(nextValues) && nextValues.length > 0) {
sourceList.push(...nextValues);
}
}
return JSON.stringify(sourceList);
// Output as source object
return sourceObject;
};
}

View File

@ -1,169 +1,102 @@
// order_metrics_generators.js
/**
* Order Metrics Generators Definitions
*
* This file contains generator functions for each block defined in
* order_metrics_blocks.js. Each generator translates a Blockly block into a JSON
* object representing order metrics retrieval operations such as fetching order volume,
* filled orders, unfilled orders, and checking order status.
*
* Note: This file assumes that `json_base_generator.js` has been loaded
* before it, which initializes `Blockly.JSON` and attaches helper functions.
* object representing order metrics retrieval operations.
*/
export function defineOrderMetricsGenerators() {
// Ensure the base generator is initialized and 'createOperationObject' is available
if (!Blockly.JSON || !Blockly.JSON.createOperationObject) {
console.error("Blockly.JSON or createOperationObject not initialized. Please ensure 'json_base_generator.js' is loaded first.");
return;
}
/**
* Helper Function: validateOrderType
* Validates the order type and returns a default if invalid.
*
* @param {string} orderType - The order type to validate.
* @returns {string} - Validated order type.
*/
function validateOrderType(orderType) {
const validOrderTypes = ['filled', 'unfilled'];
if (!validOrderTypes.includes(orderType)) {
console.error(`Invalid order type '${orderType}' in 'order_volume' block. Defaulting to 'filled'.`);
return 'filled';
}
return orderType;
}
/**
* Generator for 'order_volume' Block
*
* Description:
* Generates a JSON object representing the retrieval of the cumulative volume
* of filled or unfilled orders based on the specified order type. Supports
* chaining with the 'NEXT' input to facilitate sequences or lists of operations.
*/
Blockly.JSON['order_volume'] = function(block) {
// Retrieve the 'ORDER_TYPE' from the dropdown
const orderType = block.getFieldValue('ORDER_TYPE'); // 'filled' or 'unfilled'
const validatedOrderType = validateOrderType(orderType);
// Validate the order type
const validOrderTypes = ['filled', 'unfilled'];
if (!validOrderTypes.includes(orderType)) {
console.warn(`Invalid ORDER_TYPE "${orderType}" in order_volume block. Defaulting to 'filled'.`);
}
const validatedOrderType = validOrderTypes.includes(orderType) ? orderType : 'filled';
const json = {
type: 'order_volume',
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('order_volume', {
order_type: validatedOrderType
};
});
// Handle 'NEXT' input for chaining
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
if (Array.isArray(nextJson)) {
// If NEXT is a list, concatenate
json.next = [json, ...nextJson];
} else {
// If NEXT is a single value, create a list
json.next = [json, nextJson];
}
} catch (e) {
console.warn(`Failed to parse 'NEXT' input in order_volume block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.next = [json, nextCode];
}
}
return JSON.stringify(json);
// Output as dynamic_value
return operationObject;
};
/**
* Generator for 'filled_orders' Block
*
* Description:
* Generates a JSON object representing the retrieval of the number of filled orders
* for the current strategy. Supports chaining with the 'NEXT' input to
* facilitate sequences or lists of operations.
*/
Blockly.JSON['filled_orders'] = function(block) {
// Since the block no longer specifies a symbol, assume current strategy
const json = {
type: 'filled_orders'
// Additional context like strategy can be handled globally or elsewhere
};
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('filled_orders', {});
// Handle 'NEXT' input for chaining
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
if (Array.isArray(nextJson)) {
// If NEXT is a list, concatenate
json.next = [json, ...nextJson];
} else {
// If NEXT is a single value, create a list
json.next = [json, nextJson];
}
} catch (e) {
console.warn(`Failed to parse 'NEXT' input in filled_orders block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.next = [json, nextCode];
}
}
return JSON.stringify(json);
// Output as dynamic_value
return operationObject;
};
/**
* Generator for 'unfilled_orders' Block
*
* Description:
* Generates a JSON object representing the retrieval of the number of unfilled orders
* for the current strategy. Supports chaining with the 'NEXT' input to
* facilitate sequences or lists of operations.
*/
Blockly.JSON['unfilled_orders'] = function(block) {
// Since the block no longer specifies a symbol, assume current strategy
const json = {
type: 'unfilled_orders'
// Additional context like strategy can be handled globally or elsewhere
};
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('unfilled_orders', {});
// Handle 'NEXT' input for chaining
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
if (Array.isArray(nextJson)) {
// If NEXT is a list, concatenate
json.next = [json, ...nextJson];
} else {
// If NEXT is a single value, create a list
json.next = [json, nextJson];
}
} catch (e) {
console.warn(`Failed to parse 'NEXT' input in unfilled_orders block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.next = [json, nextCode];
}
}
return JSON.stringify(json);
// Output as dynamic_value
return operationObject;
};
/**
* Generator for 'order_status' Block
*
* Description:
* Generates a JSON object to check if a specified order is in a particular status
* (Filled, Unfilled, Partial). Outputs a Boolean value indicating the result.
*/
Blockly.JSON['order_status'] = function(block) {
// Retrieve the 'ORDER_NAME' field input
const orderName = block.getFieldValue('ORDER_NAME');
const orderName = (block.getFieldValue('ORDER_NAME') || 'undefined_order').trim();
// Validate order name
if (!orderName) {
console.warn("Empty 'ORDER_NAME' in 'order_status' block. Using 'undefined_order'.");
}
// Retrieve the selected order status from the dropdown
const orderStatus = block.getFieldValue('order_status'); // 'filled', 'unfilled', 'partial'
// Validate the order name
if (!orderName || orderName.trim() === "") {
console.warn("Empty ORDER_NAME in order_status block. Defaulting to 'undefined_order'.");
}
// Validate the order status
const orderStatus = block.getFieldValue('ORDER_STATUS'); // 'filled', 'unfilled', 'partial'
const validStatuses = ['filled', 'unfilled', 'partial'];
const validatedStatus = validStatuses.includes(orderStatus) ? orderStatus : 'filled';
if (!validStatuses.includes(orderStatus)) {
console.warn(`Invalid order_status "${orderStatus}" in order_status block. Defaulting to 'filled'.`);
console.error(`Invalid order status '${orderStatus}' in 'order_status' block. Defaulting to 'filled'.`);
}
const json = {
type: 'order_status',
order_name: orderName && orderName.trim() !== "" ? orderName : 'undefined_order',
status: validStatuses.includes(orderStatus) ? orderStatus : 'filled'
};
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('order_status', {
order_name: orderName || 'undefined_order',
status: validatedStatus
});
return JSON.stringify(json);
// Output as dynamic_value (since this produces a Boolean value)
return operationObject;
};
}

View File

@ -1,165 +1,108 @@
// risk_management_generators.js
/**
* Risk Management Generators Definitions
*
* This file contains generator functions for each block defined in
* risk_management_blocks.js. Each generator translates a Blockly block into a JSON
* object representing risk management operations such as setting leverage,
* retrieving margin, calculating risk ratio, and setting maximum position size.
*
* Note: This file assumes that `json_base_generator.js` has been loaded
* before it, which initializes `Blockly.JSON` and attaches helper functions.
* object representing risk management operations.
*/
export function defineRiskManagementGenerators() {
// Ensure the base generator is initialized and 'createOperationObject' is available
if (!Blockly.JSON || !Blockly.JSON.createOperationObject) {
console.error("Blockly.JSON or createOperationObject not initialized. Please ensure 'json_base_generator.js' is loaded first.");
return;
}
/**
* Generator for 'set_leverage' Block
* Helper Function: processNumericInput
* Processes a numeric input and ensures it is a valid number.
*
* Description:
* Generates a JSON object representing the action of setting the leverage ratio
* for the trading strategy. Validates the leverage input to ensure it meets
* defined constraints.
* @param {Blockly.Block} block - The block containing the input.
* @param {string} inputName - The name of the input field.
* @param {number} defaultValue - The default value to use if input is invalid.
* @param {number} minValue - The minimum allowable value (inclusive).
* @param {number} maxValue - The maximum allowable value (inclusive).
* @returns {number} - The validated numeric input.
*/
Blockly.JSON['set_leverage'] = function(block) {
// Retrieve and parse the 'LEVERAGE' input
const leverageCode = Blockly.JSON.valueToCode(block, 'LEVERAGE', Blockly.JSON.ORDER_ATOMIC) || "1";
let leverage;
try {
leverage = Blockly.JSON.safeParse(leverageCode);
if (typeof leverage !== 'number') {
throw new Error("Leverage input is not a number.");
function processNumericInput(block, inputName, defaultValue, minValue = Number.NEGATIVE_INFINITY, maxValue = Number.POSITIVE_INFINITY) {
const inputBlock = block.getInputTargetBlock(inputName);
let value = defaultValue;
if (inputBlock) {
const inputJson = Blockly.JSON.blockToCode(inputBlock);
if (typeof inputJson === 'number') {
value = inputJson;
} else {
console.error(`Invalid value for '${inputName}' in '${block.type}' block. Expected a number.`);
}
// Optional: Enforce leverage limits (e.g., 1x to 100x)
if (leverage < 1 || leverage > 100) {
console.warn(`Leverage value ${leverage} is out of bounds. Clamping to [1, 100].`);
leverage = Math.min(Math.max(leverage, 1), 100);
}
} catch (e) {
console.warn(`Error parsing 'LEVERAGE' input in set_leverage block: ${e.message}. Defaulting to 1.`);
leverage = 1;
} else {
console.warn(`No block connected to '${inputName}' input of '${block.type}' block. Using default value ${defaultValue}.`);
}
const json = {
type: 'set_leverage',
// Enforce limits
if (value < minValue) {
console.warn(`Value for '${inputName}' in '${block.type}' block is below minimum (${minValue}). Clamping to ${minValue}.`);
value = minValue;
} else if (value > maxValue) {
console.warn(`Value for '${inputName}' in '${block.type}' block is above maximum (${maxValue}). Clamping to ${maxValue}.`);
value = maxValue;
}
return value;
}
/**
* Generator for 'set_leverage' Block
*/
Blockly.JSON['set_leverage'] = function(block) {
// Process the 'LEVERAGE' input
const leverage = processNumericInput(block, 'LEVERAGE', 1, 1, 100); // Leverage between 1 and 100
// Create operation object
const json = Blockly.JSON.createOperationObject('set_leverage', {
leverage: leverage
};
});
// Handle chaining with 'NEXT' if applicable (optional)
// Since 'set_leverage' has previous and next statements, chaining is handled differently
// Typically, in statement blocks, 'next' is handled by Blockly itself, not manually in the generator
// Therefore, no need to handle 'NEXT' here unless implementing custom chaining logic
return JSON.stringify(json);
// Output as dynamic_value
return json;
};
/**
* Generator for 'current_margin' Block
*
* Description:
* Generates a JSON object representing the retrieval of the current margin balance
* utilized by the trading strategy. Supports chaining with the 'NEXT' input.
*/
Blockly.JSON['current_margin'] = function(block) {
// Retrieve and parse the 'NEXT' input
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('current_margin', {});
const json = {
type: 'current_margin'
};
// Handle 'NEXT' input for chaining
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
if (Array.isArray(nextJson)) {
// If NEXT is a list, concatenate
json.next = [json.type, ...nextJson];
} else {
// If NEXT is a single value, create a list
json.next = [json.type, nextJson];
}
} catch (e) {
console.warn(`Failed to parse 'NEXT' input in current_margin block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.next = [json.type, nextCode];
}
}
return JSON.stringify(json);
// Output as dynamic_value
return operationObject;
};
/**
* Generator for 'risk_ratio' Block
*
* Description:
* Generates a JSON object representing the calculation and retrieval of the current
* risk ratio of the trading strategy. Supports chaining with the 'NEXT' input.
*/
Blockly.JSON['risk_ratio'] = function(block) {
// Retrieve and parse the 'NEXT' input
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('risk_ratio', {});
const json = {
type: 'risk_ratio'
};
// Handle 'NEXT' input for chaining
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
if (Array.isArray(nextJson)) {
// If NEXT is a list, concatenate
json.next = [json.type, ...nextJson];
} else {
// If NEXT is a single value, create a list
json.next = [json.type, nextJson];
}
} catch (e) {
console.warn(`Failed to parse 'NEXT' input in risk_ratio block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.next = [json.type, nextCode];
}
}
return JSON.stringify(json);
// Output as dynamic_value
return operationObject;
};
/**
* Generator for 'max_position_size' Block
*
* Description:
* Generates a JSON object representing the action of setting the maximum position
* size the trading strategy can hold. Validates the maximum size input to ensure
* it meets defined constraints.
*/
Blockly.JSON['max_position_size'] = function(block) {
// Retrieve and parse the 'MAX_SIZE' input
const maxSizeCode = Blockly.JSON.valueToCode(block, 'MAX_SIZE', Blockly.JSON.ORDER_ATOMIC) || "1";
let maxSize;
try {
maxSize = Blockly.JSON.safeParse(maxSizeCode);
if (typeof maxSize !== 'number') {
throw new Error("Max Position Size input is not a number.");
}
// Optional: Enforce maximum position size limits (e.g., min 1)
if (maxSize < 1) {
console.warn(`Max Position Size value ${maxSize} is below minimum. Clamping to 1.`);
maxSize = 1;
}
} catch (e) {
console.warn(`Error parsing 'MAX_SIZE' input in max_position_size block: ${e.message}. Defaulting to 1.`);
maxSize = 1;
}
// Process the 'MAX_SIZE' input
const maxSize = processNumericInput(block, 'MAX_SIZE', 1, 1); // Minimum of 1
const json = {
type: 'max_position_size',
// Create operation object
const json = Blockly.JSON.createOperationObject('max_position_size', {
max_size: maxSize
};
});
// Handle chaining with 'NEXT' if applicable (optional)
// Similar to 'set_leverage', no need to manually handle 'NEXT' in statement blocks
return JSON.stringify(json);
// Output as dynamic_value
return json;
};
}

View File

@ -1,63 +1,49 @@
// time_metrics_generators.js
/**
* Time Metrics Generators Definitions
*
* This file contains generator functions for each block defined in
* time_metrics_blocks.js. Each generator translates a Blockly block into a JSON
* object representing time metrics retrieval operations such as fetching the
* time elapsed since the strategy started.
*
* Note: This file assumes that `json_base_generator.js` has been loaded
* before it, which initializes `Blockly.JSON` and attaches helper functions.
* object representing time metrics retrieval operations.
*/
export function defineTimeMetricsGenerators() {
// Ensure the base generator is initialized and 'createOperationObject' is available
if (!Blockly.JSON || !Blockly.JSON.createOperationObject) {
console.error("Blockly.JSON or createOperationObject not initialized. Please ensure 'json_base_generator.js' is loaded first.");
return;
}
/**
* Helper Function: validateTimeUnit
* Validates the time unit and returns a default if invalid.
*
* @param {string} timeUnit - The time unit to validate.
* @returns {string} - Validated time unit.
*/
function validateTimeUnit(timeUnit) {
const validUnits = ['seconds', 'minutes', 'hours'];
if (!validUnits.includes(timeUnit)) {
console.error(`Invalid time unit '${timeUnit}' in 'time_since_start' block. Defaulting to 'seconds'.`);
return 'seconds';
}
return timeUnit;
}
/**
* Generator for 'time_since_start' Block
*
* Description:
* Generates a JSON object representing the retrieval of the time elapsed since
* the trading strategy started. Users can specify the unit of time (seconds,
* minutes, hours) through the 'TIME_UNIT' dropdown. Supports chaining with
* the 'NEXT' input to facilitate sequences or lists of operations.
*/
Blockly.JSON['time_since_start'] = function(block) {
// Retrieve the selected time unit from the dropdown
const timeUnit = block.getFieldValue('TIME_UNIT'); // 'seconds', 'minutes', 'hours'
const timeUnit = block.getFieldValue('TIME_UNIT') || 'seconds';
const validatedTimeUnit = validateTimeUnit(timeUnit);
// Validate the time unit
const validUnits = ['seconds', 'minutes', 'hours'];
if (!validUnits.includes(timeUnit)) {
console.warn(`Invalid TIME_UNIT "${timeUnit}" in time_since_start block. Defaulting to 'seconds'.`);
}
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('time_since_start', {
unit: validatedTimeUnit
});
const unit = validUnits.includes(timeUnit) ? timeUnit : 'seconds';
const json = {
type: 'time_since_start',
unit: unit
};
// Handle 'NEXT' input for chaining
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
if (Array.isArray(nextJson)) {
// If NEXT is a list, concatenate
json.next = [json, ...nextJson];
} else {
// If NEXT is a single value, create a list
json.next = [json, nextJson];
}
} catch (e) {
console.warn(`Failed to parse 'NEXT' input in time_since_start block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.next = [json, nextCode];
}
}
return JSON.stringify(json);
// Output as dynamic_value
return operationObject;
};
}

View File

@ -1,81 +1,43 @@
// trade_metrics_generators.js
/**
* Trade Metrics Generators Definitions
*
* This file contains generator functions for each block defined in
* trade_metrics_blocks.js. Each generator translates a Blockly block into a JSON
* object representing trade metrics retrieval operations such as fetching active trades,
* total trades, last trade details, average entry price, unrealized profit/loss, and user active trades.
*
* Note: This file assumes that `json_base_generator.js` has been loaded
* before it, which initializes `Blockly.JSON` and attaches helper functions.
* object representing trade metrics retrieval operations.
*/
export function defineTradeMetricsGenerators() {
// Ensure the base generator is initialized and 'createOperationObject' is available
if (!Blockly.JSON || !Blockly.JSON.createOperationObject) {
console.error("Blockly.JSON or createOperationObject not initialized. Please ensure 'json_base_generator.js' is loaded first.");
return;
}
/**
* Generator for 'active_trades' Block
*
* Description:
* Generates a JSON object representing the retrieval of the number of active trades
* for the current strategy. Supports chaining with the 'NEXT' input to facilitate
* sequences or lists of operations.
*/
Blockly.JSON['active_trades'] = function(block) {
// Create JSON for 'active_trades' block
const json = { type: 'active_trades' };
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('active_trades', {});
// Handle 'NEXT' input for chaining
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
json.next = nextJson;
} catch (e) {
console.warn(`Failed to parse 'NEXT' input in active_trades block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.next = nextCode;
}
}
return JSON.stringify(json);
// Output as dynamic_value
return JSON.stringify(operationObject);
};
/**
* Generator for 'total_trades' Block
*
* Description:
* Generates a JSON object representing the retrieval of the total number of trades
* executed by the current strategy. Supports chaining with the 'NEXT' input to
* facilitate sequences or lists of operations.
*/
Blockly.JSON['total_trades'] = function(block) {
// Create JSON for 'total_trades' block
const json = { type: 'total_trades' };
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('total_trades', {});
// Handle 'NEXT' input for chaining
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
json.next = nextJson;
} catch (e) {
console.warn(`Failed to parse 'NEXT' input in total_trades block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.next = nextCode;
}
}
return JSON.stringify(json);
// Output as dynamic_value
return JSON.stringify(operationObject);
};
/**
* Generator for 'last_trade_details' Block
*
* Description:
* Generates a JSON object representing the retrieval of specific details
* (Price, Volume, Direction) of the most recent trade executed by the current strategy.
* Supports chaining with the 'NEXT' input to facilitate sequences or lists of operations.
*/
Blockly.JSON['last_trade_details'] = function(block) {
// Retrieve the 'DETAILS' field input
@ -83,113 +45,47 @@ export function defineTradeMetricsGenerators() {
// Validate the details selection
const validDetails = ['price', 'volume', 'direction'];
if (!validDetails.includes(details)) {
console.warn(`Invalid DETAILS "${details}" in last_trade_details block. Defaulting to 'price'.`);
}
const validatedDetails = validDetails.includes(details) ? details : 'price';
const json = {
type: 'last_trade_details',
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('last_trade_details', {
details: validatedDetails
};
});
// Handle 'NEXT' input for chaining
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
json.next = nextJson;
} catch (e) {
console.warn(`Failed to parse 'NEXT' input in last_trade_details block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.next = nextCode;
}
}
return JSON.stringify(json);
// Output as dynamic_value
return JSON.stringify(operationObject);
};
/**
* Generator for 'average_entry_price' Block
*
* Description:
* Generates a JSON object representing the retrieval of the average entry price
* of all open positions within the current strategy. Supports chaining with the 'NEXT' input
* to facilitate sequences or lists of operations.
*/
Blockly.JSON['average_entry_price'] = function(block) {
// Create JSON for 'average_entry_price' block
const json = { type: 'average_entry_price' };
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('average_entry_price', {});
// Handle 'NEXT' input for chaining
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
json.next = nextJson;
} catch (e) {
console.warn(`Failed to parse 'NEXT' input in average_entry_price block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.next = nextCode;
}
}
return JSON.stringify(json);
// Output as dynamic_value
return JSON.stringify(operationObject);
};
/**
* Generator for 'unrealized_profit_loss' Block
*
* Description:
* Generates a JSON object representing the retrieval of the total unrealized profit
* or loss across all current positions of the current strategy. Supports chaining with the 'NEXT' input
* to facilitate sequences or lists of operations.
*/
Blockly.JSON['unrealized_profit_loss'] = function(block) {
// Create JSON for 'unrealized_profit_loss' block
const json = { type: 'unrealized_profit_loss' };
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('unrealized_profit_loss', {});
// Handle 'NEXT' input for chaining
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
json.next = nextJson;
} catch (e) {
console.warn(`Failed to parse 'NEXT' input in unrealized_profit_loss block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.next = nextCode;
}
}
return JSON.stringify(json);
// Output as dynamic_value
return JSON.stringify(operationObject);
};
/**
* Generator for 'user_active_trades' Block
*
* Description:
* Generates a JSON object representing the retrieval of the number of active trades
* across all strategies and symbols for the user. This provides an aggregate view
* of the user's active trading positions. Supports chaining with the 'NEXT' input.
*/
Blockly.JSON['user_active_trades'] = function(block) {
// Create JSON for 'user_active_trades' block
const json = { type: 'user_active_trades' };
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('user_active_trades', {});
// Handle 'NEXT' input for chaining
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
json.next = nextJson;
} catch (e) {
console.warn(`Failed to parse 'NEXT' input in user_active_trades block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.next = nextCode;
}
}
return JSON.stringify(json);
// Output as dynamic_value
return JSON.stringify(operationObject);
};
}

View File

@ -1,351 +1,291 @@
// trade_order_generators.js
/**
* Trade Order Generators Definitions
*
* This file contains generator functions for each block defined in
* trade_order_blocks.js. Each generator translates a Blockly block into a JSON
* object representing trade order operations such as executing trade actions,
* setting time in force, stop loss, take profit, limit prices, trailing stops,
* defining target markets, and naming orders.
*
* Note: This file assumes that `json_base_generator.js` has been loaded
* before it, which initializes `Blockly.JSON` and attaches helper functions.
* object representing trade order operations.
*/
export function defineTradeOrderGenerators() {
// Ensure the base generator is initialized and 'createOperationObject' is available
if (!Blockly.JSON || !Blockly.JSON.createOperationObject) {
console.error("Blockly.JSON or createOperationObject not initialized. Please ensure 'json_base_generator.js' is loaded first.");
return;
}
/**
* Generator for 'trade_action' Block
*
* Description:
* Generates a JSON object representing the execution of a trade action
* (Buy/Sell), specifying the amount and optional trade options.
* Does NOT support chaining via 'NEXT'; instead, it integrates within
* Blockly's statement sequencing.
*/
Blockly.JSON['trade_action'] = function(block) {
// Retrieve the trade type (Buy/Sell)
const tradeType = block.getFieldValue('tradeType'); // 'buy' or 'sell'
const validTradeTypes = ['buy', 'sell'];
const type = validTradeTypes.includes(tradeType) ? tradeType : 'buy';
// Retrieve and parse the trade size
const sizeCode = Blockly.JSON.valueToCode(block, 'size', Blockly.JSON.ORDER_ATOMIC) || "0";
let size;
try {
size = Blockly.JSON.safeParse(sizeCode);
if (typeof size !== 'number') {
throw new Error("Size input is not a number.");
Blockly.JSON['trade_action'] = function(block, argsCount = null, maxDepth = 10) {
/**
* Helper Function: collectTradeOptions
* Recursively collects all connected trade_options blocks.
*
* @param {Blockly.Block} block - The first trade_options block.
* @returns {Array} - Array of trade_options JSON objects.
*/
function collectTradeOptions(block) {
let options = [];
while (block) {
const optionJson = Blockly.JSON._blockToJson(block);
if (optionJson) {
delete optionJson.next;
options.push(optionJson);
}
block = block.getNextBlock();
}
if (size <= 0) {
console.warn(`Trade size ${size} is not positive. Defaulting to 1.`);
size = 1;
return options;
};
/**
* Helper Function: validateTradeType
* Validates the trade type and returns a default if invalid.
*
* @param {string} tradeType - The trade type to validate.
* @returns {string} - Validated trade type.
*/
function validateTradeType(tradeType) {
const validTradeTypes = ['buy', 'sell'];
if (!validTradeTypes.includes(tradeType)) {
console.error(`Invalid trade type '${tradeType}' in 'trade_action' block. Defaulting to 'buy'.`);
return 'buy';
}
} catch (e) {
console.warn(`Error parsing 'size' input in trade_action block: ${e.message}. Defaulting to 1.`);
size = 1;
return tradeType;
}
// Initialize the JSON object
// Retrieve and validate the trade type
const tradeType = block.getFieldValue('tradeType') || 'buy';
const validatedTradeType = validateTradeType(tradeType);
// Retrieve and process the trade size
const size = Blockly.JSON.processNumericInput(block, 'size', 1); // Default size is 1
// **Assign the processed 'size' to 'inputs.size'**
const json = {
type: 'trade_action',
trade_type: type,
size: size
trade_type: validatedTradeType,
inputs: {
size: size
}
};
// Handle 'trade_options' input (statement)
const optionsJson = Blockly.JSON.statementToCode(block, 'trade_options');
if (optionsJson) {
try {
// Assume 'trade_options' generates a JSON object or array
const parsedOptions = JSON.parse(optionsJson);
json.trade_options = parsedOptions;
} catch (e) {
console.warn(`Failed to parse 'trade_options' in trade_action block: ${e.message}`);
// Optionally, handle the error or set trade_options to null/empty
json.trade_options = null;
// **Process 'trade_options' statement input within the generator**
const tradeOptionsFirstBlock = block.getInputTargetBlock('trade_options');
if (tradeOptionsFirstBlock) {
const allTradeOptions = collectTradeOptions(tradeOptionsFirstBlock);
if (allTradeOptions && allTradeOptions.length > 0) {
json.trade_options = allTradeOptions;
}
}
// Handle previous and next statements via Blockly's statement connections
// No need to manually handle 'NEXT' input; Blockly manages the sequencing
// **Set the skipAdditionalParsing flag**
json.skipAdditionalParsing = true;
return JSON.stringify(json);
console.log(`Generated JSON for 'trade_action' block:`, json);
return json;
};
/**
* Generator for 'time_in_force' Block
*
* Description:
* Generates a JSON object representing the setting of the Time in Force parameter
* for an order. Integrates within Blockly's statement sequencing.
*/
Blockly.JSON['time_in_force'] = function(block) {
// Retrieve the Time in Force value
const tif = block.getFieldValue('tif'); // 'gtc', 'fok', 'ioc'
// Retrieve the Time in Force value from the dropdown
const tif = block.getFieldValue('tif') || 'gtc';
const validTIF = ['gtc', 'fok', 'ioc'];
const time_in_force = validTIF.includes(tif) ? tif : 'gtc';
const validatedTIF = validTIF.includes(tif) ? tif : 'gtc';
if (!validTIF.includes(tif)) {
console.error(`Invalid Time in Force '${tif}' in 'time_in_force' block. Defaulting to 'gtc'.`);
}
// Create operation object
const json = {
type: 'time_in_force',
time_in_force: time_in_force
inputs: {
time_in_force: validatedTIF
}
};
// Handle previous and next statements via Blockly's statement connections
// No need to handle 'NEXT' input; Blockly manages the sequencing
// No need to handle 'NEXT' input; sequencing is managed by _blockToJson
return JSON.stringify(json);
return json;
};
/**
* Generator for 'stop_loss' Block
*
* Description:
* Generates a JSON object representing the setting of a Stop Loss parameter
* for a trade. Integrates within Blockly's statement sequencing.
*/
Blockly.JSON['stop_loss'] = function(block) {
// Retrieve and parse the stop loss input
const stopLossCode = Blockly.JSON.valueToCode(block, 'stop_loss_input', Blockly.JSON.ORDER_ATOMIC) || "0";
let stop_loss;
try {
stop_loss = Blockly.JSON.safeParse(stopLossCode);
if (typeof stop_loss !== 'number') {
throw new Error("Stop Loss input is not a number.");
}
if (stop_loss <= 0) {
console.warn(`Stop Loss value ${stop_loss} is not positive. Defaulting to 1.`);
stop_loss = 1;
}
} catch (e) {
console.warn(`Error parsing 'stop_loss_input' in stop_loss block: ${e.message}. Defaulting to 1.`);
stop_loss = 1;
// Retrieve and process the 'stop_loss_input' numeric input
const stop_loss = Blockly.JSON.processNumericInput(block, 'stop_loss_input', 0);
// Validate stop_loss
if (stop_loss <= 0) {
console.warn(`Invalid stop_loss value '${stop_loss}' in 'stop_loss' block. Using default value 0.`);
}
// Create operation object
const json = {
type: 'stop_loss',
stop_loss: stop_loss
inputs: {
stop_loss: stop_loss
}
};
// Handle previous and next statements via Blockly's statement connections
// No need to handle 'NEXT' input; Blockly manages the sequencing
// No need to handle 'NEXT' input; sequencing is managed by _blockToJson
return JSON.stringify(json);
};
/**
* Generator for 'trailing_stop' Block
*
* Description:
* Generates a JSON object representing the setting of a Trailing Stop parameter
* for a trade. Integrates within Blockly's statement sequencing.
*/
Blockly.JSON['trailing_stop'] = function(block) {
// Retrieve and parse the trailing stop distance
const trailDistanceCode = Blockly.JSON.valueToCode(block, 'trail_distance', Blockly.JSON.ORDER_ATOMIC) || "0";
let trail_distance;
try {
trail_distance = Blockly.JSON.safeParse(trailDistanceCode);
if (typeof trail_distance !== 'number') {
throw new Error("Trailing Stop distance is not a number.");
}
if (trail_distance <= 0) {
console.warn(`Trailing Stop distance ${trail_distance} is not positive. Defaulting to 1.`);
trail_distance = 1;
}
} catch (e) {
console.warn(`Error parsing 'trail_distance' in trailing_stop block: ${e.message}. Defaulting to 1.`);
trail_distance = 1;
}
const json = {
type: 'trailing_stop',
trail_distance: trail_distance
};
// Handle previous and next statements via Blockly's statement connections
// No need to handle 'NEXT' input; Blockly manages the sequencing
return JSON.stringify(json);
return json;
};
/**
* Generator for 'take_profit' Block
*
* Description:
* Generates a JSON object representing the setting of a Take Profit parameter
* for a trade. Integrates within Blockly's statement sequencing.
*/
Blockly.JSON['take_profit'] = function(block) {
// Retrieve and parse the take profit input
const takeProfitCode = Blockly.JSON.valueToCode(block, 'take_profit_input', Blockly.JSON.ORDER_ATOMIC) || "0";
let take_profit;
try {
take_profit = Blockly.JSON.safeParse(takeProfitCode);
if (typeof take_profit !== 'number') {
throw new Error("Take Profit input is not a number.");
}
if (take_profit <= 0) {
console.warn(`Take Profit value ${take_profit} is not positive. Defaulting to 1.`);
take_profit = 1;
}
} catch (e) {
console.warn(`Error parsing 'take_profit_input' in take_profit block: ${e.message}. Defaulting to 1.`);
take_profit = 1;
// Retrieve and process the 'take_profit_input' numeric input
const take_profit = Blockly.JSON.processNumericInput(block, 'take_profit_input', 0);
// Validate take_profit
if (take_profit <= 0) {
console.warn(`Invalid take_profit value '${take_profit}' in 'take_profit' block. Using default value 0.`);
}
// Create operation object
const json = {
type: 'take_profit',
take_profit: take_profit
inputs: {
take_profit: take_profit
}
};
// Handle previous and next statements via Blockly's statement connections
// No need to handle 'NEXT' input; Blockly manages the sequencing
// No need to handle 'NEXT' input; sequencing is managed by _blockToJson
return JSON.stringify(json);
return json;
};
/**
* Generator for 'limit' Block
*
* Description:
* Generates a JSON object representing the setting of a Limit price for a trade order.
* Integrates within Blockly's statement sequencing.
*/
Blockly.JSON['limit'] = function(block) {
// Retrieve and parse the limit input
const limitCode = Blockly.JSON.valueToCode(block, 'limit_input', Blockly.JSON.ORDER_ATOMIC) || "0";
let limit;
try {
limit = Blockly.JSON.safeParse(limitCode);
if (typeof limit !== 'number') {
throw new Error("Limit input is not a number.");
}
if (limit <= 0) {
console.warn(`Limit value ${limit} is not positive. Defaulting to 1.`);
limit = 1;
}
} catch (e) {
console.warn(`Error parsing 'limit_input' in limit block: ${e.message}. Defaulting to 1.`);
limit = 1;
// Retrieve and process the 'limit_input' numeric input
const limit = Blockly.JSON.processNumericInput(block, 'limit_input', 0);
// Validate limit
if (limit <= 0) {
console.warn(`Invalid limit value '${limit}' in 'limit' block. Using default value 0.`);
}
// Create operation object
const json = {
type: 'limit',
limit: limit
inputs: {
limit: limit
}
};
// Handle previous and next statements via Blockly's statement connections
// No need to handle 'NEXT' input; Blockly manages the sequencing
// No need to handle 'NEXT' input; sequencing is managed by _blockToJson
return JSON.stringify(json);
return json;
};
/**
* Generator for 'trailing_stop' Block
*/
Blockly.JSON['trailing_stop'] = function(block) {
// Retrieve and process the 'trail_distance' numeric input
const trail_distance = Blockly.JSON.processNumericInput(block, 'trail_distance', 0);
// Validate trail_distance
if (trail_distance <= 0) {
console.warn(`Invalid trail_distance value '${trail_distance}' in 'trailing_stop' block. Using default value 0.`);
}
// Create operation object
const json = {
type: 'trailing_stop',
inputs: {
trail_distance: trail_distance
}
};
// No need to handle 'NEXT' input; sequencing is managed by _blockToJson
return json;
};
/**
* Generator for 'trailing_limit' Block
*
* Description:
* Generates a JSON object representing the setting of a Trailing Limit parameter
* for a trade order. Integrates within Blockly's statement sequencing.
*/
Blockly.JSON['trailing_limit'] = function(block) {
// Retrieve and parse the trailing limit distance
const trailLimitDistanceCode = Blockly.JSON.valueToCode(block, 'trail_limit_distance', Blockly.JSON.ORDER_ATOMIC) || "0";
let trail_limit_distance;
try {
trail_limit_distance = Blockly.JSON.safeParse(trailLimitDistanceCode);
if (typeof trail_limit_distance !== 'number') {
throw new Error("Trailing Limit distance is not a number.");
}
if (trail_limit_distance <= 0) {
console.warn(`Trailing Limit distance ${trail_limit_distance} is not positive. Defaulting to 1.`);
trail_limit_distance = 1;
}
} catch (e) {
console.warn(`Error parsing 'trail_limit_distance' in trailing_limit block: ${e.message}. Defaulting to 1.`);
trail_limit_distance = 1;
// Retrieve and process the 'trail_limit_distance' numeric input
const trail_limit_distance = Blockly.JSON.processNumericInput(block, 'trail_limit_distance', 0);
// Validate trail_limit_distance
if (trail_limit_distance <= 0) {
console.warn(`Invalid trail_limit_distance value '${trail_limit_distance}' in 'trailing_limit' block. Using default value 0.`);
}
// Create operation object
const json = {
type: 'trailing_limit',
trail_limit_distance: trail_limit_distance
inputs: {
trail_limit_distance: trail_limit_distance
}
};
// Handle previous and next statements via Blockly's statement connections
// No need to handle 'NEXT' input; Blockly manages the sequencing
// No need to handle 'NEXT' input; sequencing is managed by _blockToJson
return JSON.stringify(json);
return json;
};
/**
* Generator for 'target_market' Block
*
* Description:
* Generates a JSON object representing the specification of the target market
* for executing trades, including Time Frame, Exchange, and Symbol.
* Integrates within Blockly's statement sequencing.
*/
Blockly.JSON['target_market'] = function(block) {
// Retrieve the Time Frame, Exchange, and Symbol selections
const tf = block.getFieldValue('TF'); // Time Frame
const exc = block.getFieldValue('EXC'); // Exchange
const sym = block.getFieldValue('SYM'); // Symbol
const timeFrame = block.getFieldValue('TF') || '1m'; // Default to '1m'
const exchange = block.getFieldValue('EXC') || 'Binance'; // Default to 'Binance'
const symbol = block.getFieldValue('SYM') || 'BTCUSDT'; // Default to 'BTCUSDT'
// Validate selections
const timeFrameOption = timeframeOptions.find(option => option[1] === tf);
if (!timeFrameOption) {
console.warn(`Invalid Time Frame "${tf}" in target_market block. Defaulting to first available option.`);
}
const time_frame = timeFrameOption ? tf : timeframeOptions[0][1];
const exchangeOption = exchangeOptions.find(option => option[1] === exc);
if (!exchangeOption) {
console.warn(`Invalid Exchange "${exc}" in target_market block. Defaulting to first available option.`);
}
const exchange = exchangeOption ? exc : exchangeOptions[0][1];
const symbolOption = symbolOptions.find(option => option[1] === sym);
if (!symbolOption) {
console.warn(`Invalid Symbol "${sym}" in target_market block. Defaulting to first available option.`);
}
const symbol = symbolOption ? sym : symbolOptions[0][1];
// Validate fields if necessary
// Example: Ensure timeFrame matches expected patterns
// Skipping detailed validation for brevity
// Create operation object
const json = {
type: 'target_market',
time_frame: time_frame,
exchange: exchange,
symbol: symbol
inputs: {
time_frame: timeFrame,
exchange: exchange,
symbol: symbol
}
};
// Handle previous and next statements via Blockly's statement connections
// No need to handle 'NEXT' input; Blockly manages the sequencing
// No need to handle 'NEXT' input; sequencing is managed by _blockToJson
return JSON.stringify(json);
return json;
};
/**
* Generator for 'name_order' Block
*
* Description:
* Generates a JSON object representing the assignment of a custom name to the current order.
* Integrates within Blockly's statement sequencing.
*/
Blockly.JSON['name_order'] = function(block) {
// Retrieve the order name from the input field
const orderName = block.getFieldValue('order_name').trim();
const orderName = (block.getFieldValue('order_name') || 'Unnamed Order').trim();
// Validate order name
if (!orderName) {
console.warn("Empty 'order_name' in name_order block. Defaulting to 'Unnamed Order'.");
console.warn("Empty 'order_name' in 'name_order' block. Using 'Unnamed Order'.");
}
// Create operation object
const json = {
type: 'name_order',
order_name: orderName || 'Unnamed Order'
inputs: {
order_name: orderName || 'Unnamed Order'
}
};
// Handle previous and next statements via Blockly's statement connections
// No need to handle 'NEXT' input; Blockly manages the sequencing
// No need to handle 'NEXT' input; sequencing is managed by _blockToJson
return JSON.stringify(json);
return json;
};
}

View File

@ -33,178 +33,211 @@ export function defineVAFGenerators() {
type: 'notify_user',
message: message && message.trim() !== "" ? message : 'No message provided.'
};
// Handle 'NEXT' input if connected (optional)
// Since 'notify_user' block does not have a 'NEXT' input, this section is optional
/*
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
json.next = nextJson;
} catch (e) {
console.warn(`Failed to parse NEXT input in notify_user block: ${e.message}`);
}
}
*/
return JSON.stringify(json);
return json;
};
/**
* Generator for 'value_input' Block
*
* Description:
* Generates a JSON object representing a numerical value input.
* Supports chaining by handling the 'NEXT' input if connected.
* Generates a JSON object representing a list of numerical values or nested objects.
* Handles 'VALUE' and 'NEXT' inputs separately to support dynamic block chaining.
*
* @param {Block} block - The current block to generate JSON for.
* @returns {Object} The generated JSON object.
*/
Blockly.JSON['value_input'] = function(block) {
// Retrieve the 'VALUE' field input and parse it as a float
const valueStr = block.getFieldValue('VALUE');
const value = parseFloat(valueStr);
const values = []; // Initialize the values array
if (isNaN(value)) {
console.warn(`Invalid VALUE "${valueStr}" in value_input block. Defaulting to 0.`);
/**
* Recursively processes each block in the chain.
*
* @param {Block} currentBlock - The current block to process.
* @param {number} currentDepth - The current depth in the recursion.
*/
function processBlock(currentBlock, currentDepth) {
if (!currentBlock || currentDepth > 10) { // Enforce max depth and skip if block is missing
if (currentDepth > 10) {
console.warn("Max depth exceeded in value_input chain. Stopping aggregation.");
}
return;
}
// Process 'VALUE' and add to values
const valueJson = processConnectedBlock(currentBlock, 'VALUE');
if (valueJson !== null) values.push(valueJson);
// Process 'NEXT' block in the chain
const nextBlock = currentBlock.getInputTargetBlock('NEXT');
if (nextBlock) processBlock(nextBlock, currentDepth + 1);
}
/**
* Processes a connected block and retrieves its generated JSON or field value.
*
* @param {Block} block - The current block to process.
* @param {string} inputName - The input name to retrieve.
* @returns {*} The processed value or null if invalid.
*/
function processConnectedBlock(block, inputName) {
const connectedBlock = block.getInputTargetBlock(inputName);
if (connectedBlock) {
const generator = Blockly.JSON[connectedBlock.type];
if (typeof generator === 'function') {
const generatedValue = generator(connectedBlock);
return generatedValue.type === 'dynamic_value' && Array.isArray(generatedValue.values)
? generatedValue.values
: generatedValue;
}
console.warn(`No generator found for block type "${connectedBlock.type}". Skipping this block.`);
return null;
}
// If no connected block, parse field value as float
const fieldValue = parseFloat(block.getFieldValue(inputName) || NaN);
if (isNaN(fieldValue)) {
console.warn(`Invalid VALUE "${block.getFieldValue(inputName)}" in value_input block. Skipping this block.`);
return null;
}
return fieldValue;
}
processBlock(block, 1); // Start processing from the initial block
const json = {
type: 'value_input',
value: isNaN(value) ? 0 : value
type: 'dynamic_value',
values: values.flat(), // Flatten values array to handle nested arrays
skipAdditionalParsing: true // Flag to skip further parsing
};
// Handle 'NEXT' input for chaining
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
if (Array.isArray(nextJson)) {
// If NEXT is a list, concatenate
json.values = [json.value, ...nextJson];
} else {
// If NEXT is a single value, create a list
json.values = [json.value, nextJson];
}
} catch (e) {
console.warn(`Failed to parse NEXT input in value_input block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.values = [json.value, nextCode];
}
} else {
// If no NEXT, wrap value in a list for consistency
json.values = [json.value];
}
// Remove 'value' if 'values' exists to maintain consistency
if (json.values) {
delete json.value;
}
return JSON.stringify(json);
console.log(`Generated JSON for 'value_input' block:`, json);
return json;
};
/**
* Generator for 'get_variable' Block
*
* Description:
* Generates a JSON object to retrieve the value of a specified variable.
* Supports chaining with the 'NEXT' input to facilitate lists or sequences.
* Generates a JSON object representing a list of variable names.
* Handles 'NEXT' inputs to support dynamic block chaining.
*
* @param {Block} block - The current block to generate JSON for.
* @param {number|null} argsCount - Number of required variable names.
* null: Aggregate all connected blocks.
* n (positive integer): Aggregate up to 'n' variable names.
* @param {number} maxDepth - A limit on the recursion depth to prevent infinite loops.
* @returns {Object} The generated JSON object.
*/
Blockly.JSON['get_variable'] = function(block) {
// Retrieve the 'variable_name' field input
const variableName = block.getFieldValue('variable_name');
Blockly.JSON['get_variable'] = function(block, argsCount = null, maxDepth = 10) {
// Initialize the variables array
const variables = [];
// Validate the variable name
if (!variableName || variableName.trim() === "") {
console.warn("Empty variable_name in get_variable block. Defaulting to 'undefined_var'.");
}
const json = {
type: 'get_variable',
variable_name: variableName && variableName.trim() !== "" ? variableName : 'undefined_var'
};
// Handle 'NEXT' input for chaining
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
if (Array.isArray(nextJson)) {
// If NEXT is a list, concatenate
json.variables = [json.variable_name, ...nextJson];
} else {
// If NEXT is a single value, create a list
json.variables = [json.variable_name, nextJson];
}
} catch (e) {
console.warn(`Failed to parse NEXT input in get_variable block: ${e.message}`);
// If parsing fails, treat NEXT as a single value
json.variables = [json.variable_name, nextCode];
/**
* Recursively processes each block in the chain.
*
* @param {Block} currentBlock - The current block to process.
* @param {number} currentDepth - The current depth in the recursion.
*/
function processBlock(currentBlock, currentDepth) {
if (!currentBlock) {
return;
}
if (currentDepth > maxDepth) {
console.warn("Max depth exceeded in get_variable chain. Stopping aggregation.");
return;
}
// Retrieve the 'variable_name' field input
const variableName = currentBlock.getFieldValue('variable_name');
const trimmedName = variableName && variableName.trim() !== "" ? variableName.trim() : 'undefined_var';
if (variableName.trim() === "") {
console.warn("Empty variable_name in get_variable block. Defaulting to 'undefined_var'.");
}
variables.push({ 'variable': trimmedName });
// Process the 'NEXT' connection
const nextBlock = currentBlock.getInputTargetBlock('NEXT');
if (nextBlock) {
const generator = Blockly.JSON[nextBlock.type];
if (typeof generator === 'function') {
const generatedValue = generator(nextBlock, null, maxDepth - currentDepth);
if (generatedValue.type === 'dynamic_value' && Array.isArray(generatedValue.values)) {
variables.push(...generatedValue.values);
} else {
variables.push(generatedValue);
}
} else {
console.warn(`No generator found for block type "${nextBlock.type}". Skipping this block.`);
}
}
} else {
// If no NEXT, wrap variable name in a list for consistency
json.variables = [json.variable_name];
}
// Remove 'variable_name' if 'variables' exists to maintain consistency
if (json.variables) {
delete json.variable_name;
}
// Start processing from the initial block
processBlock(block, 1);
return JSON.stringify(json);
// Construct the final JSON object as a dynamic_value
return {
type: 'dynamic_value',
values: variables
};
};
/**
* Generator for 'set_variable' Block
*
* Description:
* Generates a JSON object to assign a specific value to a variable.
* Generates a JSON object to assign a specific value or list of values to a variable.
* Supports numerical, string, or dynamic values, enabling versatile usage.
*/
Blockly.JSON['set_variable'] = function(block) {
// Retrieve the 'VAR_NAME' field input
const varName = block.getFieldValue('VAR_NAME');
Blockly.JSON['set_variable'] = function(block, argsCount = null, maxDepth = 10) {
// Retrieve and validate the 'VAR_NAME' field input
const varName = block.getFieldValue('VAR_NAME')?.trim() || 'undefined_var';
// Validate the variable name
if (!varName || varName.trim() === "") {
if (varName === 'undefined_var') {
console.warn("Empty VAR_NAME in set_variable block. Defaulting to 'undefined_var'.");
}
// Retrieve the 'VALUE' input
const valueCode = Blockly.JSON.valueToCode(block, 'VALUE', Blockly.JSON.ORDER_ATOMIC) || "null";
let value;
// Retrieve the connected block via 'VALUE' input
const valueBlock = block.getInputTargetBlock('VALUE');
let values = [];
try {
// Attempt to parse the VALUE input
value = Blockly.JSON.safeParse(valueCode);
if (value === undefined) {
// If parsing returns undefined, treat it as null
value = null;
console.warn(`VALUE input in set_variable block could not be parsed. Defaulting to null.`);
if (valueBlock) {
const generator = Blockly.JSON[valueBlock.type];
if (typeof generator === 'function') {
// Invoke the generator for the connected block
const generatedValue = generator(valueBlock, null, maxDepth - 1);
// Handle different types of generated values
if (generatedValue.type === 'dynamic_value' && Array.isArray(generatedValue.values)) {
// Assign the entire list
values = generatedValue.values;
} else if (typeof generatedValue === 'object') {
// If it's an object, handle accordingly (customize as needed)
values.push(generatedValue);
} else {
// For primitive types (number, string, etc.)
values.push(generatedValue);
}
} else {
console.warn(`No generator found for block type "${valueBlock.type}". Skipping this block.`);
}
} catch (e) {
console.warn(`Error parsing VALUE input in set_variable block: ${e.message}. Defaulting to null.`);
value = null;
} else {
console.warn("No connected block to 'VALUE' input in set_variable block. Defaulting to empty list.");
}
// Construct the JSON object for 'set_variable'
const json = {
type: 'set_variable',
variable_name: varName && varName.trim() !== "" ? varName : 'undefined_var',
value: value
variable_name: varName,
values: values
};
// Handle 'NEXT' input if connected (optional)
// Since 'set_variable' block does not have a 'NEXT' input, this section is optional
/*
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
json.next = nextJson;
} catch (e) {
console.warn(`Failed to parse NEXT input in set_variable block: ${e.message}`);
}
}
*/
return JSON.stringify(json);
return json;
};
/**
@ -237,22 +270,7 @@ export function defineVAFGenerators() {
flag_name: flagName && flagName.trim() !== "" ? flagName : 'undefined_flag',
flag_value: validFlagValues.includes(flagValue) ? (flagValue === 'TRUE') : false
};
// Handle 'NEXT' input if connected (optional)
// Since 'set_flag' block does not have a 'NEXT' input, this section is optional
/*
const nextCode = Blockly.JSON.valueToCode(block, 'NEXT', Blockly.JSON.ORDER_ATOMIC);
if (nextCode) {
try {
const nextJson = JSON.parse(nextCode);
json.next = nextJson;
} catch (e) {
console.warn(`Failed to parse NEXT input in set_flag block: ${e.message}`);
}
}
*/
return JSON.stringify(json);
return json;
};
/**
@ -286,6 +304,6 @@ export function defineVAFGenerators() {
flag_value: validFlagValues.includes(flagValue) ? (flagValue === 'TRUE') : true
};
return JSON.stringify(json);
return json;
};
}

View File

@ -0,0 +1,66 @@
// client/indicator_blocks.js
// Define Blockly blocks and their JSON generators dynamically based on indicators
export function defineIndicatorBlocks() {
// Ensure Blockly.JSON is available
if (!Blockly.JSON) {
console.error('Blockly.JSON is not defined. Ensure json_generators.js is loaded before indicator_blocks.js.');
return;
}
// Retrieve the indicator outputs configuration
const indicatorOutputs = window.UI.indicators.getIndicatorOutputs();
const toolboxCategory = document.querySelector('#toolbox_advanced category[name="Indicators"]');
if (!toolboxCategory) {
console.error('Indicators category not found in the toolbox.');
return;
}
for (let indicatorName in indicatorOutputs) {
const outputs = indicatorOutputs[indicatorName];
// Create a unique block type for each indicator
const blockType = 'indicator_' + indicatorName;
// Define the block for this indicator
Blockly.defineBlocksWithJsonArray([{
"type": blockType,
"message0": `${indicatorName} Output %1`,
"args0": [
{
"type": "field_dropdown",
"name": "OUTPUT",
"options": outputs.map(output => [output, output])
}
],
"output": "dynamic_value",
"colour": 230,
"tooltip": `Select the ${indicatorName} output`,
"helpUrl": ""
}]);
// Define the JSON generator for this block
Blockly.JSON[blockType] = function(block) {
const selectedOutput = block.getFieldValue('OUTPUT');
const json = {
type: 'indicator',
fields: {
NAME: indicatorName,
OUTPUT: selectedOutput
},
inputs: {},
statements: {}
};
return json;
};
// Append the newly created block to the Indicators category in the toolbox
const blockElement = document.createElement('block');
blockElement.setAttribute('type', blockType);
toolboxCategory.appendChild(blockElement);
}
console.log('Indicator blocks and their JSON generators have been defined and inserted into the toolbox.');
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB