diff --git a/src/Users.py b/src/Users.py
index fb22a00..990291f 100644
--- a/src/Users.py
+++ b/src/Users.py
@@ -267,7 +267,7 @@ class UserAccountManagement(BaseUser):
username = f'guest_{suffix}'
# Check if the username already exists in the database
- if not self.data.get_rows_from_datacache(cache_name='users', filter_vals=[('user_name', username)]):
+ if self.data.get_rows_from_datacache(cache_name='users', filter_vals=[('user_name', username)]).empty:
return username
attempts += 1
diff --git a/src/static/Strategies.js b/src/static/Strategies.js
index e5c1712..421aa90 100644
--- a/src/static/Strategies.js
+++ b/src/static/Strategies.js
@@ -1,379 +1,519 @@
-
class Strategies {
constructor(target_id) {
// The list of strategies.
this.strategies = [];
- // The html element id that displays the strategies.
+ // The HTML element id that displays the strategies.
this.target_id = target_id;
- // The html element that displays the the strategies.
+ // The HTML element that displays the strategies.
this.target = null;
+ this.workspace = null;
+
}
+ // Create the Blockly workspace and define custom blocks
createWorkspace() {
// Dispose of the existing workspace before creating a new one
- if (this.workspace) {this.workspace.dispose();}
+ if (this.workspace) {
+ this.workspace.dispose();
+ }
+ // Define custom blocks before initializing the workspace
+ this.defineCustomBlocks();
+ this.defineIndicatorBlocks();
+
+ // Initialize Blockly workspace
this.workspace = Blockly.inject('blocklyDiv', {
- toolbox: document.getElementById('toolbox'),
- scrollbars: true,
- trashcan: true,
+ toolbox: document.getElementById('toolbox'),
+ scrollbars: true,
+ trashcan: true,
});
+ // Define Python generators after workspace initialization
+ this.definePythonGenerators();
}
+ // Generate Python code from the Blockly workspace and return as JSON
generateStrategyJson() {
- var code = Blockly.JavaScript.workspaceToCode(this.workspace);
- var json = {
- strategy: code
+ // Initialize Python generator with the current workspace
+ Blockly.Python.init(this.workspace);
+
+ // Generate Python code from the Blockly workspace
+ const pythonCode = Blockly.Python.workspaceToCode(this.workspace);
+
+ // Create a strategy object with generated code
+ const json = {
+ name: document.getElementById('name_box').value,
+ code: pythonCode // This is the generated Python code
};
+
return JSON.stringify(json);
}
- // Call to display Create new signal dialog.
- open_form() { document.getElementById("new_strat_form").style.display = "grid"; }
- // Call to hide Create new signal dialog.
- close_form() { document.getElementById("new_strat_form").style.display = "none"; }
- submit(){
- /*
- - Collect the data from the form fields and
- create a json object representing a strategy.
- - Append the strategy to a local list of strategies.
- - Update the display output.
- - Send the strategy info to the server.
- */
- let strat = {};
- strat.name = document.getElementById('stg_name').value;
- if (strat.name == ''){
- alert('Please provide a name.');
- return;
+
+ // Show the "Create New Strategy" form (open the workspace)
+ open_form() {
+ const formElement = document.getElementById("new_strat_form");
+
+ // Check if the form element exists
+ if (formElement) {
+ formElement.style.display = "grid"; // Open the form
+ } else {
+ console.error('Form element "new_strat_form" not found.');
}
- // The type of strategy will determine underlying feature of its execution.
- strat.type = document.getElementById('strat_type').value;
- // Whether the strategy is bullish or bearish.
- strat.side = document.getElementById('trade_in_side').value;
- // Position size for each trade the strategy executes.
- strat.trade_amount = document.getElementById('trade_amount').value;
- // The maximum combined position allowed.
- strat.max_position = document.getElementById('strgy_total').value;
- // The fee the exchange charges per trade.
- strat.trading_fee = document.getElementById('fee').value;
- // The maximum allowable loss the strategy can endure in combined trades.
- // Before trading out and terminating execution.
- strat.max_loss = document.getElementById('max_loss').value;
- // The trading pair being exchanged.
- strat.symbol = window.UI.data.trading_pair;
- // The combined profit or loss including trading fees.
- strat.net_pl = 0;
- // The combined profit or loss.
- strat.gross_pl = 0;
- // The quantity of the traded assets being held.
- strat.combined_position = 0;
- // Opening value of the asset.
- strat.opening_value = 0;
- // The current value of the asset.
- strat.current_value = 0;
- // Whether or not the strategy has begun trading.
- strat.active = false;
- // The conditions that must be met before trading in.
- strat.trd_in_conds = {};
- let conds = Array.from(document.querySelectorAll('#trade_in_cond>li'));
- for (let cond of conds){
- let json_obj = JSON.parse(cond.innerHTML);
- strat.trd_in_conds[json_obj.Trigger] = json_obj.Value;
- }
- // The conditions that must be met before taking profit.
- let take_profit = {};
- take_profit.typ = document.getElementById('prof_typ').value;
- if (take_profit.typ == 'conditional'){
- take_profit.trig = document.getElementById('prof_trig').value;
- take_profit.val = document.getElementById('prof_trigVal').value;
- }else{
- take_profit.val = document.getElementById('prof_val').value;
- }
- strat.take_profit = take_profit;
-
- // The conditions that must be met before taking a loss.
- let stop_loss = {};
- stop_loss.typ = document.getElementById('loss_typ').value;
- if ( stop_loss.typ == 'conditional' ){
- stop_loss.trig = document.getElementById('loss_trig').value;
- stop_loss.val = document.getElementById('loss_trigVal').value;
- }else{
- stop_loss.val = document.getElementById('loss_val').value;
- }
- strat.stop_loss = stop_loss;
-
- // Add the strategy to the instance list.
- this.strategies.push(strat);
-
- // Add the strategy to display.
- this.update_html();
-
- // Send the new strategy to the server.
- window.UI.data.comms.sendToApp( "new_strategy", strat);
-
- // Close the html form.
- this.close_form();
}
- update_received(stg_updts){
- if ( 'cmd' in stg_updts) {
- let alert =
- window.UI.alerts.publish_alerts('strategy', stg_updts);
- this.executeCmd(stg_updts.cmd);
+
+ // Hide the "Create New Strategy" form
+ close_form() {
+ const formElement = document.getElementById("new_strat_form");
+
+ if (formElement) {
+ formElement.style.display = "none"; // Close the form
+ } else {
+ console.error('Form element "new_strat_form" not found.');
}
- console.log('recieved stategy update.');
- console.log(stg_updts);
}
- set_data(strats){
- for (let strat of strats){
- // Add the strategy to the instance list.
- this.strategies.push(JSON.parse(strat));
+
+ // Submit the strategy and send it to the server
+ submit() {
+ // Generate the Python code as JSON
+ const strategyJson = this.generateStrategyJson();
+
+ // Send the strategy to the server
+ fetch('/new_strategy', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: strategyJson
+ })
+ .then(response => response.json())
+ .then(data => console.log('Strategy submitted successfully:', data))
+ .catch(error => console.error('Error submitting strategy:', error));
+ }
+
+ // Update the display of the created strategies
+ update_html() {
+ let stratsHtml = '';
+ const onClick = "window.UI.strats.del(this.value);";
+ for (let strat of this.strategies) {
+ let deleteButton = ``;
+ stratsHtml += `
${deleteButton}
${JSON.stringify(strat, null, 2)}
`;
}
- // Add the strategy to display.
- this.update_html();
+ document.getElementById(this.target_id).innerHTML = stratsHtml;
}
- open_stg_form(){
+
+ // Open the form and create the Blockly workspace
+ open_stg_form() {
this.open_form();
- this.createWorkspace()
+ this.createWorkspace();
}
- clear_innerHTML(el){
- el.innerHTML="";
- }
- dropDown(name, label, parent, options){
- /* Create a html selection element and append it to a parent element.
- name: name and id of the element.
- label: text displayed beside the selection list element.
- parent: the html element to append to.
- options: An array of selection options.
- */
- let lbl = document.createElement("label");
- lbl.for = name;
- lbl.id = name + '_lbl';
- lbl.innerHTML = label;
- let select = document.createElement("select");
- select.id = name;
- select.name = name;
- for(let option of options){
- select.innerHTML += '';
- }
- parent.appendChild(lbl);
- parent.appendChild(select);
- }
- val_input(name, label, parent, min, max, i_val, style = null){
- /* Create an input element.
- name: name and id of the element.
- label: text displayed beside the element.
- parent: the html element to append to.
- min, max, i_val: Range and initialization values.
- */
- let lbl = document.createElement("label");
- lbl.for = name;
- lbl.id = name + '_lbl';
- lbl.innerHTML = label;
- let input = document.createElement("input");
- input.name = name;
- input.id = name;
- input.type = 'number';
- input.min = min;
- if (max) {input.max = max;}
- input.value = i_val;
- if (style){
- input.style = style;
- }
- parent.appendChild(lbl);
- parent.appendChild(input);
- }
- hideShowTags(elements, cmd){
- /* Hides or shows 1 or many elements.
- Receives either string or array of string.
- */
- if (typeof(elements) == 'string'){
- elements = [elements];
- }
- for(let el of elements){
- if(cmd== 'hide'){document.getElementById(el).style.display = 'none';}
- if(cmd== 'show'){document.getElementById(el).style.display = 'inline-block';}
- }
- }
- fill_field(field, value){
- if (field == 'strat_opt'){
- let options = document.getElementById('strat_opt');
-
- if (value == 'in-out'){
- // Clear previous content.
- this.clear_innerHTML(options);
-
- // Add a horizontal rule.
- options.appendChild(document.createElement('hr'));
-
- //Create a drop down for buy/sell option
- this.dropDown('trade_in_side','Side:', options, ['buy','sell']);
-
- //Create an input for the margin. (1-100)
- this.val_input('margin_select', 'Margin:', options, 1,100, 50);
-
- // Add a line break.
- options.appendChild( document.createElement('br') );
-
- //Create an input for the amount. (1-*)
- this.val_input('trade_amount', 'Trade amount:', options, 1, 0, 1,'width: 54px;');
-
- //Create an input for the amount. (1-*)
- this.val_input('strgy_total', 'Strategy total:', options, 1, 0, 10, 'width: 54px;');
-
- // Add a line break.
- options.appendChild( document.createElement('br') );
-
- //Create an input for the fee. (0.01-1)
- this.val_input('fee', 'Trading Fee:', options, 0.001, 1, 0.025);
-
- // Create a un ordered list to hold the conditions of trading in.
- let ul_in_cond = document.createElement('ul');
- ul_in_cond.id ='trade_in_cond';
-
- // Create a submit button for the conditions.
- let add_cond_btn = document.createElement('button');
- add_cond_btn.setAttribute('id','add_cond_btn');
- add_cond_btn.setAttribute('type','button');
- add_cond_btn.innerHTML = "Add Condition";
- add_cond_btn.onclick = function () {
- let li = document.createElement('li');
- li.innerHTML = JSON.stringify({ Trigger: trigger.value, Value: trigVal.value });
- ul_in_cond.appendChild(li);
- };
-
- // Add a horizontal rule.
- options.appendChild(document.createElement('hr'));
-
- //Create a drop down for trigger options
- // Get the signal options from the signal instance.
- let ops = [];
- for (let signal of window.UI.signals.signals) {ops.push(signal.name);}
- this.dropDown('trigger','Trigger Signal:', options, ops);
-
-
- //Create a drop down for trigger value.
- this.dropDown('trigVal','Trigger Value:', options, ['true', 'false', 'changed']);
-
- // Add the submit btn and the list to the dom.
- options.appendChild(add_cond_btn);
- options.appendChild(ul_in_cond);
-
- // Add a horizontal rule.
- options.appendChild(document.createElement('hr'));
-
- //Create a drop down for take profit type.
- this.dropDown('prof_typ','Profit type:', options, ['value', 'conditional']);
- // Add and onchange function to show and hide some options.
- let that = this;
- document.getElementById('prof_typ').onchange= function(){
- if (this.value == 'value'){
- that.hideShowTags(['prof_val_lbl','prof_val'], 'show');
- that.hideShowTags(['prof_trig_lbl', 'prof_trig', 'prof_trigVal_lbl', 'prof_trigVal'], 'hide');
- }
- else if (this.value == 'conditional'){
- that.hideShowTags(['prof_val_lbl','prof_val'], 'hide');
- that.hideShowTags(['prof_trig_lbl', 'prof_trig', 'prof_trigVal_lbl', 'prof_trigVal'], 'show');
- }
- };
-
- // Add a line break.
- options.appendChild(document.createElement('br'));
-
- // Create an input for take profit value.
- this.val_input('prof_val', 'Profit %:', options, 1,100, 50);
- // Set display to visible.
- this.hideShowTags(['prof_val_lbl','prof_val'], 'show');
-
- // Create an input for take profit signal trigger.
- // Get the signal options from the signal instance.
- ops = []; for (let signal of window.UI.signals.signals) {ops.push(signal.name);}
- this.dropDown('prof_trig','Profit trigger:', options, ops);
- // Set display to hidden.
- this.hideShowTags(['prof_trig_lbl','prof_trig'], 'hide');
-
- //Create a drop down for take profit trigger value.
- this.dropDown('prof_trigVal','Trigger Value:', options, ['true', 'false', 'changed']);
- // Set display to hidden.
- this.hideShowTags(['prof_trigVal_lbl','prof_trigVal'], 'hide');
-
- // Add a line break.
- options.appendChild(document.createElement('br'));
- // Add a horizontal rule.
- options.appendChild(document.createElement('hr'));
-
- // Create an input for tolerable loss.
- this.val_input('max_loss', 'Max loss :', options, 1,100, 50);
- // Add a line break.
- options.appendChild(document.createElement('br'));
-
- //Create a drop down for Stop Loss type.
- this.dropDown('loss_typ','Stop-Loss type:', options, ['value', 'conditional']);
- // Add and onchange function to show and hide some options.
- document.getElementById('loss_typ').onchange= function(){
- if (this.value == 'value'){
- that.hideShowTags(['loss_val_lbl','loss_val'], 'show');
- that.hideShowTags(['loss_trig_lbl', 'loss_trig', 'loss_trigVal_lbl', 'loss_trigVal'], 'hide');
- }
- else if (this.value == 'conditional'){
- that.hideShowTags(['loss_val_lbl','loss_val'], 'hide');
- that.hideShowTags(['loss_trig_lbl', 'loss_trig', 'loss_trigVal_lbl', 'loss_trigVal'], 'show');
- }
- };
-
- // Add a line break.
- options.appendChild( document.createElement('br') );
-
- // Create an input for stop loss value.
- this.val_input('loss_val', 'Loss %:', options, 1,100, 50);
- // Set display to visible.
- this.hideShowTags(['loss_val_lbl','loss_val'], 'show');
-
- // Create an input for take profit signal trigger.
- // Get the signal options from the signal instance.
- ops = []; for (let signal of window.UI.signals.signals) {ops.push(signal.name);}
- this.dropDown('loss_trig','Stop-Loss trigger:', options, ops);
- // Set display to hidden.
- this.hideShowTags(['loss_trig_lbl','loss_trig'], 'hide');
-
- //Create a drop down for take profit trigger value.
- this.dropDown('loss_trigVal','Loss Value:', options, ['true', 'false', 'changed']);
- // Set display to hidden.
- this.hideShowTags(['loss_trigVal_lbl','loss_trigVal'], 'hide');
-
- // Add a line break.
- options.appendChild(document.createElement('br'));
- // Add a horizontal rule.
- options.appendChild(document.createElement('hr'));
-
- }
- if (value == 'incremental_profits'){
- options.innerHTML="Incremental_profits -> not done.";
- }
- if (value == 'swing'){
- options.innerHTML="swing -> not done.";
- }
- }
- }
- initialize(){
- // This is called after the html document has been parsed.
- this.target = document.getElementById(this.target_id);
- // Send a request to the server for any loaded data.
- window.UI.data.comms.sendToApp('request', 'strategies');
- }
- del(name){
+ // Delete a strategy by its name
+ del(name) {
window.UI.data.comms.sendToApp('delete_strategy', name);
- // Get the child element node
+ // Remove the strategy from the UI
let child = document.getElementById(name + '_item');
- // Remove the child element from the document
child.parentNode.removeChild(child);
}
- update_html(){
- let strats ='';
- let on_click = " window.UI.strats.del(this.value);";
- for (let strat of this.strategies){
- let button ="";
- strats += "
" + button + "
" + JSON.stringify(strat) + "
";
+
+ // Initialize the Strategies UI component
+ initialize() {
+ this.target = document.getElementById(this.target_id);
+ if (!this.target) {
+ console.error('Target element', this.target_id, 'not found.');
+ }
+ }
+ // Define Blockly blocks dynamically based on indicators
+ defineIndicatorBlocks() {
+ const indicatorOutputs = window.UI.indicators.getIndicatorOutputs();
+ const toolboxCategory = document.querySelector('#toolbox category[name="Indicators"]');
+
+ for (let indicatorName in indicatorOutputs) {
+ const outputs = indicatorOutputs[indicatorName];
+
+ // Define the block for this indicator
+ Blockly.defineBlocksWithJsonArray([{
+ "type": indicatorName,
+ "message0": `${indicatorName} %1`,
+ "args0": [
+ {
+ "type": "field_dropdown",
+ "name": "OUTPUT",
+ "options": outputs.map(output => [output, output])
+ }
+ ],
+ "output": "Number",
+ "colour": 230,
+ "tooltip": `Select the ${indicatorName} output`,
+ "helpUrl": ""
+ }]);
+
+ // Define how this block will generate Python code
+ Blockly.Python[indicatorName] = Blockly.Python.forBlock[indicatorName] = function(block) {
+ const selectedOutput = block.getFieldValue('OUTPUT');
+ const code = `get_${indicatorName.toLowerCase()}_value('${selectedOutput}')`;
+ return [code, Blockly.Python.ORDER_ATOMIC];
+ };
+
+ // Append dynamically created blocks to the Indicators category in the toolbox
+ const blockElement = document.createElement('block');
+ blockElement.setAttribute('type', indicatorName);
+ toolboxCategory.appendChild(blockElement);
}
- this.target.innerHTML = strats;
}
+ // Define custom Blockly blocks and Python code generation
+ defineCustomBlocks() {
+ // Custom block for retrieving last candle values
+ Blockly.defineBlocksWithJsonArray([
+ {
+ "type": "last_candle_value",
+ "message0": "Last candle %1 value",
+ "args0": [
+ {
+ "type": "field_dropdown",
+ "name": "CANDLE_PART",
+ "options": [
+ ["Open", "open"],
+ ["High", "high"],
+ ["Low", "low"],
+ ["Close", "close"]
+ ]
+ }
+ ],
+ "output": "Number",
+ "colour": 230,
+ "tooltip": "Get the value of the last candle.",
+ "helpUrl": ""
+ }
+ ]);
+
+ // Comparison Block (for Candle Close > Candle Open, etc.)
+ Blockly.defineBlocksWithJsonArray([
+ {
+ "type": "comparison",
+ "message0": "%1 %2 %3",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "LEFT"
+ },
+ {
+ "type": "field_dropdown",
+ "name": "OPERATOR",
+ "options": [
+ [">", ">"],
+ ["<", "<"],
+ ["==", "=="]
+ ]
+ },
+ {
+ "type": "input_value",
+ "name": "RIGHT"
+ }
+ ],
+ "inputsInline": true,
+ "output": "Boolean",
+ "colour": 160,
+ "tooltip": "Compare two values.",
+ "helpUrl": ""
+ }
+ ]);
+
+ Blockly.defineBlocksWithJsonArray([{
+ "type": "trade_action",
+ "message0": "if %1 then %2 with Stop Loss %3 and Take Profit %4 (Options) %5",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "CONDITION",
+ "check": "Boolean"
+ },
+ {
+ "type": "field_dropdown",
+ "name": "TRADE_TYPE",
+ "options": [
+ ["Buy", "buy"],
+ ["Sell", "sell"]
+ ]
+ },
+ {
+ "type": "input_value",
+ "name": "STOP_LOSS",
+ "check": "Number"
+ },
+ {
+ "type": "input_value",
+ "name": "TAKE_PROFIT",
+ "check": "Number"
+ },
+ {
+ "type": "input_statement",
+ "name": "TRADE_OPTIONS",
+ "check": "trade_option" // This will accept the chain of trade options
+ }
+ ],
+ "previousStatement": null,
+ "nextStatement": null,
+ "colour": 230,
+ "tooltip": "Executes a trade with optional stop loss, take profit, and trade options",
+ "helpUrl": ""
+ }]);
+
+
+ // Stop Loss Block
+ Blockly.defineBlocksWithJsonArray([{
+ "type": "stop_loss",
+ "message0": "Stop Loss %1",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "STOP_LOSS",
+ "check": "Number"
+ }
+ ],
+ "output": "Number",
+ "colour": 230,
+ "tooltip": "Sets a stop loss value",
+ "helpUrl": ""
+ }]);
+
+ // Take Profit Block
+ Blockly.defineBlocksWithJsonArray([{
+ "type": "take_profit",
+ "message0": "Take Profit %1",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "TAKE_PROFIT",
+ "check": "Number"
+ }
+ ],
+ "output": "Number",
+ "colour": 230,
+ "tooltip": "Sets a take profit value",
+ "helpUrl": ""
+ }]);
+ // Logical AND Block
+ Blockly.defineBlocksWithJsonArray([{
+ "type": "logical_and",
+ "message0": "%1 AND %2",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "LEFT",
+ "check": "Boolean"
+ },
+ {
+ "type": "input_value",
+ "name": "RIGHT",
+ "check": "Boolean"
+ }
+ ],
+ "inputsInline": true,
+ "output": "Boolean",
+ "colour": 210,
+ "tooltip": "Logical AND of two conditions",
+ "helpUrl": ""
+ }]);
+ // Logical OR Block
+ Blockly.defineBlocksWithJsonArray([{
+ "type": "logical_or",
+ "message0": "%1 OR %2",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "LEFT",
+ "check": "Boolean"
+ },
+ {
+ "type": "input_value",
+ "name": "RIGHT",
+ "check": "Boolean"
+ }
+ ],
+ "inputsInline": true,
+ "output": "Boolean",
+ "colour": 210,
+ "tooltip": "Logical OR of two conditions",
+ "helpUrl": ""
+ }]);
+ // "is" Block
+ Blockly.defineBlocksWithJsonArray([{
+ "type": "is_true",
+ "message0": "%1 is true",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "CONDITION",
+ "check": "Boolean"
+ }
+ ],
+ "output": "Boolean",
+ "colour": 160,
+ "tooltip": "Checks if the condition is true",
+ "helpUrl": ""
+ }]);
+ // Order Type Block with Limit Price
+ Blockly.defineBlocksWithJsonArray([{
+ "type": "order_type",
+ "message0": "Order Type %1 %2",
+ "args0": [
+ {
+ "type": "field_dropdown",
+ "name": "ORDER_TYPE",
+ "options": [
+ ["Market", "market"],
+ ["Limit", "limit"]
+ ]
+ },
+ {
+ "type": "input_value",
+ "name": "LIMIT_PRICE", // Input for limit price when Limit order is selected
+ "check": "Number"
+ }
+ ],
+ "previousStatement": "trade_option",
+ "nextStatement": "trade_option",
+ "colour": 230,
+ "tooltip": "Select order type (Market or Limit) with optional limit price",
+ "helpUrl": ""
+ }]);
+ Blockly.defineBlocksWithJsonArray([{
+ "type": "value_input",
+ "message0": "Value %1",
+ "args0": [
+ {
+ "type": "field_number",
+ "name": "VALUE",
+ "value": 0,
+ "min": 0
+ }
+ ],
+ "output": "Number",
+ "colour": 230,
+ "tooltip": "Enter a numerical value",
+ "helpUrl": ""
+ }]);
+ // Time In Force (TIF) Block
+ Blockly.defineBlocksWithJsonArray([{
+ "type": "time_in_force",
+ "message0": "Time in Force %1",
+ "args0": [
+ {
+ "type": "field_dropdown",
+ "name": "TIF",
+ "options": [
+ ["GTC (Good Till Canceled)", "gtc"],
+ ["FOK (Fill or Kill)", "fok"],
+ ["IOC (Immediate or Cancel)", "ioc"]
+ ]
+ }
+ ],
+ "previousStatement": "trade_option",
+ "nextStatement": "trade_option",
+ "colour": 230,
+ "tooltip": "Select time in force for the order",
+ "helpUrl": ""
+ }]);
+ console.log('Custom blocks defined');
+
+ }
+
+ // Define Python generators for custom blocks
+ definePythonGenerators() {
+ // Last candle value to Python code
+ Blockly.Python['last_candle_value'] = Blockly.Python.forBlock['last_candle_value'] = function(block) {
+ var candlePart = block.getFieldValue('CANDLE_PART');
+ var code = `market.get_last_candle_value('${candlePart}')`;
+ return [code, Blockly.Python.ORDER_ATOMIC];
+ };
+
+ // Comparison block to Python code
+ Blockly.Python['comparison'] = Blockly.Python.forBlock['comparison'] = function(block) {
+ const left = Blockly.Python.valueToCode(block, 'LEFT', Blockly.Python.ORDER_ATOMIC);
+ const right = Blockly.Python.valueToCode(block, 'RIGHT', Blockly.Python.ORDER_ATOMIC);
+ const operator = block.getFieldValue('OPERATOR');
+ return [left + ' ' + operator + ' ' + right, Blockly.Python.ORDER_ATOMIC];
+ };
+
+ // Logical OR block to Python code
+ Blockly.Python['logical_or'] = Blockly.Python.forBlock['logical_or'] = function(block) {
+ var left = Blockly.Python.valueToCode(block, 'LEFT', Blockly.Python.ORDER_ATOMIC);
+ var right = Blockly.Python.valueToCode(block, 'RIGHT', Blockly.Python.ORDER_ATOMIC);
+ var code = `${left} or ${right}`;
+ return [code, Blockly.Python.ORDER_ATOMIC];
+ };
+
+ // Logical AND block to Python code
+ Blockly.Python['logical_and'] = Blockly.Python.forBlock['logical_and'] = function(block) {
+ var left = Blockly.Python.valueToCode(block, 'LEFT', Blockly.Python.ORDER_ATOMIC);
+ var right = Blockly.Python.valueToCode(block, 'RIGHT', Blockly.Python.ORDER_ATOMIC);
+ var code = `${left} and ${right}`;
+ return [code, Blockly.Python.ORDER_ATOMIC];
+ };
+
+ // Stop Loss block to Python code
+ Blockly.Python['stop_loss'] = Blockly.Python.forBlock['stop_loss'] = function(block) {
+ var stopLoss = Blockly.Python.valueToCode(block, 'STOP_LOSS', Blockly.Python.ORDER_ATOMIC);
+ return [stopLoss, Blockly.Python.ORDER_ATOMIC];
+ };
+
+ // Take Profit block to Python code
+ Blockly.Python['take_profit'] = Blockly.Python.forBlock['take_profit'] = function(block) {
+ var takeProfit = Blockly.Python.valueToCode(block, 'TAKE_PROFIT', Blockly.Python.ORDER_ATOMIC);
+ return [takeProfit, Blockly.Python.ORDER_ATOMIC];
+ };
+
+ // Is True block to Python code
+ Blockly.Python['is_true'] = Blockly.Python.forBlock['is_true'] = function(block) {
+ var condition = Blockly.Python.valueToCode(block, 'CONDITION', Blockly.Python.ORDER_ATOMIC);
+ var code = `${condition}`;
+ return [code, Blockly.Python.ORDER_ATOMIC];
+ };
+
+ // Trade Action block to Python code
+ Blockly.Python['trade_action'] = Blockly.Python.forBlock['trade_action'] = function(block) {
+ alert('Generating Python for trade_action block'); // Debugging alert
+
+ var condition = Blockly.Python.valueToCode(block, 'CONDITION', Blockly.Python.ORDER_ATOMIC);
+ var tradeType = block.getFieldValue('TRADE_TYPE');
+ var stopLoss = Blockly.Python.valueToCode(block, 'STOP_LOSS', Blockly.Python.ORDER_ATOMIC) || 'None';
+ var takeProfit = Blockly.Python.valueToCode(block, 'TAKE_PROFIT', Blockly.Python.ORDER_ATOMIC) || 'None';
+ var tradeOptions = Blockly.Python.statementToCode(block, 'TRADE_OPTIONS').trim();
+
+ var code = `if ${condition}:\n`;
+ code += ` ${tradeType}_order(stop_loss=${stopLoss}, take_profit=${takeProfit}`;
+
+ // Include trade options if they are set
+ if (tradeOptions) {
+ code += `, ${tradeOptions}`;
+ }
+
+ code += `)\n`;
+ return code;
+ };
+
+ // Order Type block to Python code
+ Blockly.Python['order_type'] = Blockly.Python.forBlock['order_type'] = function(block) {
+ var orderType = block.getFieldValue('ORDER_TYPE');
+ var limitPrice = Blockly.Python.valueToCode(block, 'LIMIT_PRICE', Blockly.Python.ORDER_ATOMIC) || 'None';
+
+ // If it's a limit order, include the limit price in the output
+ if (orderType === 'limit') {
+ return `order_type='limit', limit_price=${limitPrice}`;
+ } else {
+ return `order_type='market'`;
+ }
+ };
+
+ // Value Input block to Python code
+ Blockly.Python['value_input'] = Blockly.Python.forBlock['value_input'] = function(block) {
+ var value = block.getFieldValue('VALUE');
+ return [value.toString(), Blockly.Python.ORDER_ATOMIC]; // Returning both value and precedence
+ };
+
+ // Time in Force block to Python code
+ Blockly.Python['time_in_force'] = Blockly.Python.forBlock['time_in_force'] = function(block) {
+ var tif = block.getFieldValue('TIF');
+ return `tif='${tif}'`;
+ };
+
+ console.log('Python generators defined');
+ }
}
diff --git a/src/static/general.js b/src/static/general.js
index 33ea9c9..5400ae2 100644
--- a/src/static/general.js
+++ b/src/static/general.js
@@ -1,84 +1,59 @@
-
-class User_Interface{
-/* This contains all the code for our User interface.
- The code is separated into classes that maintain and
- provide the data and functionality of each panel of
- the UI. */
+class User_Interface {
constructor() {
+ // Initialize all components needed by the user interface
+ this.strats = new Strategies('strats_display'); // Handles strategies display and interaction
+ this.exchanges = new Exchanges(); // Manages exchange-related data
+ this.data = new Data(); // Stores and maintains application-wide data
+ this.controls = new Controls(); // Handles user controls
+ this.signals = new Signals(this.data.indicators); // Manages signals used in strategies
+ this.alerts = new Alerts("alert_list"); // Manages the alerts system
+ this.trade = new Trade(); // Manages trade-related data and operations
+ this.users = new Users(); // Handles user login and account details
+ this.indicators = new Indicators(this.data.comms); // Manages indicators and interaction with charts
- /*The Exchanges class is is responsible for maintaining
- and retrieving exchanges data from the server. Also
- issuing exchange related commands to the server.*/
- this.exchanges = new Exchanges();
-
- /* Data object is responsible for fetching and maintaining
- up-to-date configurable and variable data for the UI */
- this.data = new Data();
-
- /* The object that handles the interface controls.*/
- this.controls = new Controls();
-
- /* The object that handles the signals interface.*/
- this.signals = new Signals(this.data.indicators);
-
- /* The object that handles alerts. Pass in the html
- element that will hold the list of alerts*/
- this.alerts = new Alerts("alert_list");
-
- /* The object that handles alerts. Pass in the html
- element that will hold the strategies interface.*/
- this.strats = new Strategies("strats_display");
-
- /* The object that handles trades. Pass in the html
- element that will hold the trade interface.*/
- this.trade = new Trade();
-
- /* The object that handles user log in */
- this.users = new Users();
-
- /* Indicators contains methods that interact with indicator
- related menus and update indicator output in charts and panels. */
- this.indicators = new Indicators(this.data.comms);
-
- /* Register an Indicator callback method to receive updates from the data class. */
+ // Register a callback function for when indicator updates are received from the data object
this.data.registerCallback_i_updates(this.indicators.update);
- window.addEventListener('load', function () {
- /* This javascript files are loaded before the entire
- HTML document gets parsed. Place initializers here
- to be executed after the document is loaded. */
+ // Initialize all components after the page has loaded and Blockly is ready
+ this.initializeAll();
+ }
- /* Charts object is responsible for maintaining the
- data visualisation area in the UI. */
+ /**
+ * Initializes all components after the DOM has loaded.
+ * This includes initializing charts, signals, strategies, and other interface components.
+ * Components that rely on external libraries like Blockly are initialized after they are ready.
+ */
+ initializeAll() {
+ // Use an arrow function to preserve the value of 'this' (User_Interface instance)
+ window.addEventListener('load', () => {
+ // Initialize the Charts component with the required data
let chart_init_data = {
- chart1_id : window.UI.data.chart1_id,
- chart2_id : window.UI.data.chart2_id,
- chart3_id : window.UI.data.chart3_id,
- trading_pair : window.UI.data.trading_pair,
- price_history : window.UI.data.price_history
- }
- window.UI.charts = new Charts(chart_init_data);
-
- /* Pass the initialization data to indicators. */
- let ind_init_data = {
- // A list of indicator object available for display.
- indicators: window.UI.data.indicators,
- // Output data - indicator specific format. Example: list of indexed value, bools, or colours codes.
- indicator_data: window.UI.data.indicator_data
+ chart1_id: this.data.chart1_id, // ID of the first chart element
+ chart2_id: this.data.chart2_id, // ID of the second chart element
+ chart3_id: this.data.chart3_id, // ID of the third chart element
+ trading_pair: this.data.trading_pair, // Active trading pair
+ price_history: this.data.price_history // Historical price data
};
- // Pass in a reference to Charts so the indicators can update them directly
- window.UI.indicators.addToCharts(window.UI.charts, ind_init_data);
+ this.charts = new Charts(chart_init_data); // Initialize the charts
- /* Request Initialization data from the server. All incoming
- data is forwarded to any registered callback functions. */
- window.UI.signals.request_signals();
- window.UI.alerts.set_target();
- window.UI.strats.initialize();
- window.UI.controls.init_TP_selector();
- window.UI.trade.initialize();
- window.UI.exchanges.initialize();
+ // Initialize indicators and link them to the charts
+ let ind_init_data = {
+ indicators: this.data.indicators, // List of indicators
+ indicator_data: this.data.indicator_data // Data for rendering indicators
+ };
+ this.indicators.addToCharts(this.charts, ind_init_data); // Add indicators to the charts
+
+ // Initialize various components that don't depend on external libraries
+ this.signals.request_signals(); // Request and initialize trading signals
+ this.alerts.set_target(); // Set up alert notifications
+ this.controls.init_TP_selector(); // Initialize trade parameter selectors
+ this.trade.initialize(); // Initialize trade-related data and UI elements
+ this.exchanges.initialize(); // Initialize exchange-related data
+
+ this.strats.initialize(); // Initialize strategies once Blockly is ready
});
-
}
}
-UI = new User_Interface();
+
+// Instantiate the User_Interface class and assign it to a global variable for access in other parts of the app
+window.UI = new User_Interface();
diff --git a/src/static/indicators.js b/src/static/indicators.js
index 5f226c6..aab2712 100644
--- a/src/static/indicators.js
+++ b/src/static/indicators.js
@@ -47,6 +47,7 @@ class Indicator {
this.name = name;
this.lines = [];
this.hist = [];
+ this.outputs = [];
}
static getIndicatorConfig() {
@@ -162,6 +163,7 @@ class SMA extends Indicator {
constructor(name, chart, color, lineWidth = 2) {
super(name);
this.addLine('line', chart, color, lineWidth);
+ this.outputs = ['value'];
}
static getIndicatorConfig() {
@@ -201,6 +203,7 @@ class RSI extends Indicator {
}
let chart = charts.chart2;
this.addLine('line', chart, color, lineWidth);
+ this.outputs = ['value'];
}
static getIndicatorConfig() {
@@ -231,6 +234,7 @@ class MACD extends Indicator {
this.addLine('line_m', chart, color_1, lineWidth);
this.addLine('line_s', chart, color_2, lineWidth);
this.addHist(name, chart);
+ this.outputs = ['macd', 'signal', 'hist'];
}
static getIndicatorConfig() {
@@ -286,6 +290,7 @@ class ATR extends Indicator {
init(data) {
this.updateDisplay(this.name, data.at(-1).value, 'value');
+ this.outputs = ['value'];
}
update(data) {
@@ -300,6 +305,7 @@ class Volume extends Indicator {
super(name);
this.addHist(name, chart);
this.hist[name].applyOptions({ scaleMargins: { top: 0.95, bottom: 0.0 } });
+ this.outputs = ['value'];
}
static getIndicatorConfig() {
@@ -325,6 +331,7 @@ class Bolenger extends Indicator {
this.addLine('line_u', chart, color_1, lineWidth);
this.addLine('line_m', chart, color_2, lineWidth);
this.addLine('line_l', chart, color_3, lineWidth);
+ this.outputs = ['upper', 'middle', 'lower'];
}
static getIndicatorConfig() {
@@ -396,6 +403,19 @@ class Indicators {
}
}
+ // Method to retrieve outputs for each indicator
+ getIndicatorOutputs() {
+ let indicatorOutputs = {};
+ for (let name in this.i_objs) {
+ if (this.i_objs[name].outputs) {
+ indicatorOutputs[name] = this.i_objs[name].outputs;
+ } else {
+ indicatorOutputs[name] = ['value']; // Default output if not defined
+ }
+ }
+ return indicatorOutputs;
+ }
+
addToCharts(charts, idata){
/*
Receives indicator data, creates and stores the indicator
diff --git a/src/templates/index.html b/src/templates/index.html
index c37f677..0de3e2f 100644
--- a/src/templates/index.html
+++ b/src/templates/index.html
@@ -8,7 +8,10 @@
-
+
+
+
+