The strategy popup is now draggable and resizeable.
This commit is contained in:
parent
034b8ab925
commit
4e3e8e5abf
|
|
@ -322,12 +322,31 @@ class BrighterTrades:
|
||||||
:param data: A dictionary containing the attributes of the new strategy.
|
:param data: A dictionary containing the attributes of the new strategy.
|
||||||
:return: An error message if the required attribute is missing, or the incoming data for chaining on success.
|
:return: An error message if the required attribute is missing, or the incoming data for chaining on success.
|
||||||
"""
|
"""
|
||||||
if 'name' not in data:
|
# Extract user_name from the data and get user_id
|
||||||
return "The new strategy must have a 'name' attribute."
|
user_name = data.get('user_name')
|
||||||
|
if not user_name:
|
||||||
|
return {"success": False, "message": "User not specified"}
|
||||||
|
|
||||||
self.strategies.new_strategy(data)
|
# Fetch the user_id using the user_name
|
||||||
self.config.set_setting('strategies', self.strategies.get_strategies('dict'))
|
user_id = self.get_user_info(user_name=user_name, info='User_id')
|
||||||
return data
|
if not user_id:
|
||||||
|
return {"success": False, "message": "User ID not found"}
|
||||||
|
|
||||||
|
# Prepare the strategy data for insertion
|
||||||
|
strategy_data = {
|
||||||
|
"creator": user_id,
|
||||||
|
"name": data['name'],
|
||||||
|
"workspace": data['workspace'],
|
||||||
|
"code": data['code'],
|
||||||
|
"stats": data.get('stats', {}),
|
||||||
|
"public": data.get('public', 0), # Default to private if not specified
|
||||||
|
"fee": data.get('fee', None) # Default to None if not specified
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save the new strategy (in both cache and database)
|
||||||
|
self.strategies.new_strategy(strategy_data)
|
||||||
|
|
||||||
|
return {"success": True, "message": "Strategy created successfully", "data": strategy_data}
|
||||||
|
|
||||||
def delete_strategy(self, strategy_name: str) -> None:
|
def delete_strategy(self, strategy_name: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import json
|
import json
|
||||||
from DataCache_v2 import DataCache
|
from DataCache_v3 import DataCache
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
|
||||||
class Strategy:
|
class Strategy:
|
||||||
|
|
@ -169,59 +170,90 @@ class Strategy:
|
||||||
|
|
||||||
class Strategies:
|
class Strategies:
|
||||||
def __init__(self, data: DataCache, trades):
|
def __init__(self, data: DataCache, trades):
|
||||||
# Handles database connections
|
"""
|
||||||
self.data = data
|
Initializes the Strategies class.
|
||||||
|
|
||||||
# Reference to the trades object that maintains all trading actions and data.
|
:param data: Instance of DataCache to manage cache and database interactions.
|
||||||
|
:param trades: Reference to the trades object that maintains trading actions and data.
|
||||||
|
"""
|
||||||
|
self.data = data # Database interaction instance
|
||||||
self.trades = trades
|
self.trades = trades
|
||||||
|
self.strat_list = [] # List to hold strategy objects
|
||||||
|
|
||||||
self.strat_list = []
|
# Create a cache for strategies with necessary columns
|
||||||
|
self.data.create_cache(name='strategies',
|
||||||
|
cache_type='table',
|
||||||
|
size_limit=100,
|
||||||
|
eviction_policy='deny',
|
||||||
|
default_expiration=dt.timedelta(hours=24),
|
||||||
|
columns=["id", "creator", "name", "workspace", "code", "stats", "public", "fee"])
|
||||||
|
|
||||||
def get_all_strategy_names(self) -> list | None:
|
def new_strategy(self, data: dict):
|
||||||
"""Return a list of all strategies in the database"""
|
"""
|
||||||
# # Load existing Strategies from file.
|
Add a new strategy to the cache and database.
|
||||||
# loaded_strategies = self.data.get_setting('strategies')
|
|
||||||
# if loaded_strategies is None:
|
|
||||||
# # Populate the list and file with defaults defined in this class.
|
|
||||||
# loaded_strategies = self.get_strategy_defaults()
|
|
||||||
# config.set_setting('strategies', loaded_strategies)
|
|
||||||
#
|
|
||||||
# for entry in loaded_strategies:
|
|
||||||
# # Initialise all the strategy objects with data from file.
|
|
||||||
# self.strat_list.append(Strategy(**entry))
|
|
||||||
|
|
||||||
return None
|
:param data: A dictionary containing strategy data such as name, code, and workspace.
|
||||||
|
"""
|
||||||
def new_strategy(self, data):
|
# Create a new Strategy object and add it to the list
|
||||||
# Create an instance of the new Strategy.
|
|
||||||
self.strat_list.append(Strategy(**data))
|
self.strat_list.append(Strategy(**data))
|
||||||
|
|
||||||
def delete_strategy(self, name):
|
# Insert the strategy into the database and cache
|
||||||
|
self.data.insert_row_into_datacache(
|
||||||
|
cache_name='strategies',
|
||||||
|
columns=("creator", "name", "workspace", "code", "stats", "public", "fee"),
|
||||||
|
values=(data.get('creator'), data['name'], data['workspace'], data['code'], data.get('stats', {}),
|
||||||
|
data.get('public', False), data.get('fee', 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete_strategy(self, name: str):
|
||||||
|
"""
|
||||||
|
Delete a strategy from the cache and database by name.
|
||||||
|
|
||||||
|
:param name: The name of the strategy to delete.
|
||||||
|
"""
|
||||||
obj = self.get_strategy_by_name(name)
|
obj = self.get_strategy_by_name(name)
|
||||||
if obj:
|
if obj:
|
||||||
self.strat_list.remove(obj)
|
self.strat_list.remove(obj)
|
||||||
|
|
||||||
def get_strategies(self, form):
|
# Remove the strategy from cache and database
|
||||||
# Return a python object of all the strategies stored in this instance.
|
self.data.remove_row_from_datacache(
|
||||||
|
cache_name='strategies',
|
||||||
|
filter_vals=[('name', name)]
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_all_strategy_names(self) -> list | None:
|
||||||
|
"""
|
||||||
|
Return a list of all strategy names stored in the cache or database.
|
||||||
|
"""
|
||||||
|
# Fetch all strategy names from the cache or database
|
||||||
|
strategies_df = self.data.get_rows_from_datacache(cache_name='strategies', filter_vals=[])
|
||||||
|
|
||||||
|
if not strategies_df.empty:
|
||||||
|
return strategies_df['name'].tolist()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_strategies(self, form: str):
|
||||||
|
"""
|
||||||
|
Return strategies stored in this instance in various formats.
|
||||||
|
|
||||||
|
:param form: The desired format ('obj', 'json', or 'dict').
|
||||||
|
:return: A list of strategies in the requested format.
|
||||||
|
"""
|
||||||
if form == 'obj':
|
if form == 'obj':
|
||||||
return self.strat_list
|
return self.strat_list
|
||||||
# Return a JSON object of all the strategies stored in this instance.
|
|
||||||
elif form == 'json':
|
elif form == 'json':
|
||||||
strats = self.strat_list
|
return [strat.to_json() for strat in self.strat_list]
|
||||||
json_str = []
|
|
||||||
for strat in strats:
|
|
||||||
json_str.append(strat.to_json())
|
|
||||||
return json_str
|
|
||||||
# Return a dictionary object of all the strategies stored in this instance.
|
|
||||||
elif form == 'dict':
|
elif form == 'dict':
|
||||||
strats = self.strat_list
|
return [strat.__dict__ for strat in self.strat_list]
|
||||||
s_list = []
|
return None
|
||||||
for st in strats:
|
|
||||||
dic = st.__dict__
|
|
||||||
s_list.append(dic)
|
|
||||||
return s_list
|
|
||||||
|
|
||||||
def get_strategy_by_name(self, name):
|
def get_strategy_by_name(self, name: str):
|
||||||
|
"""
|
||||||
|
Retrieve a strategy object by name.
|
||||||
|
|
||||||
|
:param name: The name of the strategy to retrieve.
|
||||||
|
:return: The strategy object if found, otherwise False.
|
||||||
|
"""
|
||||||
for obj in self.strat_list:
|
for obj in self.strat_list:
|
||||||
if obj.name == name:
|
if obj.name == name:
|
||||||
return obj
|
return obj
|
||||||
|
|
|
||||||
35
src/app.py
35
src/app.py
|
|
@ -105,39 +105,46 @@ def index():
|
||||||
@sock.route('/ws')
|
@sock.route('/ws')
|
||||||
def ws(socket_conn):
|
def ws(socket_conn):
|
||||||
"""
|
"""
|
||||||
Open a websocket to handle two-way communication with UI without browser refreshes.
|
Open a WebSocket to handle two-way communication with UI without browser refreshes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def json_msg_received(msg_obj):
|
def json_msg_received(msg_obj):
|
||||||
"""
|
"""
|
||||||
Handle incoming json msg's.
|
Handle incoming JSON messages with authentication.
|
||||||
"""
|
"""
|
||||||
# Validate input
|
# Validate input
|
||||||
if 'message_type' not in msg_obj:
|
if 'message_type' not in msg_obj or 'data' not in msg_obj:
|
||||||
return
|
return
|
||||||
|
|
||||||
msg_type, msg_data = msg_obj['message_type'], msg_obj['data']
|
msg_type, msg_data = msg_obj['message_type'], msg_obj['data']
|
||||||
|
|
||||||
print(f'\n[MSG_RECEIVED]\nMSG Type:{msg_type}\n{msg_data}')
|
# Extract user_name from the incoming message data
|
||||||
|
user_name = msg_data.get('user_name')
|
||||||
resp = brighter_trades.process_incoming_message(msg_type=msg_type, msg_data=msg_data)
|
if not user_name:
|
||||||
|
socket_conn.send(json.dumps({"success": False, "message": "User not specified"}))
|
||||||
socket_conn.send(json.dumps(resp))
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# The rendered page connects to the exchange_interface and relays the candle data back here
|
# Check if the user is logged in
|
||||||
# this socket also handles data and processing requests
|
if not brighter_trades.get_user_info(user_name=user_name, info='Is logged in?'):
|
||||||
|
socket_conn.send(json.dumps({"success": False, "message": "User not logged in"}))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Process the incoming message based on the type
|
||||||
|
resp = brighter_trades.process_incoming_message(msg_type=msg_type, msg_data=msg_data)
|
||||||
|
|
||||||
|
# Send the response back to the client
|
||||||
|
if resp:
|
||||||
|
socket_conn.send(json.dumps(resp))
|
||||||
|
|
||||||
|
# Main loop to receive messages and handle them
|
||||||
while True:
|
while True:
|
||||||
msg = socket_conn.receive()
|
msg = socket_conn.receive()
|
||||||
if msg:
|
if msg:
|
||||||
# If msg is in json, convert the message into a dictionary then feed the message handler.
|
|
||||||
# Otherwise, log the output.
|
|
||||||
try:
|
try:
|
||||||
json_msg = json.loads(msg)
|
json_msg = json.loads(msg)
|
||||||
json_msg_received(json_msg)
|
json_msg_received(json_msg)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
print(f'Msg received from client: {msg}')
|
print(f'Msg received from client (not JSON): {msg}')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/settings', methods=['POST'])
|
@app.route('/settings', methods=['POST'])
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,13 @@ class Strategies {
|
||||||
// Define Python generators after workspace initialization
|
// Define Python generators after workspace initialization
|
||||||
this.definePythonGenerators();
|
this.definePythonGenerators();
|
||||||
}
|
}
|
||||||
|
resizeWorkspace() {
|
||||||
|
const blocklyDiv = document.getElementById('blocklyDiv');
|
||||||
|
if (this.workspace) {
|
||||||
|
// Adjust workspace dimensions to match the blocklyDiv
|
||||||
|
Blockly.svgResize(this.workspace);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Generate Python code from the Blockly workspace and return as JSON
|
// Generate Python code from the Blockly workspace and return as JSON
|
||||||
generateStrategyJson() {
|
generateStrategyJson() {
|
||||||
// Initialize Python generator with the current workspace
|
// Initialize Python generator with the current workspace
|
||||||
|
|
@ -38,15 +45,43 @@ class Strategies {
|
||||||
// Generate Python code from the Blockly workspace
|
// Generate Python code from the Blockly workspace
|
||||||
const pythonCode = Blockly.Python.workspaceToCode(this.workspace);
|
const pythonCode = Blockly.Python.workspaceToCode(this.workspace);
|
||||||
|
|
||||||
// Create a strategy object with generated code
|
// Serialize the workspace to XML format
|
||||||
|
const workspaceXml = Blockly.Xml.workspaceToDom(this.workspace);
|
||||||
|
const workspaceXmlText = Blockly.Xml.domToText(workspaceXml);
|
||||||
|
|
||||||
const json = {
|
const json = {
|
||||||
name: document.getElementById('name_box').value,
|
name: document.getElementById('name_box').value,
|
||||||
code: pythonCode // This is the generated Python code
|
code: pythonCode, // This is the generated Python code
|
||||||
|
workspace: workspaceXmlText // Serialized workspace XML
|
||||||
};
|
};
|
||||||
|
|
||||||
return JSON.stringify(json);
|
return JSON.stringify(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restoreWorkspaceFromXml(workspaceXmlText) {
|
||||||
|
// Convert the text back into an XML DOM object
|
||||||
|
const workspaceXml = Blockly.Xml.textToDom(workspaceXmlText);
|
||||||
|
|
||||||
|
// Clear the current workspace before loading a new one
|
||||||
|
if (this.workspace) {
|
||||||
|
this.workspace.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the workspace from the XML DOM object
|
||||||
|
Blockly.Xml.domToWorkspace(workspaceXml, this.workspace);
|
||||||
|
}
|
||||||
|
fetchSavedStrategies() {
|
||||||
|
fetch('/get_strategies', {
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
this.strategies = data.strategies;
|
||||||
|
this.update_html();
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching strategies:', error));
|
||||||
|
}
|
||||||
|
|
||||||
// Show the "Create New Strategy" form (open the workspace)
|
// Show the "Create New Strategy" form (open the workspace)
|
||||||
open_form() {
|
open_form() {
|
||||||
const formElement = document.getElementById("new_strat_form");
|
const formElement = document.getElementById("new_strat_form");
|
||||||
|
|
@ -70,23 +105,40 @@ class Strategies {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit the strategy and send it to the server
|
|
||||||
submit() {
|
submit() {
|
||||||
// Generate the Python code as JSON
|
// Generate the Python code as JSON
|
||||||
const strategyJson = this.generateStrategyJson();
|
const strategyJson = this.generateStrategyJson();
|
||||||
|
|
||||||
// Send the strategy to the server
|
// Get the fee and public checkbox values
|
||||||
fetch('/new_strategy', {
|
const fee = parseFloat(document.getElementById('fee_box').value) || 0;
|
||||||
method: 'POST',
|
if (fee < 0) {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
alert("Fee cannot be negative");
|
||||||
body: strategyJson
|
return;
|
||||||
})
|
|
||||||
.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
|
const is_public = document.getElementById('public_checkbox').checked ? 1 : 0;
|
||||||
|
|
||||||
|
// Merge additional fields into the strategy data
|
||||||
|
const strategyData = {
|
||||||
|
...JSON.parse(strategyJson), // Spread the existing strategy JSON data
|
||||||
|
fee, // Add the fee
|
||||||
|
public: is_public // Add the public status
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the strategy to the server via WebSocket through the Comms class
|
||||||
|
if (window.UI.data.comms) { // Assuming the Comms instance is accessible via window.UI.data.comms
|
||||||
|
window.UI.data.comms.sendToApp('new_strategy', strategyData);
|
||||||
|
} else {
|
||||||
|
console.error("Comms instance not available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFeeBox() {
|
||||||
|
const publicCheckbox = document.getElementById('public_checkbox');
|
||||||
|
const feeBox = document.getElementById('fee_box');
|
||||||
|
feeBox.disabled = !publicCheckbox.checked; // Enable feeBox only if publicCheckbox is checked
|
||||||
|
}
|
||||||
|
// Update the UI with saved strategies
|
||||||
update_html() {
|
update_html() {
|
||||||
let stratsHtml = '';
|
let stratsHtml = '';
|
||||||
const onClick = "window.UI.strats.del(this.value);";
|
const onClick = "window.UI.strats.del(this.value);";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
class Comms {
|
class Comms {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.connectionOpen = false;
|
||||||
|
this.appCon = null; // WebSocket connection for app communication
|
||||||
|
|
||||||
// Callback collections that will receive various updates.
|
// Callback collections that will receive various updates.
|
||||||
this.candleUpdateCallbacks = [];
|
this.candleUpdateCallbacks = [];
|
||||||
|
|
@ -170,33 +172,55 @@ class Comms {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a message to the application server.
|
* Sends a message to the application server via WebSocket.
|
||||||
|
* Automatically includes the user_name from `window.UI.data.user_name` for authentication.
|
||||||
* @param {string} messageType - The type of the message.
|
* @param {string} messageType - The type of the message.
|
||||||
* @param {Object} data - The data to be sent with the message.
|
* @param {Object} data - The data to be sent with the message.
|
||||||
*/
|
*/
|
||||||
sendToApp(messageType, data) {
|
sendToApp(messageType, data) {
|
||||||
console.log('window.UI.data.comms.sendToApp(): Sending->');
|
const user_name = window.UI.data.user_name; // Access user_name from window.UI.data
|
||||||
console.log(JSON.stringify({ message_type: messageType, data: data }));
|
|
||||||
|
if (!user_name) {
|
||||||
|
console.error('User not logged in. Cannot send message.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
message_type: messageType,
|
||||||
|
data: {
|
||||||
|
...data, // Include the existing data
|
||||||
|
user_name: user_name // Add user_name for authentication
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Comms: Sending->', JSON.stringify(messageData));
|
||||||
|
|
||||||
if (this.connectionOpen) {
|
if (this.connectionOpen) {
|
||||||
this.appCon.send(JSON.stringify({ message_type: messageType, data: data }));
|
this.appCon.send(JSON.stringify(messageData));
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.UI.data.comms.appCon.send(JSON.stringify({ message_type: messageType, data: data }));
|
if (this.appCon) {
|
||||||
|
this.appCon.send(JSON.stringify(messageData));
|
||||||
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the WebSocket connection to the application server.
|
* Sets up the WebSocket connection to the application server.
|
||||||
*/
|
*/
|
||||||
setAppCon() {
|
setAppCon() {
|
||||||
this.appCon = new WebSocket('ws://localhost:5000/ws');
|
this.appCon = new WebSocket('ws://localhost:5000/ws');
|
||||||
|
|
||||||
|
// On connection open
|
||||||
this.appCon.onopen = () => {
|
this.appCon.onopen = () => {
|
||||||
|
console.log("WebSocket connection established");
|
||||||
this.appCon.send("Connection OK");
|
this.appCon.send("Connection OK");
|
||||||
this.connectionOpen = true;
|
this.connectionOpen = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle incoming messages
|
||||||
this.appCon.addEventListener('message', (event) => {
|
this.appCon.addEventListener('message', (event) => {
|
||||||
if (event.data) {
|
if (event.data) {
|
||||||
const message = JSON.parse(event.data);
|
const message = JSON.parse(event.data);
|
||||||
|
|
@ -207,24 +231,29 @@ class Comms {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message && message.reply !== undefined) {
|
if (message && message.reply !== undefined) {
|
||||||
|
// Handle different reply types from the server
|
||||||
if (message.reply === 'updates') {
|
if (message.reply === 'updates') {
|
||||||
const { i_updates, s_updates, stg_updts, trade_updts } = message.data;
|
const { i_updates, s_updates, stg_updts, trade_updts } = message.data;
|
||||||
|
|
||||||
|
// Handle indicator updates
|
||||||
if (i_updates) {
|
if (i_updates) {
|
||||||
this.indicatorUpdate(i_updates);
|
this.indicatorUpdate(i_updates);
|
||||||
window.UI.signals.i_update(i_updates);
|
window.UI.signals.i_update(i_updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle signal updates
|
||||||
if (s_updates) {
|
if (s_updates) {
|
||||||
const updates = s_updates;
|
window.UI.signals.update_signal_states(s_updates);
|
||||||
window.UI.signals.update_signal_states(updates);
|
window.UI.alerts.publish_alerts('signal_changes', s_updates);
|
||||||
window.UI.alerts.publish_alerts('signal_changes', updates);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle strategy updates
|
||||||
if (stg_updts) {
|
if (stg_updts) {
|
||||||
const stg_updts = stg_updts;
|
|
||||||
window.UI.strats.update_received(stg_updts);
|
window.UI.strats.update_received(stg_updts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle trade updates
|
||||||
if (trade_updts) {
|
if (trade_updts) {
|
||||||
const trade_updts = trade_updts;
|
|
||||||
window.UI.trade.update_received(trade_updts);
|
window.UI.trade.update_received(trade_updts);
|
||||||
}
|
}
|
||||||
} else if (message.reply === 'signals') {
|
} else if (message.reply === 'signals') {
|
||||||
|
|
@ -248,8 +277,20 @@ class Comms {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// On connection close
|
||||||
|
this.appCon.onclose = () => {
|
||||||
|
console.log("WebSocket connection closed");
|
||||||
|
this.connectionOpen = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// On WebSocket error
|
||||||
|
this.appCon.onerror = (error) => {
|
||||||
|
console.error("WebSocket error:", error);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up a WebSocket connection to the exchange for receiving candlestick data.
|
* Sets up a WebSocket connection to the exchange for receiving candlestick data.
|
||||||
* @param {string} interval - The interval of the candlestick data.
|
* @param {string} interval - The interval of the candlestick data.
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
class User_Interface {
|
class User_Interface {
|
||||||
constructor() {
|
constructor() {
|
||||||
// Initialize all components needed by the user interface
|
// Initialize all components needed by the user interface
|
||||||
this.strats = new Strategies('strats_display'); // Handles strategies display and interaction
|
this.strats = new Strategies('strats_display');
|
||||||
this.exchanges = new Exchanges(); // Manages exchange-related data
|
this.exchanges = new Exchanges();
|
||||||
this.data = new Data(); // Stores and maintains application-wide data
|
this.data = new Data();
|
||||||
this.controls = new Controls(); // Handles user controls
|
this.controls = new Controls();
|
||||||
this.signals = new Signals(this.data.indicators); // Manages signals used in strategies
|
this.signals = new Signals(this.data.indicators);
|
||||||
this.alerts = new Alerts("alert_list"); // Manages the alerts system
|
this.alerts = new Alerts("alert_list");
|
||||||
this.trade = new Trade(); // Manages trade-related data and operations
|
this.trade = new Trade();
|
||||||
this.users = new Users(); // Handles user login and account details
|
this.users = new Users();
|
||||||
this.indicators = new Indicators(this.data.comms); // Manages indicators and interaction with charts
|
this.indicators = new Indicators(this.data.comms);
|
||||||
|
|
||||||
// Register a callback function for when indicator updates are received from the data object
|
// Register a callback function for when indicator updates are received from the data object
|
||||||
this.data.registerCallback_i_updates(this.indicators.update);
|
this.data.registerCallback_i_updates(this.indicators.update);
|
||||||
|
|
@ -18,41 +18,124 @@ class User_Interface {
|
||||||
this.initializeAll();
|
this.initializeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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() {
|
initializeAll() {
|
||||||
// Use an arrow function to preserve the value of 'this' (User_Interface instance)
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
// Initialize the Charts component with the required data
|
this.initializeChartsAndIndicators();
|
||||||
let chart_init_data = {
|
this.initializeResizablePopup("new_strat_form", this.strats.resizeWorkspace.bind(this.strats));
|
||||||
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
|
|
||||||
};
|
|
||||||
this.charts = new Charts(chart_init_data); // Initialize the charts
|
|
||||||
|
|
||||||
// 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
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeChartsAndIndicators() {
|
||||||
|
let chart_init_data = {
|
||||||
|
chart1_id: this.data.chart1_id,
|
||||||
|
chart2_id: this.data.chart2_id,
|
||||||
|
chart3_id: this.data.chart3_id,
|
||||||
|
trading_pair: this.data.trading_pair,
|
||||||
|
price_history: this.data.price_history
|
||||||
|
};
|
||||||
|
this.charts = new Charts(chart_init_data);
|
||||||
|
|
||||||
|
let ind_init_data = {
|
||||||
|
indicators: this.data.indicators,
|
||||||
|
indicator_data: this.data.indicator_data
|
||||||
|
};
|
||||||
|
this.indicators.addToCharts(this.charts, ind_init_data);
|
||||||
|
|
||||||
|
this.signals.request_signals();
|
||||||
|
this.alerts.set_target();
|
||||||
|
this.controls.init_TP_selector();
|
||||||
|
this.trade.initialize();
|
||||||
|
this.exchanges.initialize();
|
||||||
|
this.strats.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a popup resizable, and optionally pass a resize callback (like for Blockly workspaces).
|
||||||
|
* @param {string} popupId - The ID of the popup to make resizable.
|
||||||
|
* @param {function|null} resizeCallback - Optional callback to run when resizing (for Blockly or other elements).
|
||||||
|
*/
|
||||||
|
initializeResizablePopup(popupId, resizeCallback = null) {
|
||||||
|
const popupElement = document.getElementById(popupId);
|
||||||
|
this.dragElement(popupElement);
|
||||||
|
this.makeResizable(popupElement, resizeCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make an element draggable by dragging its header.
|
||||||
|
* @param {HTMLElement} elm - The element to make draggable.
|
||||||
|
*/
|
||||||
|
dragElement(elm) {
|
||||||
|
const header = document.getElementById("draggable_header");
|
||||||
|
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
|
||||||
|
|
||||||
|
if (header) {
|
||||||
|
header.onmousedown = dragMouseDown;
|
||||||
|
} else {
|
||||||
|
elm.onmousedown = dragMouseDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragMouseDown(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
pos3 = e.clientX;
|
||||||
|
pos4 = e.clientY;
|
||||||
|
document.onmouseup = closeDragElement;
|
||||||
|
document.onmousemove = elementDrag;
|
||||||
|
}
|
||||||
|
|
||||||
|
function elementDrag(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
pos1 = pos3 - e.clientX;
|
||||||
|
pos2 = pos4 - e.clientY;
|
||||||
|
pos3 = e.clientX;
|
||||||
|
pos4 = e.clientY;
|
||||||
|
elm.style.top = (elm.offsetTop - pos2) + "px";
|
||||||
|
elm.style.left = (elm.offsetLeft - pos1) + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDragElement() {
|
||||||
|
document.onmouseup = null;
|
||||||
|
document.onmousemove = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make an element resizable and optionally call a resize callback (like for Blockly workspace).
|
||||||
|
* @param {HTMLElement} elm - The element to make resizable.
|
||||||
|
* @param {function|null} resizeCallback - Optional callback to resize specific content.
|
||||||
|
*/
|
||||||
|
makeResizable(elm, resizeCallback = null) {
|
||||||
|
const resizer = document.getElementById("resize-br");
|
||||||
|
let originalWidth = 0;
|
||||||
|
let originalHeight = 0;
|
||||||
|
let originalMouseX = 0;
|
||||||
|
let originalMouseY = 0;
|
||||||
|
|
||||||
|
resizer.addEventListener('mousedown', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
originalWidth = parseFloat(getComputedStyle(elm, null).getPropertyValue('width').replace('px', ''));
|
||||||
|
originalHeight = parseFloat(getComputedStyle(elm, null).getPropertyValue('height').replace('px', ''));
|
||||||
|
originalMouseX = e.pageX;
|
||||||
|
originalMouseY = e.pageY;
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', resize);
|
||||||
|
window.addEventListener('mouseup', stopResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
const resize = (e) => {
|
||||||
|
elm.style.width = originalWidth + (e.pageX - originalMouseX) + 'px';
|
||||||
|
elm.style.height = originalHeight + (e.pageY - originalMouseY) + 'px';
|
||||||
|
|
||||||
|
// Optionally call the resize callback (for Blockly or other resizable components)
|
||||||
|
if (resizeCallback) {
|
||||||
|
resizeCallback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopResize = () => {
|
||||||
|
window.removeEventListener('mousemove', resize);
|
||||||
|
window.removeEventListener('mouseup', stopResize);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate the User_Interface class and assign it to a global variable for access in other parts of the app
|
// Instantiate the User_Interface class and assign it to a global variable for access in other parts of the app
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,111 @@
|
||||||
<!-- Strategy Form Popup -->
|
<!-- Strategy Form Popup -->
|
||||||
<div class="form-popup" id="new_strat_form" style="display: none; overflow: auto;">
|
<div class="form-popup" id="new_strat_form" style="display: none; overflow: hidden; position: absolute; width: 400px; height: 640px; border-radius: 10px;">
|
||||||
<form class="form-container" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto;">
|
|
||||||
|
<!-- Draggable Header Section -->
|
||||||
|
<div id="draggable_header" style="cursor: move; padding: 10px; background-color: #3E3AF2; color: white; border-bottom: 1px solid #ccc;">
|
||||||
|
<h1 style="margin: 0;">Create New Strategy</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content (Scrollable) -->
|
||||||
|
<form class="form-container" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto; height: calc(100% - 60px); overflow-y: auto;">
|
||||||
<!-- Panel 1 of 1 -->
|
<!-- 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;">
|
<div id="strat_pan_1" class="form_panels" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto auto; gap: 10px; padding: 10px;">
|
||||||
<!-- Panel title -->
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Blockly workspace -->
|
<!-- Blockly workspace -->
|
||||||
<div id="blocklyDiv" style="grid-column: 1 / span 2; grid-row: 3 / span 3; height: 300px; width: 100%;"></div>
|
<div id="blocklyDiv" style="grid-column: 1; height: 300px; width: 100%;"></div>
|
||||||
|
|
||||||
|
<!-- Name of the strategy -->
|
||||||
|
<div style="grid-column: 1;">
|
||||||
|
<label for="name_box" style="display: inline-block; width: 20%;">Name:</label>
|
||||||
|
<input class="ietextbox" type="text" id="name_box" name="name_box" value="Indicator name" style="width: 75%; display: inline-block;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Public checkbox -->
|
||||||
|
<div style="grid-column: 1;">
|
||||||
|
<label for="public_checkbox" style="display: inline-block; width: 20%;">Public:</label>
|
||||||
|
<input type="checkbox" id="public_checkbox" name="public_checkbox" style="display: inline-block;" onclick="UI.strats.toggleFeeBox()">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fee input field -->
|
||||||
|
<div style="grid-column: 1;">
|
||||||
|
<label for="fee_box" style="display: inline-block; width: 20%;">Fee(%):</label>
|
||||||
|
<input class="ietextbox" type="number" id="fee_box" name="fee_box" placeholder="0.00" step="0.01" min="0" style="width: 65px; display: inline-block;" disabled>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<div style="grid-column: 1 / span 2; grid-row: 6; text-align: center;">
|
<div style="grid-column: 1; 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>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- Single Resize Handle in Bottom Right -->
|
||||||
|
<div class="resize-handle" id="resize-br"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Add CSS to hide scrollbars but allow scrolling and fix the resize handle -->
|
||||||
|
<style>
|
||||||
|
/* Hide scrollbars but still allow scrolling */
|
||||||
|
.form-popup {
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-popup::-webkit-scrollbar {
|
||||||
|
display: none; /* Chrome, Safari, Opera */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the draggable header section */
|
||||||
|
#draggable_header {
|
||||||
|
cursor: move;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #3E3AF2;
|
||||||
|
color: white;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the form container to be scrollable and take up remaining space */
|
||||||
|
.form-container {
|
||||||
|
height: calc(100% - 60px); /* Takes up the remaining height minus header height */
|
||||||
|
overflow-y: auto; /* Makes it scrollable */
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container::-webkit-scrollbar {
|
||||||
|
display: none; /* Chrome, Safari, Opera */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Label and input field styling */
|
||||||
|
.form_panels label {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ietextbox {
|
||||||
|
width: 75%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resize handle styling */
|
||||||
|
.resize-handle {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #000;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
cursor: se-resize;
|
||||||
|
z-index: 10;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<xml id="toolbox" style="display: none">
|
<xml id="toolbox" style="display: none">
|
||||||
<category name="Indicators" colour="230">
|
<category name="Indicators" colour="230">
|
||||||
<!-- Indicator blocks go here -->
|
<!-- Indicator blocks go here -->
|
||||||
|
|
@ -45,6 +132,3 @@
|
||||||
</category>
|
</category>
|
||||||
</xml>
|
</xml>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue