The generator definitions seem to work now.

This commit is contained in:
Rob 2024-11-04 18:20:09 -04:00
parent bb7fdbf8f9
commit 50bcfa05cd
21 changed files with 1358 additions and 563 deletions

View File

@ -1,27 +1,222 @@
"""
{
"type": "strategy",
"statements": [
{
"type": "trade_action",
"trade_type": "buy",
"inputs": {
"size": {
"type": "sqrt",
"inputs": {
"number": {
"type": "power",
"inputs": {
"base": 2,
"exponent": 3
"name": "fff",
"strategy_json": {
"type": "strategy",
"statements": [
{
"type": "execute_if",
"inputs": {
"CONDITION": {
"type": "comparison",
"operator": "<",
"inputs": {
"LEFT": {
"type": "risk_ratio",
"inputs": {}
},
"RIGHT": {
"type": "dynamic_value",
"values": [
{
"type": "current_balance",
"inputs": {}
}
]
}
}
}
},
"statements": {
"DO": [
{
"type": "max_position_size",
"inputs": {
"MAX_SIZE": {
"type": "available_balance",
"inputs": {}
}
},
"next": {
"type": "execute_if",
"inputs": {
"CONDITION": {
"type": "is_false",
"inputs": {
"condition": {
"type": "order_status",
"inputs": {
"order_name": "order_name",
"status": "filled"
}
}
}
}
},
"statements": {
"DO": [
{
"type": "execute_if",
"inputs": {
"CONDITION": {
"type": "comparison",
"operator": ">",
"inputs": {
"LEFT": {
"type": "dynamic_value",
"values": [
{
"type": "last_candle_value",
"inputs": {
"source": {},
"candle_part": "open"
}
}
]
},
"RIGHT": {
"type": "dynamic_value",
"values": [
44
]
}
}
}
},
"statements": {
"DO": [
{
"type": "trade_action",
"trade_type": "buy",
"inputs": {
"size": {
"type": "math_operation",
"inputs": {
"operator": "divide",
"left_operand": {
"type": "starting_balance",
"inputs": {}
},
"right_operand": 15
}
}
},
"trade_options": [
{
"type": "name_order",
"inputs": {
"order_name": "order_name"
}
},
{
"type": "target_market",
"inputs": {
"time_frame": "15m",
"exchange": "binance",
"symbol": "ETH/BTC"
}
}
]
}
]
}
}
]
}
}
},
{
"type": "execute_if",
"inputs": {
"CONDITION": {
"type": "is_false",
"inputs": {
"condition": {
"type": "order_status",
"inputs": {
"order_name": "order_name",
"status": "filled"
}
}
}
}
},
"statements": {
"DO": [
{
"type": "execute_if",
"inputs": {
"CONDITION": {
"type": "comparison",
"operator": ">",
"inputs": {
"LEFT": {
"type": "dynamic_value",
"values": [
{
"type": "last_candle_value",
"inputs": {
"source": {},
"candle_part": "open"
}
}
]
},
"RIGHT": {
"type": "dynamic_value",
"values": [
44
]
}
}
}
},
"statements": {
"DO": [
{
"type": "trade_action",
"trade_type": "buy",
"inputs": {
"size": {
"type": "math_operation",
"inputs": {
"operator": "divide",
"left_operand": {
"type": "starting_balance",
"inputs": {}
},
"right_operand": 15
}
}
},
"trade_options": [
{
"type": "name_order",
"inputs": {
"order_name": "order_name"
}
},
{
"type": "target_market",
"inputs": {
"time_frame": "15m",
"exchange": "binance",
"symbol": "ETH/BTC"
}
}
]
}
]
}
}
]
}
}
]
}
}
}
]
]
},
"workspace": "<xml xmlns=\"https://developers.google.com/blockly/xml\"><block type=\"execute_if\" id=\"MsS{sEm446Tq-9cZ?LEM\" x=\"50\" y=\"110\"><comment pinned=\"false\" h=\"80\" w=\"160\">Execute the enclosed statements if the condition is true.</comment><value name=\"CONDITION\"><block type=\"comparison\" id=\"o$er@YT|44pRfD2*;kC%\"><field name=\"OPERATOR\">&lt;</field><comment pinned=\"false\" h=\"80\" w=\"160\">Compare two values using operators like &gt;, &lt;, ==.</comment><value name=\"LEFT\"><block type=\"risk_ratio\" id=\"-{)}8vrsu.uQ`,}~MEf-\"><comment pinned=\"false\" h=\"80\" w=\"160\">Calculate and retrieve the current risk ratio.</comment></block></value><value name=\"RIGHT\"><block type=\"current_balance\" id=\"rG=}HE!*$H|BWca!-G+%\"><comment pinned=\"false\" h=\"80\" w=\"160\">Retrieve the current balance of the strategy.</comment></block></value></block></value><statement name=\"DO\"><block type=\"max_position_size\" id=\"p{Do;wN=r3%T.7QD!]H@\"><comment pinned=\"false\" h=\"80\" w=\"160\">Set a maximum limit on the number of positions the strategy can hold.</comment><value name=\"MAX_SIZE\"><block type=\"available_balance\" id=\"H;h(SZy-/g/;;|S_]YQb\"><comment pinned=\"false\" h=\"80\" w=\"160\">Retrieve the overall available (liquid) balance of the user.</comment></block></value><next><block type=\"execute_if\" id=\"85pyvJR)Fr*LmqgKnIUm\"><comment pinned=\"false\" h=\"80\" w=\"160\">Execute the enclosed statements if the condition is true.</comment><value name=\"CONDITION\"><block type=\"is_false\" id=\"(vRWbh:/a=bX}${W4(/t\"><comment pinned=\"false\" h=\"80\" w=\"160\">Check if a condition is false.</comment><value name=\"condition\"><block type=\"order_status\" id=\"v#j1BnPG5pKC$M?csI)8\"><field name=\"ORDER_NAME\">order_name</field><field name=\"order_status\">partial</field><comment pinned=\"false\" h=\"80\" w=\"160\">Get the status of a named order.</comment></block></value></block></value><statement name=\"DO\"><block type=\"execute_if\" id=\"+dPkaDvC^21ylWseCZV;\"><comment pinned=\"false\" h=\"80\" w=\"160\">Execute the enclosed statements if the condition is true.</comment><value name=\"CONDITION\"><block type=\"comparison\" id=\"YTu*wGkeOU0k4]D|(j*:\"><field name=\"OPERATOR\">&gt;</field><comment pinned=\"false\" h=\"80\" w=\"160\">Compare two values using operators like &gt;, &lt;, ==.</comment><value name=\"LEFT\"><block type=\"last_candle_value\" id=\"8h3nZn?#t3UXcuU}icCC\"><field name=\"candle_part\">open</field><comment pinned=\"false\" h=\"80\" w=\"160\">Retrieve a specific part (Open, High, Low, Close) of the last candle from a given source.</comment></block></value><value name=\"RIGHT\"><block type=\"value_input\" id=\"i|%1Z,IK5~WCR5t~[T=J\"><field name=\"VALUE\">44</field><comment pinned=\"false\" h=\"80\" w=\"160\">Enter a numerical value. Chain multiple for a list.</comment></block></value></block></value><statement name=\"DO\"><block type=\"trade_action\" id=\"CYMHOlTFGfsK|l:ce{bX\"><field name=\"tradeType\">buy</field><comment pinned=\"false\" h=\"80\" w=\"160\">Execute a Buy/Sell trade based on a condition with specified size and options.</comment><value name=\"size\"><block type=\"math_operation\" id=\"^+dd,6d[Yg#w+bS|C{{S\"><field name=\"operator\">DIVIDE</field><comment pinned=\"false\" h=\"80\" w=\"160\">Perform basic arithmetic operations between two values.</comment><value name=\"LEFT\"><block type=\"starting_balance\" id=\"Bi5*6JEj#xP`DK9M1-p^\"><comment pinned=\"false\" h=\"80\" w=\"160\">Retrieve the starting balance of the strategy.</comment></block></value><value name=\"RIGHT\"><block type=\"value_input\" id=\".kd4Xeke%72-6/tdATz(\"><field name=\"VALUE\">15</field><comment pinned=\"false\" h=\"80\" w=\"160\">Enter a numerical value. Chain multiple for a list.</comment></block></value></block></value><statement name=\"trade_options\"><block type=\"name_order\" id=\"c;$K`P/c1nf2Ut)yJf=]\"><field name=\"order_name\">order_name</field><comment pinned=\"false\" h=\"80\" w=\"160\">Assign a custom name to the current order.</comment><next><block type=\"target_market\" id=\"FsX9_==AQf?DKtj]1C`]\"><field name=\"TF\">15m</field><field name=\"EXC\">binance</field><field name=\"SYM\">ETH/BTC</field><comment pinned=\"false\" h=\"80\" w=\"160\">Choose the target market for posting orders.</comment></block></next></block></statement></block></statement></block></statement></block></next></block></statement></block></xml>"
}
"""

