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