The strategy section is generating code. Implemented code is mostly functional.

This commit is contained in:
Rob 2024-09-23 09:55:27 -03:00
parent 38de03c022
commit 034b8ab925
6 changed files with 600 additions and 471 deletions

View File

@ -267,7 +267,7 @@ class UserAccountManagement(BaseUser):
username = f'guest_{suffix}' username = f'guest_{suffix}'
# Check if the username already exists in the database # 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 return username
attempts += 1 attempts += 1

View File

@ -1,379 +1,519 @@
class Strategies { class Strategies {
constructor(target_id) { constructor(target_id) {
// The list of strategies. // The list of strategies.
this.strategies = []; this.strategies = [];
// The html element id that displays the strategies. // The HTML element id that displays the strategies.
this.target_id = target_id; this.target_id = target_id;
// The html element that displays the the strategies. // The HTML element that displays the strategies.
this.target = null; this.target = null;
this.workspace = null;
} }
// Create the Blockly workspace and define custom blocks
createWorkspace() { createWorkspace() {
// Dispose of the existing workspace before creating a new one // 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', { this.workspace = Blockly.inject('blocklyDiv', {
toolbox: document.getElementById('toolbox'), toolbox: document.getElementById('toolbox'),
scrollbars: true, scrollbars: true,
trashcan: true, trashcan: true,
}); });
// Define Python generators after workspace initialization
this.definePythonGenerators();
} }
// Generate Python code from the Blockly workspace and return as JSON
generateStrategyJson() { generateStrategyJson() {
var code = Blockly.JavaScript.workspaceToCode(this.workspace); // Initialize Python generator with the current workspace
var json = { Blockly.Python.init(this.workspace);
strategy: code
// 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); 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;
}
// 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. // Show the "Create New Strategy" form (open the workspace)
let stop_loss = {}; open_form() {
stop_loss.typ = document.getElementById('loss_typ').value; const formElement = document.getElementById("new_strat_form");
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. // Check if the form element exists
this.strategies.push(strat); if (formElement) {
formElement.style.display = "grid"; // Open the form
} else {
console.error('Form element "new_strat_form" not found.');
}
}
// Add the strategy to display. // Hide the "Create New Strategy" form
this.update_html(); close_form() {
const formElement = document.getElementById("new_strat_form");
// Send the new strategy to the server. if (formElement) {
window.UI.data.comms.sendToApp( "new_strategy", strat); formElement.style.display = "none"; // Close the form
} else {
console.error('Form element "new_strat_form" not found.');
}
}
// Close the html form. // Submit the strategy and send it to the server
this.close_form(); 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_received(stg_updts){
if ( 'cmd' in stg_updts) { // Update the display of the created strategies
let alert = update_html() {
window.UI.alerts.publish_alerts('strategy', stg_updts); let stratsHtml = '';
this.executeCmd(stg_updts.cmd); const onClick = "window.UI.strats.del(this.value);";
for (let strat of this.strategies) {
let deleteButton = `<button type='button' name='delete' class='e_btn' value='${strat.name}' onclick='${onClick}'>&#10008;</button>`;
stratsHtml += `<li id='${strat.name}_item'>${deleteButton}<pre>${JSON.stringify(strat, null, 2)}</pre></li>`;
} }
console.log('recieved stategy update.'); document.getElementById(this.target_id).innerHTML = stratsHtml;
console.log(stg_updts);
} }
set_data(strats){
for (let strat of strats){ // Open the form and create the Blockly workspace
// Add the strategy to the instance list. open_stg_form() {
this.strategies.push(JSON.parse(strat));
}
// Add the strategy to display.
this.update_html();
}
open_stg_form(){
this.open_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 += '<option>' + option + '</option>';
}
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);
} // Delete a strategy by its name
hideShowTags(elements, cmd){ del(name) {
/* 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){
window.UI.data.comms.sendToApp('delete_strategy', 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'); let child = document.getElementById(name + '_item');
// Remove the child element from the document
child.parentNode.removeChild(child); child.parentNode.removeChild(child);
} }
update_html(){
let strats =''; // Initialize the Strategies UI component
let on_click = " window.UI.strats.del(this.value);"; initialize() {
for (let strat of this.strategies){ this.target = document.getElementById(this.target_id);
let button ="<button type='button' name='delete' class='e_btn' value='" + strat.name + "' onclick='" + on_click + "'>&#10008;</button>"; if (!this.target) {
strats += "<li id='" + strat.name + "_item'>" + button + "<pre>" + JSON.stringify(strat) + "</pre></li>"; 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');
}
} }

View File

@ -1,84 +1,59 @@
class User_Interface {
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. */
constructor() { 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 // Register a callback function for when indicator updates are received from the data object
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. */
this.data.registerCallback_i_updates(this.indicators.update); this.data.registerCallback_i_updates(this.indicators.update);
window.addEventListener('load', function () { // Initialize all components after the page has loaded and Blockly is ready
/* This javascript files are loaded before the entire this.initializeAll();
HTML document gets parsed. Place initializers here
to be executed after the document is loaded. */
/* Charts object is responsible for maintaining the
data visualisation area in the UI. */
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 = { * Initializes all components after the DOM has loaded.
// A list of indicator object available for display. * This includes initializing charts, signals, strategies, and other interface components.
indicators: window.UI.data.indicators, * Components that rely on external libraries like Blockly are initialized after they are ready.
// Output data - indicator specific format. Example: list of indexed value, bools, or colours codes. */
indicator_data: window.UI.data.indicator_data 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: 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 this.charts = new Charts(chart_init_data); // Initialize the charts
window.UI.indicators.addToCharts(window.UI.charts, ind_init_data);
/* Request Initialization data from the server. All incoming // Initialize indicators and link them to the charts
data is forwarded to any registered callback functions. */ let ind_init_data = {
window.UI.signals.request_signals(); indicators: this.data.indicators, // List of indicators
window.UI.alerts.set_target(); indicator_data: this.data.indicator_data // Data for rendering indicators
window.UI.strats.initialize(); };
window.UI.controls.init_TP_selector(); this.indicators.addToCharts(this.charts, ind_init_data); // Add indicators to the charts
window.UI.trade.initialize();
window.UI.exchanges.initialize(); // 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();

View File

@ -47,6 +47,7 @@ class Indicator {
this.name = name; this.name = name;
this.lines = []; this.lines = [];
this.hist = []; this.hist = [];
this.outputs = [];
} }
static getIndicatorConfig() { static getIndicatorConfig() {
@ -162,6 +163,7 @@ class SMA extends Indicator {
constructor(name, chart, color, lineWidth = 2) { constructor(name, chart, color, lineWidth = 2) {
super(name); super(name);
this.addLine('line', chart, color, lineWidth); this.addLine('line', chart, color, lineWidth);
this.outputs = ['value'];
} }
static getIndicatorConfig() { static getIndicatorConfig() {
@ -201,6 +203,7 @@ class RSI extends Indicator {
} }
let chart = charts.chart2; let chart = charts.chart2;
this.addLine('line', chart, color, lineWidth); this.addLine('line', chart, color, lineWidth);
this.outputs = ['value'];
} }
static getIndicatorConfig() { static getIndicatorConfig() {
@ -231,6 +234,7 @@ class MACD extends Indicator {
this.addLine('line_m', chart, color_1, lineWidth); this.addLine('line_m', chart, color_1, lineWidth);
this.addLine('line_s', chart, color_2, lineWidth); this.addLine('line_s', chart, color_2, lineWidth);
this.addHist(name, chart); this.addHist(name, chart);
this.outputs = ['macd', 'signal', 'hist'];
} }
static getIndicatorConfig() { static getIndicatorConfig() {
@ -286,6 +290,7 @@ class ATR extends Indicator {
init(data) { init(data) {
this.updateDisplay(this.name, data.at(-1).value, 'value'); this.updateDisplay(this.name, data.at(-1).value, 'value');
this.outputs = ['value'];
} }
update(data) { update(data) {
@ -300,6 +305,7 @@ class Volume extends Indicator {
super(name); super(name);
this.addHist(name, chart); this.addHist(name, chart);
this.hist[name].applyOptions({ scaleMargins: { top: 0.95, bottom: 0.0 } }); this.hist[name].applyOptions({ scaleMargins: { top: 0.95, bottom: 0.0 } });
this.outputs = ['value'];
} }
static getIndicatorConfig() { static getIndicatorConfig() {
@ -325,6 +331,7 @@ class Bolenger extends Indicator {
this.addLine('line_u', chart, color_1, lineWidth); this.addLine('line_u', chart, color_1, lineWidth);
this.addLine('line_m', chart, color_2, lineWidth); this.addLine('line_m', chart, color_2, lineWidth);
this.addLine('line_l', chart, color_3, lineWidth); this.addLine('line_l', chart, color_3, lineWidth);
this.outputs = ['upper', 'middle', 'lower'];
} }
static getIndicatorConfig() { 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){ addToCharts(charts, idata){
/* /*
Receives indicator data, creates and stores the indicator Receives indicator data, creates and stores the indicator

View File

@ -8,7 +8,10 @@
<!-- Load lightweight charts --> <!-- Load lightweight charts -->
<script src="{{ url_for('static', filename='lightweight-charts.standalone.production.js') }}"></script> <script src="{{ url_for('static', filename='lightweight-charts.standalone.production.js') }}"></script>
<!-- Load Blockly --> <!-- Load Blockly -->
<script src="https://unpkg.com/blockly/blockly.min.js"></script> <script src="{{ url_for('static', filename='blockly/blockly-develop/dist/blockly_compressed.js') }}"></script>
<script src="{{ url_for('static', filename='blockly/blockly-develop/dist/blocks_compressed.js') }}"></script>
<script src="{{ url_for('static', filename='blockly/blockly-develop/dist/python_compressed.js') }}"></script>
<!-- The server passed initiation data to the HTML. This loads it into the DOM. --> <!-- The server passed initiation data to the HTML. This loads it into the DOM. -->
<script type="text/javascript"> <script type="text/javascript">
function get_init_data(vars) {return vars} function get_init_data(vars) {return vars}

View File

@ -1,59 +1,50 @@
<div class="form-popup" id="new_strat_form" style="overflow: auto;"> <!-- Strategy Form Popup -->
<form action="/new_strategy" class="form-container" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto;"> <div class="form-popup" id="new_strat_form" style="display: none; overflow: auto;">
<!-- Panel 1 of 1 (5 rows, 2 columns) --> <form class="form-container" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto;">
<div id="strat_pan_1" class="form_panels" style="display: grid; grid-template-columns:repeat(2,1fr); grid-template-rows: repeat(5,1fr); gap: 10px;"> <!-- Panel 1 of 1 -->
<div id="strat_pan_1" class="form_panels" style="display: grid; grid-template-columns:repeat(2,1fr); grid-template-rows: repeat(6,1fr); gap: 10px;">
<!-- Panel title (row 1/5)--> <!-- Panel title -->
<h1 style="grid-column: 1 / span 2; grid-row: 1;">Create New Strategy</h1> <h1 style="grid-column: 1 / span 2; grid-row: 1;">Create New Strategy</h1>
<div style="grid-column: 1 / span 2; grid-row: 2;">Name: <input class="ietextbox" type="text" id="name_box" name="name_box" value="Indicator name"></div>
<!-- Workspace (row 2-4/5)--> <!-- Blockly workspace -->
<div id="blocklyDiv" style="grid-column: 1 / span 2; grid-row: 2 / span 3; height: 300px; width: 100%;"></div> <div id="blocklyDiv" style="grid-column: 1 / span 2; grid-row: 3 / span 3; height: 300px; width: 100%;"></div>
<!-- Buttons (row 5/5)--> <!-- Buttons -->
<div style="grid-column: 1 / span 2; grid-row: 5; text-align: center;"> <div style="grid-column: 1 / span 2; grid-row: 6; text-align: center;">
<button type="button" class="btn cancel" onclick="UI.strats.close_form()">Close</button> <button type="button" class="btn cancel" onclick="UI.strats.close_form()">Close</button>
<button type="button" class="btn next" onclick="UI.strats.submit()">Create Strategy</button> <button type="button" class="btn next" onclick="UI.strats.submit()">Create Strategy</button>
</div> </div>
</div>
</div><!----End panel 1--------->
</form> </form>
</div> </div>
<xml id="toolbox" style="display: none"> <xml id="toolbox" style="display: none">
<!-- Define your custom blocks here --> <category name="Indicators" colour="230">
<!-- Indicator blocks go here -->
</category>
<category name="Values" colour="230">
<block type="last_candle_value"></block>
<block type="value_input"></block>
</category>
<category name="Logic" colour="210"> <category name="Logic" colour="210">
<block type="controls_if"></block> <block type="comparison"></block>
<block type="logic_compare"></block> <block type="logical_and"></block>
<block type="logical_or"></block>
<block type="is_true"></block>
</category> </category>
<category name="Math" colour="230">
<block type="math_number"></block> <!-- New category for Trading Actions -->
<block type="math_arithmetic"></block> <category name="Trading" colour="230">
<block type="trade_action"></block>
<block type="order_type"></block>
<block type="time_in_force"></block>
<block type="stop_loss"></block>
<block type="take_profit"></block>
</category> </category>
<!-- Add more blocks as needed --> </xml>
</xml>
<!-- <form action="/new_strategy" class="form-container">-->
<!-- &lt;!&ndash; Panel 1 of 1 (5 rows, 2 columns) &ndash;&gt;-->
<!-- <div id="strat_pan_1" class="form_panels" style="grid-template-columns:repeat(2,1fr);grid-template-rows: repeat(5,1fr);">-->
<!-- &lt;!&ndash; Panel title (row 1/5)&ndash;&gt;-->
<!-- <h1 style="grid-column: 1 / span 2; grid-row: 1;">Create New Strategy</h1>-->
<!-- &lt;!&ndash; Name Input field (row 2/5)&ndash;&gt;-->
<!-- <div id = "strat_name_div" style="grid-column: 1 / span 2; grid_row:2;">-->
<!-- <label for='stg_name' >Strategy Name:</label>-->
<!-- <input type="text" id="stg_name" name="strat_name" />-->
<!-- </div>-->
<!-- &lt;!&ndash; Source Input field (row 3/5)&ndash;&gt;-->
<!-- <label for="strat_type" style="grid-column: 1; grid-row: 3;"><b>Strategy type</b></label>-->
<!-- <select name="strat_type" id="strat_type" style="grid-column: 2; grid-row: 3;" onchange= "UI.strats.fill_field('strat_opt', this.value)">-->
<!-- <option>in-out</option>-->
<!-- <option>incremental_profits</option>-->
<!-- <option>swing</option>-->
<!-- </select>-->
<!-- <div id="strat_opt"></div>-->
<!-- <div style="grid-column: 1 / span 2; grid-row: 5;">-->
<!-- <button type="button" class="btn cancel" onclick="UI.strats.close_form()">Close</button>-->
<!-- <button type="button" class="btn next" onclick="UI.strats.submit()">Create Strategy</button>-->
<!-- </div>-->
<!-- </div>&lt;!&ndash;&#45;&#45;End panel 1-&#45;&#45;&#45;&#45;&#45;&#45;&ndash;&gt;-->
<!-- </form>-->