View File

@ -0,0 +1,120 @@
### general:
```plantuml
@startuml
start
:User creates blocks in Blockly workspace;
:User triggers code generation;
:Blockly.JSON.blockToCode(block) is called;
if (Generator function exists for block type?) then (Yes)
:Call the generator function for the block type;
if (Valid JSON object returned?) then (Yes)
:Proceed to process inputs and statements;
else (No)
:Log error about invalid JSON object;
stop
endif
else (No)
:Log error about missing generator function;
stop
endif
:Process inputs and statements recursively;
:Handle 'next' blocks recursively;
:Assemble final JSON representation;
stop
@enduml
```
### blockToCode:
```plantuml
@startuml
start
:Input block;
if (Generator function exists for block.type?) then (Yes)
:Call generator function to get JSON;
if (JSON is valid?) then (Yes)
if (skipAdditionalParsing flag is set?) then (Yes)
:Return JSON as is;
else (No)
:Call _blockToJson for further processing;
endif
else (No)
:Log error about invalid JSON;
stop
endif
else (No)
:Log error about missing generator function;
stop
endif
stop
@enduml
```
### _blockToJson:
```plantuml
@startuml
start
:Check if block is null;
if (block is null?) then (Yes)
:Return null;
stop
endif
:Call _calculateNestingDepth(block, currentDepth);
if (Depth exceeds maxDepth?) then (Yes)
:Throw Error "Maximum block nesting depth exceeded.";
stop
endif
if (Custom JSON generator exists for block.type?) then (Yes)
:Call custom generator to get json;
if (json is not valid?) then (Yes)
:Log error;
:Return null;
stop
endif
if (json.skipAdditionalParsing is true?) then (Yes)
:Remove skipAdditionalParsing from json;
if (json.type is not defined?) then (Yes)
:Return null;
else
:Return json;
stop
endif
endif
else (No)
:Create default json object with type, fields, inputs, statements;
:Capture all fields in the block;
endif
:Initialize json.inputs and json.statements if not already present;
:Initialize index = 0;
while (index < length of block.inputList) is (Yes)
:input = block.inputList[index];
if (input.connection and input.connection.targetBlock() exist?) then (Yes)
:Get targetBlock and targetJson;
:Call _blockToJson(targetBlock, currentDepth + 1);
if (input.type == INPUT_VALUE) then (Yes)
:Add targetJson to json.inputs[input.name];
else if (input.type == NEXT_STATEMENT) then (Yes)
:Add targetJson to json.statements[input.name] array;
endif
endif
:Increment index;
endwhile
:Handle 'next' connection;
if (block has next block?) then (Yes)
:Call _blockToJson(block.getNextBlock(), currentDepth);
:Assign result to json.next;
endif
:Return json;
stop
@enduml
```

View File

@ -0,0 +1,254 @@
### StratWorkspaceManager:
```plantuml
@startuml
start
:Initialize StratWorkspaceManager;
note right
- workspace = null
- blocksDefined = false
- MAX_TOP_LEVEL_BLOCKS = 10
end note
if (blocklyDiv exists?) then (yes)
if (workspace exists?) then (yes)
:Dispose of existing workspace;
endif
:Load Modules and Init Workspace;
note right
Calls _loadModulesAndInitWorkspace()
end note
:Initialize Blockly Workspace;
note right
- Set blocksDefined = true
- Inject workspace into blocklyDiv
end note
else (no)
:Log error "blocklyDiv is not loaded.";
endif
:Adjust Workspace;
note right
Calls adjustWorkspace()
end note
stop
@enduml
```
### initWorkspace:
```plantuml
@startuml
participant "StratWorkspaceManager" as SWM
participant "Document" as Doc
participant "Blockly"
SWM -> Doc: getElementById('blocklyDiv')
alt blocklyDiv exists
SWM -> SWM: workspace.dispose() [if workspace exists]
SWM -> SWM: _loadModulesAndInitWorkspace()
SWM -> SWM: blocksDefined = true
SWM -> Blockly: inject('blocklyDiv', options)
else
SWM -> SWM: console.error("blocklyDiv is not loaded.")
end
@enduml
```
### _loadModulesAndInitWorkspace:
```plantuml
@startuml
participant "StratWorkspaceManager" as SWM
participant "Document" as Doc
participant "Blockly"
@startuml
participant "StratWorkspaceManager" as SWM
participant "Custom Blocks Modules" as Modules
participant "Document" as Doc
participant "Blockly"
SWM -> SWM: Check blocksDefined
alt blocksDefined is false
SWM -> Modules: Import and define modules
SWM -> SWM: blocksDefined = true
else
SWM -> SWM: Skip module loading
end
SWM -> Doc: getElementById('toolbox_advanced')
alt toolbox exists
SWM -> Blockly: inject('blocklyDiv', options)
SWM -> SWM: Workspace initialized
else
SWM -> SWM: console.error("toolbox is not loaded.")
end
@enduml
SWM -> Doc: getElementById('blocklyDiv')
alt blocklyDiv exists
SWM -> SWM: workspace.dispose() [if workspace exists]
SWM -> SWM: _loadModulesAndInitWorkspace()
SWM -> SWM: blocksDefined = true
SWM -> Blockly: inject('blocklyDiv', options)
else
SWM -> SWM: console.error("blocklyDiv is not loaded.")
end
@enduml
```
### adjustWorkspace:
```plantuml
@startuml
participant "StratWorkspaceManager" as SWM
participant "Document" as Doc
participant "Blockly"
SWM -> Doc: getElementById('blocklyDiv')
alt blocklyDiv and workspace exist
SWM -> Blockly: svgResize(workspace)
else
SWM -> SWM: console.error("Cannot resize workspace.")
end
@enduml
```
### compileStrategyJson:
```plantuml
@startuml
start
:Check if workspace is available;
if (workspace exists?) then (yes)
:Get strategy name from name_box;
:Generate strategy JSON;
note right
Calls _generateStrategyJsonFromWorkspace()
end note
if (strategyJson is null?) then (yes)
:Log error "Failed to generate strategy JSON.";
stop
endif
:Serialize workspace to XML;
:Return JSON string containing strategy data;
else (no)
:Log error "Workspace is not available.";
stop
endif
@enduml
```
### _generateStrategyJsonFromWorkspace:
```plantuml
@startuml
start
:Categorize orphaned blocks;
note right
Calls _categorizeOrphanedBlocks(workspace)
end note
:Calculate totalTopLevelBlocks;
if (totalTopLevelBlocks > MAX_TOP_LEVEL_BLOCKS?) then (yes)
:Log error "Too many top-level blocks.";
:Alert user;
:Return null;
stop
else (no)
:Create strategy JSON;
note right
Calls _createStrategyJson(initializationBlocks, actionBlocks)
end note
:Return strategy JSON;
stop
endif
@enduml
```
### _categorizeOrphanedBlocks:
```plantuml
@startuml
start
:Get top-level blocks from workspace;
:Initialize initializationBlocks and actionBlocks arrays;
:Initialize index = 0;
while (index < total number of blocks?) is (yes)
:Get block at index;
if (block.type is initialization type?) then (yes)
:Add block to initializationBlocks;
else if (block.type is action type?) then (yes)
:Add block to actionBlocks;
else
:Log warning "Block type not allowed as top-level block.";
endif
:Increment index;
endwhile
:Return { initializationBlocks, actionBlocks };
stop
@enduml
```
### _createStrategyJson:
```plantuml
@startuml
start
:Initialize empty statements array;
:Initialize index = 0;
while (index < number of initializationBlocks?) is (yes)
:Get block at index from initializationBlocks;
:Convert block to JSON;
:Add to statements array;
:Increment index;
endwhile
:Reset index = 0;
while (index < number of actionBlocks?) is (yes)
:Get block at index from actionBlocks;
:Convert block to JSON;
:Add to statements array;
:Increment index;
endwhile
:Create strategy JSON with type 'strategy' and statements;
:Return strategy JSON;
stop
@enduml
```
### _loadWorkspaceFromXml:
```plantuml
@startuml
participant "StratWorkspaceManager" as SWM
participant "Blockly"
participant "Workspace" as WS
SWM -> SWM: Check if workspace is initialized
alt workspace exists
SWM -> Blockly: textToDom(workspaceXmlText)
alt workspaceXml is valid
SWM -> WS: clear()
SWM -> Blockly: domToWorkspace(workspaceXml, workspace)
else
SWM -> SWM: console.error('Invalid workspace XML.')
SWM -> User: alert('Invalid workspace data.')
end
else
SWM -> SWM: console.error('Workspace is not initialized.')
end
@enduml
```

74
markdown/strategies.md Normal file
View File

@ -0,0 +1,74 @@
### Strategies:
```plantuml
@startuml
class StratUIManager {
- workspaceManager
- targetEl
- formElement
--
+ initUI(targetId, formElId)
+ displayForm(action, strategyData = null)
+ hideForm()
+ updateStrategiesHtml(strategies)
+ toggleFeeInput()
+ registerDeleteStrategyCallback(callback)
}
class StratDataManager {
- strategies
--
+ fetchSavedStrategies(comms, data)
+ addNewStrategy(data)
+ updateStrategyData(data)
+ removeStrategy(name)
+ applyBatchUpdates(data)
+ getAllStrategies()
}
class StratWorkspaceManager {
- workspace
- blocksDefined
- MAX_TOP_LEVEL_BLOCKS
--
+ initWorkspace()
- loadModulesAndInitWorkspace()
+ adjustWorkspace()
+ compileStrategyJson()
- generateStrategyJsonFromWorkspace()
- categorizeOrphanedBlocks(workspace)
- createStrategyJson(initializationBlocks, actionBlocks)
+ loadWorkspaceFromXml(workspaceXmlText)
}
class Strategies {
- dataManager
- workspaceManager
- uiManager
- comms
- data
- _initialized
--
+ initialize(targetId, formElId, data)
+ handleStrategyError(errorData)
+ handleStrategies(data)
+ handleStrategyCreated(data)
+ handleStrategyUpdated(data)
+ handleStrategyDeleted(data)
+ handleUpdates(data)
+ resizeWorkspace()
+ generateStrategyJson()
+ restoreWorkspaceFromXml(workspaceXmlText)
+ submitStrategy(action)
+ deleteStrategy(name)
}
' Relationships
Strategies --> StratDataManager
Strategies --> StratWorkspaceManager
Strategies --> StratUIManager
StratUIManager --> StratWorkspaceManager
@enduml
...

View File

@ -1,5 +1,6 @@
class StratUIManager {
constructor() {
constructor(workspaceManager) {
this.workspaceManager = workspaceManager;
this.targetEl = null;
this.formElement = null;
}
@ -26,7 +27,7 @@ class StratUIManager {
/**
* Displays the form for creating or editing a strategy.
* @param {string} action - The action to perform ('new' or 'edit').
* @param {string|null} strategyData - The data of the strategy to edit (only applicable for 'edit' action).
* @param {object|null} strategyData - The data of the strategy to edit (only applicable for 'edit' action).
*/
async displayForm(action, strategyData = null) {
console.log(`Opening form for action: ${action}, strategy: ${strategyData?.name}`);
@ -64,9 +65,9 @@ class StratUIManager {
this.formElement.style.display = "grid";
// Initialize Blockly workspace after the form becomes visible
if (UI.strats && UI.strats.workspaceManager) {
if (UI.strats && this.workspaceManager) {
try {
await UI.strats.workspaceManager.initWorkspace();
await this.workspaceManager.initWorkspace();
console.log("Blockly workspace initialized.");
} catch (error) {
console.error("Failed to initialize Blockly workspace:", error);
@ -121,8 +122,11 @@ class StratUIManager {
// Strategy icon
const strategyIcon = document.createElement('div');
strategyIcon.className = 'strategy-icon';
// Open the form with strategy data when clicked
strategyIcon.addEventListener('click', () => {
this.displayForm('edit', strat); // Open the form with strategy data when clicked
this.displayForm('edit', strat).catch(error => {
console.error('Error displaying form:', error);
});
});
// Strategy name
@ -150,6 +154,7 @@ class StratUIManager {
* Toggles the fee input field based on the state of the public checkbox.
*/
toggleFeeInput() {
/** @type {HTMLInputElement} */
const publicCheckbox = document.getElementById('public_checkbox');
const feeBox = document.getElementById('fee_box');
@ -223,7 +228,7 @@ class StratDataManager {
/**
* Handles the deletion of a strategy.
* @param {Object} data - The name for the deleted strategy.
* @param {Object} name - The name for the deleted strategy.
*/
removeStrategy(name) {
try {
@ -259,7 +264,7 @@ class StratWorkspaceManager {
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
this.MAX_DEPTH = 10; // Set your desired limit
}
/**
@ -280,6 +285,9 @@ class StratWorkspaceManager {
// Initialize custom blocks and Blockly workspace
await this._loadModulesAndInitWorkspace();
// Set the maximum allowed nesting depth
Blockly.JSON.maxDepth = this.MAX_DEPTH; // or any desired value
}
@ -291,7 +299,7 @@ class StratWorkspaceManager {
jsonBaseModule.defineJsonBaseGenerator();
console.log('Defined defineJsonBaseGenerator from json_base_generator.js');
// Define lists of generator and block files with their corresponding define functions
// Map generator and block files to the functions that define them.
const generatorModules = [
{ file: 'balances_generators.js', defineFunc: 'defineBalancesGenerators' },
{ file: 'order_metrics_generators.js', defineFunc: 'defineOrderMetricsGenerators' },
@ -408,7 +416,7 @@ class StratWorkspaceManager {
console.error("Workspace is not available.");
return null;
}
/** @type {HTMLInputElement} */
const nameElement = document.getElementById('name_box');
if (!nameElement) {
console.error("Name input element (name_box) is not available.");
@ -416,9 +424,6 @@ class StratWorkspaceManager {
}
const strategyName = nameElement.value;
// 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();
@ -442,7 +447,7 @@ 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.
* @returns {Object} - An array of JSON objects representing the top-level blocks.
*/
_generateStrategyJsonFromWorkspace() {
const { initializationBlocks, actionBlocks } = this._categorizeOrphanedBlocks(this.workspace);
@ -456,15 +461,13 @@ class StratWorkspaceManager {
}
// Proceed with strategy JSON creation
const strategyJson = this._createStrategyJson(initializationBlocks, actionBlocks);
return strategyJson;
return this._createStrategyJson(initializationBlocks, actionBlocks);
}
/**
* Identify all orphaned blocks on the workspace. Categorize them into initialization blocks and action blocks.
* @private
* @returns {Object[]} - an object containing an array of initializationBlocks and actionBlocks.
* @returns {Object} - an object containing an array of initializationBlocks and actionBlocks.
*/
_categorizeOrphanedBlocks(workspace) {
const blocks = workspace.getTopBlocks(true);
@ -481,6 +484,7 @@ class StratWorkspaceManager {
case 'set_variable':
case 'set_flag':
case 'set_leverage':
case 'max_position_size':
case 'pause_strategy':
case 'strategy_resume':
case 'strategy_exit':
@ -525,12 +529,10 @@ class StratWorkspaceManager {
});
// Create the root strategy block
const strategyJson = {
type: 'strategy',
statements: statements
return {
type: 'strategy',
statements: statements
};
return strategyJson;
}
/**
@ -567,9 +569,9 @@ class StratWorkspaceManager {
class Strategies {
constructor() {
this.uiManager = new StratUIManager();
this.dataManager = new StratDataManager();
this.workspaceManager = new StratWorkspaceManager();
this.uiManager = new StratUIManager(this.workspaceManager);
this.comms = null;
this.data = null;
this._initialized = false;
@ -593,17 +595,20 @@ class Strategies {
this.uiManager.initUI(targetId, formElId);
if (!data || typeof data !== 'object') {
throw new Error("Invalid data object provided for initialization.");
console.error("Invalid data provided for initialization.");
return;
}
this.data = data;
if (!this.data.user_name || typeof this.data.user_name !== 'string') {
throw new Error("Invalid user_name provided in data object.");
console.error("Invalid user_name provided in data object.");
return;
}
this.comms = this.data?.comms;
if (!this.comms) {
throw new Error('Communications instance not provided in data.');
console.error("Communications instance not provided in data.");
return;
}
// Register handlers with Comms for specific message types
@ -704,14 +709,6 @@ class Strategies {
this.uiManager.updateStrategiesHtml(this.dataManager.getAllStrategies());
}
/**
* Creates the Blockly workspace using StratWorkspaceManager.
* @async
*/
async createWorkspace() {
await this.workspaceManager.initializeWorkspace();
}
/**
* Resizes the Blockly workspace using StratWorkspaceManager.
*/
@ -740,8 +737,11 @@ class Strategies {
* @param {string} action - Action type, either 'new' or 'edit'.
*/
submitStrategy(action) {
/** @type {HTMLInputElement} */
const feeBox = document.getElementById('fee_box');
/** @type {HTMLInputElement} */
const nameBox = document.getElementById('name_box');
/** @type {HTMLInputElement} */
const publicCheckbox = document.getElementById('public_checkbox');
if (!feeBox || !nameBox || !publicCheckbox) {

View File

@ -67,13 +67,13 @@ export function defineLogicalBlocks() {
"args0": [
{
"type": "input_value",
"name": "left",
"name": "LEFT",
"check": "Boolean",
"align": "RIGHT"
},
{
"type": "input_value",
"name": "right",
"name": "RIGHT",
"check": "Boolean",
"align": "LEFT"
}
@ -98,13 +98,13 @@ export function defineLogicalBlocks() {
"args0": [
{
"type": "input_value",
"name": "left",
"name": "LEFT",
"check": "Boolean",
"align": "RIGHT"
},
{
"type": "input_value",
"name": "right",
"name": "RIGHT",
"check": "Boolean",
"align": "LEFT"
}

View File

@ -166,7 +166,7 @@ export function defineMarketDataBlocks() {
},
{
"type": "input_value",
"name": "NEXT",
"name": "VALUES",
"check": "dynamic_value",
"align": "RIGHT"
}

View File

@ -36,7 +36,7 @@ export function defineOrderMetricsBlocks() {
},
{
"type": "input_value",
"name": "NEXT",
"name": "VALUES",
"check": "dynamic_value",
"align": "LEFT"
}
@ -60,7 +60,7 @@ export function defineOrderMetricsBlocks() {
"args0": [
{
"type": "input_value",
"name": "NEXT",
"name": "VALUES",
"check": "dynamic_value",
"align": "LEFT"
}
@ -84,7 +84,7 @@ export function defineOrderMetricsBlocks() {
"args0": [
{
"type": "input_value",
"name": "NEXT",
"name": "VALUES",
"check": "dynamic_value",
"align": "LEFT"
}

View File

@ -37,7 +37,7 @@ export function defineTimeMetricsBlocks() {
},
{
"type": "input_value",
"name": "NEXT",
"name": "VALUES",
"check": "dynamic_value",
"align": "LEFT"
}

View File

@ -26,7 +26,7 @@ export function defineTradeMetricsBlocks() {
"args0": [
{
"type": "input_value",
"name": "NEXT",
"name": "VALUES",
"check": "dynamic_value",
"align": "LEFT"
}
@ -49,7 +49,7 @@ export function defineTradeMetricsBlocks() {
"args0": [
{
"type": "input_value",
"name": "NEXT",
"name": "VALUES",
"check": "dynamic_value",
"align": "LEFT"
}
@ -82,7 +82,7 @@ export function defineTradeMetricsBlocks() {
},
{
"type": "input_value",
"name": "NEXT",
"name": "VALUES",
"check": "dynamic_value",
"align": "LEFT"
}
@ -105,7 +105,7 @@ export function defineTradeMetricsBlocks() {
"args0": [
{
"type": "input_value",
"name": "NEXT",
"name": "VALUES",
"check": "dynamic_value",
"align": "LEFT"
}
@ -128,7 +128,7 @@ export function defineTradeMetricsBlocks() {
"args0": [
{
"type": "input_value",
"name": "NEXT",
"name": "VALUES",
"check": "dynamic_value",
"align": "LEFT"
}
@ -152,7 +152,7 @@ export function defineTradeMetricsBlocks() {
"args0": [
{
"type": "input_value",
"name": "NEXT",
"name": "VALUES",
"check": "dynamic_value"
}
],

View File

@ -15,39 +15,11 @@ export function defineAdvancedMathGenerators() {
return;
}
/**
* Helper Function: processValuesInput
* Processes the 'VALUES' input and returns an array of values.
*
* @param {Blockly.Block} block - The block containing the 'VALUES' input.
* @returns {Array} - An array of values from the 'VALUES' input.
*/
function processValuesInput(block) {
const valuesInputBlock = block.getInputTargetBlock('VALUES');
let values = [];
if (valuesInputBlock) {
const valuesJson = Blockly.JSON.blockToCode(valuesInputBlock);
// Check if valuesJson is a 'dynamic_value' object and has an array of values
if (valuesJson && valuesJson.type === 'dynamic_value' && Array.isArray(valuesJson.values)) {
values = valuesJson.values; // Use the values array directly from dynamic_value
} else if (Array.isArray(valuesJson)) {
values = valuesJson; // Use valuesJson as-is if it's already an array
} else {
values = [valuesJson]; // Wrap non-array values in a list
}
}
return values;
}
/**
* Generator for 'power' Block
*/
Blockly.JSON['power'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
const base = valuesArray[0] !== undefined ? valuesArray[0] : 0;
const exponent = valuesArray[1] !== undefined ? valuesArray[1] : 1;
@ -75,7 +47,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'modulo' Block
*/
Blockly.JSON['modulo'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
const dividend = valuesArray[0] !== undefined ? valuesArray[0] : 0;
const divisor = valuesArray[1] !== undefined ? valuesArray[1] : 1;
@ -103,7 +75,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'sqrt' Block
*/
Blockly.JSON['sqrt'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
const number = valuesArray[0] !== undefined ? valuesArray[0] : 0;
@ -129,7 +101,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'abs' Block
*/
Blockly.JSON['abs'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
const number = valuesArray[0] !== undefined ? valuesArray[0] : 0;
@ -155,7 +127,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'max' Block
*/
Blockly.JSON['max'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('max', {
@ -173,7 +145,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'min' Block
*/
Blockly.JSON['min'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('min', {
@ -191,7 +163,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'factorial' Block
*/
Blockly.JSON['factorial'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
const number = valuesArray[0] !== undefined ? valuesArray[0] : 1;
@ -217,7 +189,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'log' Block
*/
Blockly.JSON['log'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
const number = valuesArray[0] !== undefined ? valuesArray[0] : 1;
@ -243,7 +215,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'ln' Block
*/
Blockly.JSON['ln'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
const number = valuesArray[0] !== undefined ? valuesArray[0] : 1;
@ -270,7 +242,7 @@ export function defineAdvancedMathGenerators() {
*/
Blockly.JSON['trig'] = function(block) {
const operator = block.getFieldValue('operator'); // 'sin', 'cos', 'tan'
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
const angle = valuesArray[0] !== undefined ? valuesArray[0] : 0;
@ -304,7 +276,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'mean' Block
*/
Blockly.JSON['mean'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('mean', {
@ -322,7 +294,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'median' Block
*/
Blockly.JSON['median'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('median', {
@ -340,7 +312,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'std_dev' Block
*/
Blockly.JSON['std_dev'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('std_dev', {
@ -358,7 +330,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'round' Block
*/
Blockly.JSON['round'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
const number = valuesArray[0] !== undefined ? valuesArray[0] : 0;
const decimals = valuesArray[1] !== undefined ? valuesArray[1] : 0;
@ -389,7 +361,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'floor' Block
*/
Blockly.JSON['floor'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
const number = valuesArray[0] !== undefined ? valuesArray[0] : 0;
@ -415,7 +387,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'ceil' Block
*/
Blockly.JSON['ceil'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
const number = valuesArray[0] !== undefined ? valuesArray[0] : 0;
@ -441,7 +413,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'random' Block
*/
Blockly.JSON['random'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
let min, max;
if (valuesArray.length >= 2) {
@ -482,7 +454,7 @@ export function defineAdvancedMathGenerators() {
* Generator for 'clamp' Block
*/
Blockly.JSON['clamp'] = function(block) {
const valuesArray = processValuesInput(block);
const valuesArray = Blockly.JSON.processValuesInput(block);
const number = valuesArray[0] !== undefined ? valuesArray[0] : 0;
const min = valuesArray[1] !== undefined ? valuesArray[1] : null;
@ -516,36 +488,50 @@ export function defineAdvancedMathGenerators() {
* Generator for 'math_operation' Block
*/
Blockly.JSON['math_operation'] = function(block) {
/**
* Helper Function: _extractValues
* Extracts values from a given input block, handling 'dynamic_value' objects, arrays, or single values.
*
* @param {Blockly.Block} inputBlock - The block to extract values from.
* @returns {Array} - An array of values extracted from the input block.
*/
Blockly.JSON._extractValues = function(inputBlock) {
if (!inputBlock) return [];
const inputJson = Blockly.JSON.blockToCode(inputBlock);
if (inputJson?.type === 'dynamic_value' && Array.isArray(inputJson.values)) {
return inputJson.values; // Use values directly if it's a 'dynamic_value' array
}
return Array.isArray(inputJson) ? inputJson : [inputJson]; // Wrap in array if not already an array
};
const operator = block.getFieldValue('operator'); // 'ADD', 'SUBTRACT', 'MULTIPLY', 'DIVIDE'
const valuesArray = processValuesInput(block);
const leftValues = Blockly.JSON._extractValues(block.getInputTargetBlock('LEFT'));
const rightValues = Blockly.JSON._extractValues(block.getInputTargetBlock('RIGHT'));
const leftOperand = valuesArray[0] !== undefined ? valuesArray[0] : 0;
const rightOperand = valuesArray[1] !== undefined ? valuesArray[1] : 0;
// Use the first values or default to 0 if not available
const leftOperand = leftValues[0] ?? 0;
const rightOperand = rightValues[0] ?? 0;
// Validate operator
// Validate and format the operator
const validOperators = ['ADD', 'SUBTRACT', 'MULTIPLY', 'DIVIDE'];
const validatedOperator = validOperators.includes(operator) ? operator.toLowerCase() : 'add';
if (!validOperators.includes(operator)) {
console.error(`Invalid operator '${operator}' in 'math_operation' block. Defaulting to 'ADD'.`);
}
const validatedOperator = validOperators.includes(operator) ? operator : 'ADD';
// Create operation object
// Create the primary operation object
const operationObject = Blockly.JSON.createOperationObject('math_operation', {
operator: validatedOperator.toLowerCase(), // Assuming server expects lowercase operators
operator: validatedOperator,
left_operand: leftOperand,
right_operand: rightOperand
});
// Include unused values if any
const outputValues = [operationObject];
if (valuesArray.length > 2) {
outputValues.push(...valuesArray.slice(2));
}
// Output as dynamic_value
return {
type: 'dynamic_value',
values: outputValues
values: [operationObject]
};
};
}

View File

@ -120,26 +120,15 @@ export function defineBalancesGenerators() {
* This is a statement block, so it generates an action object and handles 'NEXT' connections.
*/
Blockly.JSON['set_available_strategy_balance'] = function(block) {
// Retrieve the 'BALANCE' input block
const balanceBlock = block.getInputTargetBlock('BALANCE');
let balance = 0;
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.");
}
} else {
console.warn("No balance provided in 'set_available_strategy_balance' block. Using default value 0.");
}
const values = Blockly.JSON.processNumericInput(block,'BALANCE',0);
const balance = values !== undefined ? values : 0;
const json = {
type: 'set_available_strategy_balance',
inputs: {
balance: balance
}
inputs: { BALANCE: balance },
// skipAdditionalParsing: true
};
// No need to handle 'NEXT' input; sequencing is managed by _blockToJson

View File

@ -4,12 +4,6 @@ export function defineJsonBaseGenerator() {
if (!Blockly.JSON) {
Blockly.JSON = new Blockly.Generator('JSON');
/**
* 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)
@ -89,134 +83,124 @@ export function defineJsonBaseGenerator() {
};
};
/**
* Helper Function: _blockToJson
* Recursively converts a block and its connected blocks into JSON.
* Converts a Blockly block into a JSON representation.
* Handles both custom generators and default JSON object creation.
*
* @param {Blockly.Block} block - The block to convert.
* @param {number} currentDepth - The current recursion depth.
* @returns {Object} - The JSON representation of the block.
* @param {Block} block - The block to convert.
* @param {number} currentDepth - The current depth of the block in the workspace (for handling nesting).
* @returns {object|null} - The JSON representation of the block, or null if conversion fails.
*/
Blockly.JSON._blockToJson = function(block, currentDepth = 0) {
// If the block is null, return null
if (!block) {
return null;
}
// Calculate nesting depth and enforce limits
// Handle situations where recursion exceeds the maximum allowed depth.
try {
Blockly.JSON._calculateNestingDepth(block, currentDepth);
} catch (error) {
console.error(error.message);
alert(error.message);
throw error; // Stop processing if limit exceeded
throw error;
}
// Use the block's JSON generator if it exists
let json;
if (Blockly.JSON && Blockly.JSON[block.type]) {
// Generate JSON object using the block's JSON generator
const json = Blockly.JSON[block.type](block);
// Custom generator exists
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**
// Skip additional parsing if specified
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: {}
// No custom generator, create a default JSON object
json = {
type: block.type
};
// Capture all fields in the block
block.inputList.forEach(input => {
if (input.fieldRow) {
input.fieldRow.forEach(field => {
if (field.name && field.getValue) {
json.fields[field.name] = field.getValue();
}
});
}
});
// Capture fields and process inputs
Blockly.JSON._captureFields(block, json);
Blockly.JSON._processInputs(block, json, currentDepth);
// 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;
}
// Handle 'next' connections recursively
if (block.getNextBlock()) {
json.next = Blockly.JSON._blockToJson(block.getNextBlock(), currentDepth);
}
return json;
};
/**
* Processes the inputs of a block and adds them to the JSON representation.
* Handles both value and statement inputs.
*
* @param {Blockly.Block} block - The block whose inputs are to be processed.
* @param {object} json - The JSON object to populate with input data.
* @param {number} currentDepth - The current depth of the block in the workspace.
*/
Blockly.JSON._processInputs = function(block, json, currentDepth) {
// Iterate over each input in the block's input list
block.inputList.forEach(input => {
// If the input does not have a connection to another block, skip to the next input
if (!input.connection || !input.connection.targetBlock()) return;
// Get and convert the connected block to JSON, increasing the depth
const targetBlock = input.connection.targetBlock();
const targetJson = Blockly.JSON._blockToJson(targetBlock, currentDepth + 1);
// If the input is a value input, add it to the 'inputs' section of the json
if (input.type === Blockly.INPUT_VALUE) {
if (!json.inputs) json.inputs = {};
json.inputs[input.name] = targetJson;
}
// If the input is a statement input, add it to the 'statements' section of the json
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);
}
});
};
/**
* Captures the fields of a block and adds them to the JSON representation.
*
* @param {Blockly.Block} block - The block whose fields are to be captured.
* @param {object} json - The JSON object to populate with field data.
*/
Blockly.JSON._captureFields = function(block, json) {
// Iterate over each input in the block's input list
block.inputList.forEach(input => {
// If the input doesn't contain a fieldRow, skip to the next input.
if (!input.fieldRow) return;
// Iterate over each field in the fieldRow
input.fieldRow.forEach(field => {
// If the field has no name or method to get its value, skip to the next field.
if (!field.name || !field.getValue) return;
// Initialize the fields object in JSON if it doesn't exist
if (!json.fields) json.fields = {};
// Add the field name and its value to the JSON fields object
json.fields[field.name] = field.getValue();
});
});
};
/**
* Helper Function: _calculateNestingDepth
* Calculates the nesting depth of a block and throws an error if it exceeds the maximum allowed depth.
@ -225,43 +209,56 @@ export function defineJsonBaseGenerator() {
* @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.`);
if (currentDepth > Blockly.JSON.maxDepth) {
throw new Error(`Maximum block nesting depth of ${Blockly.JSON.maxDepth} exceeded.`);
}
// Add any additional depth calculations or checks if necessary
};
/**
* Helper Function: processNumericInput
* Processes a numeric input.
* Helper Function: extractValues
* Extracts values from a block's input, handling dynamic_value arrays or single values.
*
* @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.
* @param {number | null} defaultValue - Default value for single extractions (null for array extraction).
* @returns {Array | number} - An array of values if defaultValue is null; otherwise, the first value or the default.
*/
Blockly.JSON.processNumericInput = function (block, inputName, defaultValue) {
Blockly.JSON.extractValues = function(block, inputName, defaultValue = null) {
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;
if (defaultValue !== null) {
console.warn(`No block connected to '${inputName}' input of '${block.type}' block. Using default value ${defaultValue}.`);
return defaultValue;
}
return []; // Return empty array for multi-value cases
}
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) {
if (inputJson?.type === 'dynamic_value' && Array.isArray(inputJson.values)) {
return defaultValue !== null ? inputJson.values[0] : inputJson.values;
}
if (defaultValue !== null) {
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;
return Array.isArray(inputJson) ? inputJson : [inputJson];
};
// Usage in processValuesInput (array case)
Blockly.JSON.processValuesInput = function(block) {
return Blockly.JSON.extractValues(block, 'VALUES');
};
// Usage in processNumericInput (single numeric case)
Blockly.JSON.processNumericInput = function(block, inputName, defaultValue) {
return Blockly.JSON.extractValues(block, inputName, defaultValue);
};
console.log('Blockly.JSON generator initialized with helper functions: createOperationObject, _blockToJson, collectTradeOptions.');
}
}

View File

@ -48,75 +48,77 @@ export function defineLogicalGenerators() {
return { left, right };
}
/**
* 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.
* Generator for 'comparison' Block
*
* @param {Blockly.Block} block - The block containing the 'LEFT' and 'RIGHT' inputs.
* @returns {Object} - An object with 'LEFT' and 'RIGHT' properties.
* Generates a JSON object representing a comparison operation between two values
* using operators like '>', '<', '=='. Outputs a Boolean value based on the comparison result.
*/
function processTwoValueInput(block) {
const leftBlock = block.getInputTargetBlock('LEFT');
const rightBlock = block.getInputTargetBlock('RIGHT');
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;
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;
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
}
} 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;
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
}
} 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 };
}
return { LEFT: left, RIGHT: right };
}
const { LEFT, RIGHT } = processTwoValueInput(block);
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] }
// 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: 'dynamic_value', values: [0] },
RIGHT: RIGHT !== null ? RIGHT : { type: 'dynamic_value', values: [0] }
},
skipAdditionalParsing: true
};
return operationObject;
};
return operationObject;
};
/**
* Generator for 'logical_and' Block
@ -124,101 +126,142 @@ Blockly.JSON['comparison'] = function(block) {
* Generates a JSON object representing a logical AND operation between two conditions.
*/
Blockly.JSON['logical_and'] = function(block) {
const { left, right } = processTwoValueInput(block);
// Get the connected blocks
const leftBlock = block.getInputTargetBlock('LEFT');
const rightBlock = block.getInputTargetBlock('RIGHT');
// Default to false if any condition is null
const leftCondition = typeof left === 'boolean' ? left : false;
const rightCondition = typeof right === 'boolean' ? right : false;
let leftValue = null;
let rightValue = null;
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('logical_and', {
left: leftCondition,
right: rightCondition
});
// Process the left input
if (leftBlock) {
const leftGenerator = Blockly.JSON[leftBlock.type];
if (typeof leftGenerator === 'function') {
leftValue = leftGenerator(leftBlock);
} else {
console.warn(`No generator found for block type "${leftBlock.type}". Defaulting left input to false.`);
leftValue = false;
}
} else {
console.warn("No block connected to 'LEFT' input of 'logical_and' block. Defaulting to false.");
leftValue = false;
}
// Output as dynamic_value
return operationObject;
// Process the right input
if (rightBlock) {
const rightGenerator = Blockly.JSON[rightBlock.type];
if (typeof rightGenerator === 'function') {
rightValue = rightGenerator(rightBlock);
} else {
console.warn(`No generator found for block type "${rightBlock.type}". Defaulting right input to false.`);
rightValue = false;
}
} else {
console.warn("No block connected to 'RIGHT' input of 'logical_and' block. Defaulting to false.");
rightValue = false;
}
// Create the logical_and operation object
const json = {
type: 'logical_and',
inputs: {
left: leftValue,
right: rightValue
}
};
return json;
};
/**
* Generator for 'logical_or' Block
*
* Generates a JSON object representing a logical OR operation between two conditions.
*/
Blockly.JSON['logical_or'] = function(block) {
const { left, right } = processTwoValueInput(block);
// Get the connected blocks
const leftBlock = block.getInputTargetBlock('LEFT');
const rightBlock = block.getInputTargetBlock('RIGHT');
// Default to false if any condition is null
const leftCondition = typeof left === 'boolean' ? left : false;
const rightCondition = typeof right === 'boolean' ? right : false;
let leftValue = null;
let rightValue = null;
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('logical_or', {
left: leftCondition,
right: rightCondition
});
// Process the left input
if (leftBlock) {
const leftGenerator = Blockly.JSON[leftBlock.type];
if (typeof leftGenerator === 'function') {
leftValue = leftGenerator(leftBlock);
} else {
console.warn(`No generator found for block type "${leftBlock.type}". Defaulting left input to false.`);
leftValue = false;
}
} else {
console.warn("No block connected to 'LEFT' input of 'logical_or' block. Defaulting to false.");
leftValue = false;
}
// Output as dynamic_value
return operationObject;
// Process the right input
if (rightBlock) {
const rightGenerator = Blockly.JSON[rightBlock.type];
if (typeof rightGenerator === 'function') {
rightValue = rightGenerator(rightBlock);
} else {
console.warn(`No generator found for block type "${rightBlock.type}". Defaulting right input to false.`);
rightValue = false;
}
} else {
console.warn("No block connected to 'RIGHT' input of 'logical_or' block. Defaulting to false.");
rightValue = false;
}
// Create the logical_or operation object
const json = {
type: 'logical_or',
inputs: {
left: leftValue,
right: rightValue
}
};
return json;
};
/**
* Generator for 'is_false' Block
*
* Generates a JSON object to check if a given condition is false.
*/
Blockly.JSON['is_false'] = function(block) {
// Retrieve and parse the 'CONDITION' input
const conditionBlock = block.getInputTargetBlock('CONDITION');
let condition = false;
// Get the connected block
const conditionBlock = block.getInputTargetBlock('condition');
let conditionValue = null;
// Process the condition input
if (conditionBlock) {
const conditionJson = Blockly.JSON.blockToCode(conditionBlock);
if (typeof conditionJson === 'boolean') {
condition = conditionJson;
const conditionGenerator = Blockly.JSON[conditionBlock.type];
if (typeof conditionGenerator === 'function') {
conditionValue = conditionGenerator(conditionBlock);
} else {
console.error("Invalid condition value in 'is_false' block. Expected a Boolean.");
console.warn(`No generator found for block type "${conditionBlock.type}". Defaulting condition to false.`);
conditionValue = false;
}
} else {
console.warn("No condition provided in 'is_false' block. Using default value 'false'.");
console.warn("No block connected to 'condition' input of 'is_false' block. Defaulting to false.");
conditionValue = false;
}
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('is_false', {
condition: condition
});
// 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.");
// Create the is_false operation object
const json = {
type: 'is_false',
inputs: {
condition: conditionValue
}
} 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;
return json;
};
}

View File

@ -16,72 +16,70 @@ export function defineMarketDataGenerators() {
/**
* Helper Function: processSourceInput
* Processes the 'SOURCE' input and returns its value.
* Processes the 'source' input, ensuring it is a valid source block and returning its value if connected.
*
* @param {Blockly.Block} block - The block containing the 'SOURCE' input.
* @returns {Object} - The source object or a default empty object.
* @param {Blockly.Block} block - The block containing the 'source' input.
* @returns {Object} - The source object or a default empty object if validation fails.
*/
function processSourceInput(block) {
const sourceBlock = block.getInputTargetBlock('SOURCE');
let source = {};
if (sourceBlock) {
const sourceJson = Blockly.JSON.blockToCode(sourceBlock);
if (typeof sourceJson === 'object' && sourceJson !== null) {
source = sourceJson;
} else {
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.`);
const sourceBlock = block.getInputTargetBlock('source');
if (sourceBlock && sourceBlock.type === 'source') {
return Blockly.JSON.blockToCode(sourceBlock); // Process source configuration
}
return source;
console.warn(`Invalid or missing source block connected to 'source' input of '${block.type}' block. Using default configuration.`);
return {}; // Default empty object if validation fails
}
/**
* General function to generate market data blocks, handling additional values.
* Uses extractValues to pull optional extra values connected to 'source'.
*/
function generateMarketDataBlock(block, operationType) {
// Process the 'source' input (optional) and additional values
const source = processSourceInput(block);
const operationObject = Blockly.JSON.createOperationObject(operationType, { source: source.values ? source.values[0] : source });
// Initialize output values array
const outputValues = [operationObject];
// Add the remaining values from source if it contains a dynamic_value structure
if (source?.type === 'dynamic_value' && Array.isArray(source.values)) {
outputValues.push(...source.values.slice(1));
}
// Use extractValues to capture additional values beyond the primary source
const additionalValues = Blockly.JSON.extractValues(block, 'source'); // Extract additional values if any
if (additionalValues.length > 0) {
outputValues.push(...additionalValues);
}
return {
type: 'dynamic_value',
values: outputValues
};
}
/**
* Generator for 'current_price' Block
*/
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;
return generateMarketDataBlock(block, 'current_price');
};
/**
* Generator for 'bid_price' Block
*/
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;
return generateMarketDataBlock(block, 'bid_price');
};
/**
* Generator for 'ask_price' Block
*/
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;
return generateMarketDataBlock(block, 'ask_price');
};
/**
@ -89,49 +87,51 @@ export function defineMarketDataGenerators() {
*/
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 candlePart = block.getFieldValue('candle_part') || 'close';
const validCandleParts = ['open', 'high', 'low', 'close'];
const validatedCandlePart = validCandleParts.includes(candlePart) ? candlePart : 'close';
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('last_candle_value', {
source: source,
source,
candle_part: validatedCandlePart
});
// Output as dynamic_value
return operationObject;
const outputValues = [operationObject];
const additionalValues = Blockly.JSON.extractValues(block, 'source'); // Use extractValues for any additional values
if (additionalValues.length > 0) {
outputValues.push(...additionalValues);
}
return {
type: 'dynamic_value',
values: outputValues
};
};
/**
* Generator for 'source' Block
*
* 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 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 fields if necessary
// Example: Ensure timeFrame matches expected patterns
// Skipping detailed validation for brevity
const timeFrame = block.getFieldValue('TF') || '1m';
const exchange = block.getFieldValue('EXC') || 'Binance';
const symbol = block.getFieldValue('SYM') || 'BTCUSDT';
// Create source object
const sourceObject = {
const operationObject = Blockly.JSON.createOperationObject('source', {
time_frame: timeFrame,
exchange: exchange,
symbol: symbol
};
symbol: symbol});
// Output as source object
return sourceObject;
// Prepare output, handling any additional values
const outputValues = [operationObject];
const valuesArray = Blockly.JSON.processValuesInput(block);
if (valuesArray.length > 0) {
outputValues.push(...valuesArray);
}
return {
type: 'dynamic_value',
values: outputValues
};
};
}

View File

@ -34,39 +34,60 @@ export function defineOrderMetricsGenerators() {
* Generator for 'order_volume' Block
*/
Blockly.JSON['order_volume'] = function(block) {
// Retrieve the 'ORDER_TYPE' from the dropdown
const orderType = block.getFieldValue('ORDER_TYPE'); // 'filled' or 'unfilled'
const orderType = block.getFieldValue('ORDER_TYPE');
const validatedOrderType = validateOrderType(orderType);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('order_volume', {
order_type: validatedOrderType
});
// Output as dynamic_value
return operationObject;
// Prepare output, handling any additional values
const outputValues = [operationObject];
const valuesArray = Blockly.JSON.processValuesInput(block);
if (valuesArray.length > 0) {
outputValues.push(...valuesArray);
}
return {
type: 'dynamic_value',
values: outputValues
};
};
/**
* Generator for 'filled_orders' Block
*/
Blockly.JSON['filled_orders'] = function(block) {
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('filled_orders', {});
// Output as dynamic_value
return operationObject;
const outputValues = [operationObject];
const valuesArray = Blockly.JSON.processValuesInput(block);
if (valuesArray.length > 0) {
outputValues.push(...valuesArray);
}
return {
type: 'dynamic_value',
values: outputValues
};
};
/**
* Generator for 'unfilled_orders' Block
*/
Blockly.JSON['unfilled_orders'] = function(block) {
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('unfilled_orders', {});
// Output as dynamic_value
return operationObject;
const outputValues = [operationObject];
const valuesArray = Blockly.JSON.processValuesInput(block);
if (valuesArray.length > 0) {
outputValues.push(...valuesArray);
}
return {
type: 'dynamic_value',
values: outputValues
};
};
/**
@ -74,11 +95,11 @@ export function defineOrderMetricsGenerators() {
*/
Blockly.JSON['order_status'] = function(block) {
// Retrieve the 'ORDER_NAME' field input
const orderName = (block.getFieldValue('ORDER_NAME') || 'undefined_order').trim();
const orderName = (block.getFieldValue('ORDER_NAME') || 'last_order').trim();
// Validate order name
if (!orderName) {
console.warn("Empty 'ORDER_NAME' in 'order_status' block. Using 'undefined_order'.");
console.warn("Empty 'ORDER_NAME' in 'order_status' block. Using 'last_order'.");
}
// Retrieve the selected order status from the dropdown
@ -92,7 +113,7 @@ export function defineOrderMetricsGenerators() {
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('order_status', {
order_name: orderName || 'undefined_order',
order_name: orderName || 'last_order',
status: validatedStatus
});

View File

@ -8,63 +8,89 @@
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.");
// Ensure the base generator is initialized
if (!Blockly.JSON) {
console.error("Blockly.JSON not initialized.");
return;
}
/**
* Helper Function: processNumericInput
* Processes a numeric input and ensures it is a valid number.
* Processes a numeric input and returns the value.
*
* @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.
* @param {*} defaultValue - The default value to use if the input is invalid.
* @returns {*} - The value from the input.
*/
function processNumericInput(block, inputName, defaultValue, minValue = Number.NEGATIVE_INFINITY, maxValue = Number.POSITIVE_INFINITY) {
Blockly.JSON.processNumericInput = function(block, inputName, defaultValue) {
const inputBlock = block.getInputTargetBlock(inputName);
let value = defaultValue;
if (inputBlock) {
const inputJson = Blockly.JSON.blockToCode(inputBlock);
if (typeof inputJson === 'number') {
value = inputJson;
const generator = Blockly.JSON[inputBlock.type];
if (typeof generator === 'function') {
const inputJson = generator(inputBlock);
// Handle dynamic_value
if (inputJson && inputJson.type === 'dynamic_value' && Array.isArray(inputJson.values)) {
const firstValue = inputJson.values[0];
if (typeof firstValue === 'number' || typeof firstValue === 'object') {
return firstValue;
} else {
console.warn(`Invalid value in '${inputName}'. Using default value ${defaultValue}.`);
return defaultValue;
}
} else if (typeof inputJson === 'number' || typeof inputJson === 'object') {
return inputJson;
} else {
console.warn(`Invalid input for '${inputName}'. Using default value ${defaultValue}.`);
return defaultValue;
}
} else {
console.error(`Invalid value for '${inputName}' in '${block.type}' block. Expected a number.`);
console.warn(`No generator found for block type '${inputBlock.type}'. Using default value ${defaultValue}.`);
return defaultValue;
}
} else {
console.warn(`No block connected to '${inputName}' input of '${block.type}' block. Using default value ${defaultValue}.`);
console.warn(`No block connected to '${inputName}'. Using default value ${defaultValue}.`);
return defaultValue;
}
// 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
// Process the 'LEVERAGE' input using processNumericInput
const leverageValue = Blockly.JSON.processNumericInput(block, 'LEVERAGE', 1);
// Validate the leverage value
let leverage = leverageValue;
if (typeof leverageValue === 'number') {
// Enforce limits (e.g., leverage between 1 and 100)
if (leverageValue < 1) {
console.warn(`Leverage value too low. Clamping to 1.`);
leverage = 1;
} else if (leverageValue > 100) {
console.warn(`Leverage value too high. Clamping to 100.`);
leverage = 100;
}
} else if (typeof leverageValue === 'object' && leverageValue !== null) {
// Include the object directly (e.g., if it's an operation)
leverage = leverageValue;
} else {
console.warn(`Invalid leverage value. Defaulting to 1.`);
leverage = 1;
}
// Create operation object
const json = Blockly.JSON.createOperationObject('set_leverage', {
leverage: leverage
});
const json = {
type: 'set_leverage',
inputs: {
LEVERAGE: leverage
}
};
// Output as dynamic_value
// Return the operation object directly
return json;
};
@ -73,10 +99,13 @@ export function defineRiskManagementGenerators() {
*/
Blockly.JSON['current_margin'] = function(block) {
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('current_margin', {});
const json = {
type: 'current_margin',
inputs: {}
};
// Output as dynamic_value
return operationObject;
// Return the operation object directly
return json;
};
/**
@ -84,25 +113,47 @@ export function defineRiskManagementGenerators() {
*/
Blockly.JSON['risk_ratio'] = function(block) {
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('risk_ratio', {});
const json = {
type: 'risk_ratio',
inputs: {}
};
// Output as dynamic_value
return operationObject;
// Return the operation object directly
return json;
};
/**
* Generator for 'max_position_size' Block
*/
Blockly.JSON['max_position_size'] = function(block) {
// Process the 'MAX_SIZE' input
const maxSize = processNumericInput(block, 'MAX_SIZE', 1, 1); // Minimum of 1
// Process the 'MAX_SIZE' input using processNumericInput
const maxSizeValue = Blockly.JSON.processNumericInput(block, 'MAX_SIZE', 1);
let maxSize = maxSizeValue;
if (typeof maxSizeValue === 'number') {
// Enforce limits (e.g., minimum of 1)
if (maxSizeValue < 1) {
console.warn(`Max position size too low. Clamping to 1.`);
maxSize = 1;
}
} else if (typeof maxSizeValue === 'object' && maxSizeValue !== null) {
// Include the object directly (e.g., if it's an operation)
maxSize = maxSizeValue;
} else {
console.warn(`Invalid max position size value. Defaulting to 1.`);
maxSize = 1;
}
// Create operation object
const json = Blockly.JSON.createOperationObject('max_position_size', {
max_size: maxSize
});
const json = {
type: 'max_position_size',
inputs: {
MAX_SIZE: maxSize
}
};
// Output as dynamic_value
// Return the action object directly
return json;
};
}

View File

@ -34,16 +34,21 @@ export function defineTimeMetricsGenerators() {
* Generator for 'time_since_start' Block
*/
Blockly.JSON['time_since_start'] = function(block) {
// Retrieve the selected time unit from the dropdown
const timeUnit = block.getFieldValue('TIME_UNIT') || 'seconds';
const validatedTimeUnit = validateTimeUnit(timeUnit);
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('time_since_start', {
unit: validatedTimeUnit
});
const operationObject = Blockly.JSON.createOperationObject('time_since_start', {unit: validatedTimeUnit} );
// Output as dynamic_value
return operationObject;
// Prepare output, handling any additional values
const outputValues = [operationObject];
const valuesArray = Blockly.JSON.processValuesInput(block);
if (valuesArray.length > 0) {
outputValues.push(...valuesArray);
}
return {
type: 'dynamic_value',
values: outputValues
};
};
}

View File

@ -18,74 +18,119 @@ export function defineTradeMetricsGenerators() {
* Generator for 'active_trades' Block
*/
Blockly.JSON['active_trades'] = function(block) {
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('active_trades', {});
// Output as dynamic_value
return JSON.stringify(operationObject);
// Handle additional values
const outputValues = [operationObject];
const valuesArray = Blockly.JSON.processValuesInput(block);
if (valuesArray.length > 0) {
outputValues.push(...valuesArray);
}
return {
type: 'dynamic_value',
values: outputValues
};
};
/**
* Generator for 'total_trades' Block
*/
Blockly.JSON['total_trades'] = function(block) {
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('total_trades', {});
// Output as dynamic_value
return JSON.stringify(operationObject);
// Handle additional values
const outputValues = [operationObject];
const valuesArray = Blockly.JSON.processValuesInput(block);
if (valuesArray.length > 0) {
outputValues.push(...valuesArray);
}
return {
type: 'dynamic_value',
values: outputValues
};
};
/**
* Generator for 'last_trade_details' Block
*/
Blockly.JSON['last_trade_details'] = function(block) {
// Retrieve the 'DETAILS' field input
const details = block.getFieldValue('DETAILS'); // 'price', 'volume', 'direction'
// Validate the details selection
const details = block.getFieldValue('DETAILS');
const validDetails = ['price', 'volume', 'direction'];
const validatedDetails = validDetails.includes(details) ? details : 'price';
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('last_trade_details', {
details: validatedDetails
});
// Output as dynamic_value
return JSON.stringify(operationObject);
// Handle additional values
const outputValues = [operationObject];
const valuesArray = Blockly.JSON.processValuesInput(block);
if (valuesArray.length > 0) {
outputValues.push(...valuesArray);
}
return {
type: 'dynamic_value',
values: outputValues
};
};
/**
* Generator for 'average_entry_price' Block
*/
Blockly.JSON['average_entry_price'] = function(block) {
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('average_entry_price', {});
// Output as dynamic_value
return JSON.stringify(operationObject);
// Handle additional values
const outputValues = [operationObject];
const valuesArray = Blockly.JSON.processValuesInput(block);
if (valuesArray.length > 0) {
outputValues.push(...valuesArray);
}
return {
type: 'dynamic_value',
values: outputValues
};
};
/**
* Generator for 'unrealized_profit_loss' Block
*/
Blockly.JSON['unrealized_profit_loss'] = function(block) {
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('unrealized_profit_loss', {});
// Output as dynamic_value
return JSON.stringify(operationObject);
// Handle additional values
const outputValues = [operationObject];
const valuesArray = Blockly.JSON.processValuesInput(block);
if (valuesArray.length > 0) {
outputValues.push(...valuesArray);
}
return {
type: 'dynamic_value',
values: outputValues
};
};
/**
* Generator for 'user_active_trades' Block
*/
Blockly.JSON['user_active_trades'] = function(block) {
// Create operation object
const operationObject = Blockly.JSON.createOperationObject('user_active_trades', {});
// Output as dynamic_value
return JSON.stringify(operationObject);
// Handle additional values
const outputValues = [operationObject];
const valuesArray = Blockly.JSON.processValuesInput(block);
if (valuesArray.length > 0) {
outputValues.push(...valuesArray);
}
return {
type: 'dynamic_value',
values: outputValues
};
};
}

View File

@ -35,22 +35,13 @@ export function defineVAFGenerators() {
};
return json;
};
/**
* Generator for 'value_input' Block
*
* 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) {
const values = []; // Initialize the values array
/**
* Recursively processes each block in the chain.
*
* @param {Block} currentBlock - The current block to process.
* @param {Blockly.Block} currentBlock - The current block to process.
* @param {number} currentDepth - The current depth in the recursion.
*/
function processBlock(currentBlock, currentDepth) {
@ -63,17 +54,38 @@ export function defineVAFGenerators() {
// Process 'VALUE' and add to values
const valueJson = processConnectedBlock(currentBlock, 'VALUE');
if (valueJson !== null) values.push(valueJson);
if (valueJson !== null) {
// Flatten any nested dynamic_value objects
if (valueJson && valueJson.type === 'dynamic_value' && Array.isArray(valueJson.values)) {
values.push(...valueJson.values);
} else {
values.push(valueJson);
}
}
// Process 'NEXT' block in the chain
const nextBlock = currentBlock.getInputTargetBlock('NEXT');
if (nextBlock) processBlock(nextBlock, currentDepth + 1);
if (nextBlock) {
const generator = Blockly.JSON[nextBlock.type];
if (typeof generator === 'function') {
const generatedValue = generator(nextBlock);
// Flatten any nested dynamic_value objects
if (generatedValue && generatedValue.type === 'dynamic_value' && Array.isArray(generatedValue.values)) {
values.push(...generatedValue.values);
} else {
values.push(generatedValue);
}
} else {
console.warn(`No generator found for block type "${nextBlock.type}". Skipping this block.`);
}
}
}
/**
* Processes a connected block and retrieves its generated JSON or field value.
*
* @param {Block} block - The current block to process.
* @param {Blockly.Block} block - The current block to process.
* @param {string} inputName - The input name to retrieve.
* @returns {*} The processed value or null if invalid.
*/
@ -83,29 +95,31 @@ export function defineVAFGenerators() {
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;
// Flatten any nested dynamic_value objects
if (generatedValue && generatedValue.type === 'dynamic_value' && Array.isArray(generatedValue.values)) {
return generatedValue.values; // Return the values array directly
}
return 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);
let 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;
fieldValue = block.getFieldValue(inputName);
}
return fieldValue;
}
processBlock(block, 1); // Start processing from the initial block
// Build the JSON object without the skipAdditionalParsing flag
const json = {
type: 'dynamic_value',
values: values.flat(), // Flatten values array to handle nested arrays
skipAdditionalParsing: true // Flag to skip further parsing
values: values.flat() // Flatten values array to handle nested arrays
};
console.log(`Generated JSON for 'value_input' block:`, json);
@ -114,7 +128,6 @@ export function defineVAFGenerators() {
/**
* Generator for 'get_variable' Block
*

View File

@ -49,11 +49,13 @@ export function defineIndicatorBlocks() {
fields: {
NAME: indicatorName,
OUTPUT: selectedOutput
},
inputs: {},
statements: {}
}
};
// Output as dynamic_value
return {
type: 'dynamic_value',
values: [json]
};
return json;
};
// Append the newly created block to the Indicators category in the toolbox