Getting closer to a beta. woot woot. there is a problem with created strategies only displaying two of the ones in the database

This commit is contained in:
Rob 2024-09-24 23:43:13 -03:00
parent 4e3e8e5abf
commit adedaaa540
8 changed files with 586 additions and 331 deletions

View File

@ -98,7 +98,7 @@ class BrighterTrades:
Returns specified user info. Returns specified user info.
:param user_name: The user_name. :param user_name: The user_name.
:param info: The information being requested. :param info: The information being requested.('Chart View','Is logged in?', 'User_id')
:return: The requested info or None. :return: The requested info or None.
:raises ValueError: If the provided info is invalid. :raises ValueError: If the provided info is invalid.
""" """
@ -343,29 +343,33 @@ class BrighterTrades:
"fee": data.get('fee', None) # Default to None if not specified "fee": data.get('fee', None) # Default to None if not specified
} }
# Save the new strategy (in both cache and database) # Save the new strategy (in both cache and database) and return the result.
self.strategies.new_strategy(strategy_data) return self.strategies.new_strategy(strategy_data)
return {"success": True, "message": "Strategy created successfully", "data": strategy_data} def delete_strategy(self, data: dict) -> str | dict:
def delete_strategy(self, strategy_name: str) -> None:
""" """
Deletes the specified strategy from the strategies instance and the configuration file. Deletes the specified strategy from the strategies instance and the configuration file.
:param strategy_name: The name of the strategy to delete.
:return: None :return: None
:raises ValueError: If the strategy does not exist or there are issues :raises ValueError: If the strategy does not exist or there are issues
with removing it from the configuration file. with removing it from the configuration file.
""" """
# if not self.strategies.has_strategy(strategy_name): # Extract user_name from the data and get user_id
# raise ValueError(f"The strategy '{strategy_name}' does not exist.") user_name = data.get('user_name')
if not user_name:
return {"success": False, "message": "User not specified"}
self.strategies.delete_strategy(strategy_name) # Fetch the user_id using the user_name
try: user_id = self.get_user_info(user_name=user_name, info='User_id')
# self.config.remove('strategies', strategy_name)TODO if not user_id:
pass return {"success": False, "message": "User ID not found"}
except Exception as e:
raise ValueError(f"Failed to remove the strategy '{strategy_name}' from the configuration file: {str(e)}") strategy_name = data.get('strategy_name')
if not strategy_name:
return {"success": False, "message": "strategy_name not found"}
self.strategies.delete_strategy(user_id=user_id, name=strategy_name)
return {"success": True, "message": "Strategy {strategy_name} deleted"}
def delete_signal(self, signal_name: str) -> None: def delete_signal(self, signal_name: str) -> None:
""" """
@ -389,13 +393,13 @@ class BrighterTrades:
""" """
return self.signals.get_signals('json') return self.signals.get_signals('json')
def get_strategies_json(self) -> str: def get_strategies_json(self, user_id) -> str:
""" """
Retrieve all the strategies from the strategies instance and return them as a JSON object. Retrieve all the strategies from the strategies instance and return them as a JSON object.
:return: str - A JSON object containing all the strategies. :return: str - A JSON object containing all the strategies.
""" """
return self.strategies.get_strategies('json') return self.strategies.get_all_strategies(user_id, 'json')
def connect_or_config_exchange(self, user_name: str, exchange_name: str, api_keys: dict = None) -> dict: def connect_or_config_exchange(self, user_name: str, exchange_name: str, api_keys: dict = None) -> dict:
""" """
@ -587,7 +591,9 @@ class BrighterTrades:
:param msg_type: The type of the incoming message. :param msg_type: The type of the incoming message.
:param msg_data: The data associated with the incoming message. :param msg_data: The data associated with the incoming message.
:return: dict|None - A dictionary containing the response message and data, or None if no response is needed. :return: dict|None - A dictionary containing the response message and data, or None if no response is needed or
no data is found to ensure the WebSocket channel isn't burdened with unnecessary
communication.
""" """
def standard_reply(reply_msg: str, reply_data: Any) -> dict: def standard_reply(reply_msg: str, reply_data: Any) -> dict:
@ -599,15 +605,17 @@ class BrighterTrades:
return standard_reply("updates", r_data) return standard_reply("updates", r_data)
if msg_type == 'request': if msg_type == 'request':
if msg_data == 'signals': request_for = msg_data.get('request')
if request_for == 'signals':
if signals := self.get_signals_json(): if signals := self.get_signals_json():
return standard_reply("signals", signals) return standard_reply("signals", signals)
elif msg_data == 'strategies': elif request_for == 'strategies':
if strategies := self.get_strategies_json(): user_id = self.get_user_info(msg_data['user_name'], 'User_id')
if strategies := self.get_strategies_json(user_id):
return standard_reply("strategies", strategies) return standard_reply("strategies", strategies)
elif msg_data == 'trades': elif request_for == 'trades':
if trades := self.get_trades(): if trades := self.get_trades():
return standard_reply("trades", trades) return standard_reply("trades", trades)
else: else:

View File

@ -1,171 +1,174 @@
import json import json
import pandas as pd
from DataCache_v3 import DataCache from DataCache_v3 import DataCache
import datetime as dt import datetime as dt
class Strategy: # class Strategy:
def __init__(self, **args): # def __init__(self, **args):
""" # """
:param args: An object containing key_value pairs representing strategy attributes. # :param args: An object containing key_value pairs representing strategy attributes.
Strategy format is defined in strategies.js # Strategy format is defined in strategies.js
""" # """
self.active = None # self.active = None
self.type = None # self.type = None
self.trade_amount = None # self.trade_amount = None
self.max_position = None # self.max_position = None
self.side = None # self.side = None
self.trd_in_conds = None # self.trd_in_conds = None
self.merged_loss = None # self.merged_loss = None
self.gross_loss = None # self.gross_loss = None
self.stop_loss = None # self.stop_loss = None
self.take_profit = None # self.take_profit = None
self.gross_profit = None # self.gross_profit = None
self.merged_profit = None # self.merged_profit = None
self.name = None # self.name = None
self.current_value = None # self.current_value = None
self.opening_value = None # self.opening_value = None
self.gross_pl = None # self.gross_pl = None
self.net_pl = None # self.net_pl = None
self.combined_position = None # self.combined_position = None
#
# A strategy is defined in Strategies.js it is received from the client, # # A strategy is defined in Strategies.js it is received from the client,
# then unpacked and converted into a python object here. # # then unpacked and converted into a python object here.
for name, value in args.items(): # for name, value in args.items():
# Make each keyword-argument a specific_property of the class. # # Make each keyword-argument a specific_property of the class.
setattr(self, name, value) # setattr(self, name, value)
#
# A container to hold previous state of signals. # # A container to hold previous state of signals.
self.last_states = {} # self.last_states = {}
#
# A list of all the trades made by this strategy. # # A list of all the trades made by this strategy.
self.trades = [] # self.trades = []
#
def get_position(self): # def get_position(self):
return self.combined_position # return self.combined_position
#
def get_pl(self): # def get_pl(self):
self.update_pl() # self.update_pl()
return self.net_pl # return self.net_pl
#
def update_pl(self): # def update_pl(self):
# sum the pl of all the trades. # # sum the pl of all the trades.
position_sum = 0 # position_sum = 0
pl_sum = 0 # pl_sum = 0
opening_value_sum = 0 # opening_value_sum = 0
value_sum = 0 # value_sum = 0
for trade in self.trades: # for trade in self.trades:
pl_sum += trade.profit_loss # pl_sum += trade.profit_loss
position_sum += trade.position_size # position_sum += trade.position_size
value_sum += trade.value # value_sum += trade.value
opening_value_sum += trade.opening_value # opening_value_sum += trade.opening_value
self.combined_position = position_sum # self.combined_position = position_sum
self.gross_pl = pl_sum # self.gross_pl = pl_sum
self.opening_value = opening_value_sum # self.opening_value = opening_value_sum
self.current_value = value_sum # self.current_value = value_sum
#
def to_json(self): # def to_json(self):
return json.dumps(self, default=lambda o: o.__dict__, # return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4) # sort_keys=True, indent=4)
#
def evaluate_strategy(self, signals): # def evaluate_strategy(self, signals):
""" # """
:param signals: Signals: A reference to an object that handles current signal states. # :param signals: Signals: A reference to an object that handles current signal states.
:return action: Action required based on evaluation. format{cmd:str, amount:real, margin:int} # :return action: Action required based on evaluation. format{cmd:str, amount:real, margin:int}
""" # """
#
def condition_satisfied(sig_name, value): # def condition_satisfied(sig_name, value):
""" # """
Check if a signal has a state of value. # Check if a signal has a state of value.
:param sig_name: str: The name of a signal object to compare states. # :param sig_name: str: The name of a signal object to compare states.
:param value: The state value to compare. # :param value: The state value to compare.
:return bool: True: <Signal:sig_name:state> == <value>. # :return bool: True: <Signal:sig_name:state> == <value>.
""" # """
signal = signals.get_signal_by_name(sig_name) # signal = signals.get_signal_by_name(sig_name)
# Evaluate for a state change # # Evaluate for a state change
if value == 'changed': # if value == 'changed':
# Store the state if it hasn't been stored yet. # # Store the state if it hasn't been stored yet.
if sig_name not in self.last_states: # if sig_name not in self.last_states:
self.last_states.update({sig_name: signal.state}) # self.last_states.update({sig_name: signal.state})
# Store the new state and return true if the state has changed. # # Store the new state and return true if the state has changed.
if self.last_states[sig_name] != signal.state: # if self.last_states[sig_name] != signal.state:
self.last_states.update({sig_name: signal.state}) # self.last_states.update({sig_name: signal.state})
return True # return True
else: # else:
# Else return true if the values match. # # Else return true if the values match.
return value == json.dumps(signal.state) # return value == json.dumps(signal.state)
#
def all_conditions_met(conditions): # def all_conditions_met(conditions):
# Loops through a lists of signal names and states. # # Loops through a lists of signal names and states.
# Returns True if all combinations are true. # # Returns True if all combinations are true.
if len(conditions) < 1: # if len(conditions) < 1:
print(f"no trade-in conditions supplied: {self.name}") # print(f"no trade-in conditions supplied: {self.name}")
return False # return False
# Evaluate all conditions and return false if any are un-met. # # Evaluate all conditions and return false if any are un-met.
for trigger_signal in conditions.keys(): # for trigger_signal in conditions.keys():
trigger_value = conditions[trigger_signal] # trigger_value = conditions[trigger_signal]
# Compare this signal's state with the trigger_value # # Compare this signal's state with the trigger_value
print(f'evaluating :({trigger_signal, trigger_value})') # print(f'evaluating :({trigger_signal, trigger_value})')
if not condition_satisfied(trigger_signal, trigger_value): # if not condition_satisfied(trigger_signal, trigger_value):
print('returning false') # print('returning false')
return False # return False
print('all conditions met!!!') # print('all conditions met!!!')
return True # return True
#
def trade_out_condition_met(condition_type): # def trade_out_condition_met(condition_type):
# Retrieve the condition from either the 'stop_loss' or 'take_profit' obj. # # Retrieve the condition from either the 'stop_loss' or 'take_profit' obj.
condition = getattr(self, condition_type) # condition = getattr(self, condition_type)
# Subtypes of conditions are 'conditional' or 'value'. # # Subtypes of conditions are 'conditional' or 'value'.
if condition.typ == 'conditional': # if condition.typ == 'conditional':
signal_name = condition.trig # signal_name = condition.trig
signal_value = condition.val # signal_value = condition.val
return condition_satisfied(signal_name, signal_value) # return condition_satisfied(signal_name, signal_value)
else: # else:
if condition_type == 'take_profit': # if condition_type == 'take_profit':
if self.merged_profit: # if self.merged_profit:
# If the profit condition is met send command to take profit. # # If the profit condition is met send command to take profit.
return self.gross_profit > self.take_profit.val # return self.gross_profit > self.take_profit.val
else: # else:
# Loop through each associated trade and test # # Loop through each associated trade and test
for trade in self.trades: # for trade in self.trades:
return trade.profit_loss > self.take_profit.val # return trade.profit_loss > self.take_profit.val
elif condition_type == 'value': # elif condition_type == 'value':
if self.merged_loss: # if self.merged_loss:
# If the loss condition is met, return a trade-out command. # # If the loss condition is met, return a trade-out command.
return self.gross_loss < self.stop_loss.val # return self.gross_loss < self.stop_loss.val
else: # else:
# Loop through each associated trade and test # # Loop through each associated trade and test
for trade in self.trades: # for trade in self.trades:
return trade.profit_loss < self.stop_loss.val # return trade.profit_loss < self.stop_loss.val
else: # else:
raise ValueError('trade_out_condition_met: invalid condition_type') # raise ValueError('trade_out_condition_met: invalid condition_type')
#
trade_in_cmd = self.side # trade_in_cmd = self.side
if self.side == 'buy': # if self.side == 'buy':
trade_out_cmd = 'sell' # trade_out_cmd = 'sell'
else: # else:
trade_out_cmd = 'buy' # trade_out_cmd = 'buy'
if self.type == 'in-out': # if self.type == 'in-out':
print('evaluating trade in conditions for in / out') # print('evaluating trade in conditions for in / out')
# If trade-in conditions are met. # # If trade-in conditions are met.
if all_conditions_met(self.trd_in_conds): # if all_conditions_met(self.trd_in_conds):
# If the new trade wouldn't exceed max_position. Return a trade-in command. # # If the new trade wouldn't exceed max_position. Return a trade-in command.
proposed_position_size = int(self.combined_position) + int(self.trade_amount) # proposed_position_size = int(self.combined_position) + int(self.trade_amount)
if proposed_position_size < int(self.max_position): # if proposed_position_size < int(self.max_position):
return 'open_position', trade_in_cmd # return 'open_position', trade_in_cmd
#
# If strategy is active test the take-profit or stop-loss conditions. # # If strategy is active test the take-profit or stop-loss conditions.
if self.active: # if self.active:
# Conditional take-profit trades-out if a signals equals a set value. # # Conditional take-profit trades-out if a signals equals a set value.
if trade_out_condition_met('take_profit'): # if trade_out_condition_met('take_profit'):
return 'take_profit', trade_out_cmd # return 'take_profit', trade_out_cmd
#
# Conditional stop-loss trades-outs if a signals value equals a set value. # # Conditional stop-loss trades-outs if a signals value equals a set value.
if trade_out_condition_met('stop_loss'): # if trade_out_condition_met('stop_loss'):
return 'stop_loss', trade_out_cmd # return 'stop_loss', trade_out_cmd
#
# No conditions were met. # # No conditions were met.
print('Strategies were updated and nothing to do.') # print('Strategies were updated and nothing to do.')
return 'do_nothing', 'nothing' # return 'do_nothing', 'nothing'
class Strategies: class Strategies:
@ -178,7 +181,7 @@ class Strategies:
""" """
self.data = data # Database interaction instance self.data = data # Database interaction instance
self.trades = trades self.trades = trades
self.strat_list = [] # List to hold strategy objects # self.strat_list = [] # List to hold strategy objects
# Create a cache for strategies with necessary columns # Create a cache for strategies with necessary columns
self.data.create_cache(name='strategies', self.data.create_cache(name='strategies',
@ -188,76 +191,124 @@ class Strategies:
default_expiration=dt.timedelta(hours=24), default_expiration=dt.timedelta(hours=24),
columns=["id", "creator", "name", "workspace", "code", "stats", "public", "fee"]) columns=["id", "creator", "name", "workspace", "code", "stats", "public", "fee"])
def new_strategy(self, data: dict): def new_strategy(self, data: dict) -> dict:
""" """
Add a new strategy to the cache and database. Add a new strategy to the cache and database.
:param data: A dictionary containing strategy data such as name, code, and workspace. :param data: A dictionary containing strategy data such as name, code, and workspace.
:return: A dictionary containing success or failure information.
""" """
# Create a new Strategy object and add it to the list try:
self.strat_list.append(Strategy(**data)) # # Create a new Strategy object and add it to the list
# self.strat_list.append(Strategy(**data))
# Serialize complex data fields like workspace and stats
workspace_serialized = json.dumps(data['workspace']) if isinstance(data['workspace'], dict) else data[
'workspace']
stats_serialized = json.dumps(data.get('stats', {})) # Convert stats to a JSON string
# Insert the strategy into the database and cache # Insert the strategy into the database and cache
self.data.insert_row_into_datacache( self.data.insert_row_into_datacache(
cache_name='strategies', cache_name='strategies',
columns=("creator", "name", "workspace", "code", "stats", "public", "fee"), columns=("creator", "name", "workspace", "code", "stats", "public", "fee"),
values=(data.get('creator'), data['name'], data['workspace'], data['code'], data.get('stats', {}), values=(
data.get('public', False), data.get('fee', 0)) data.get('creator'),
data['name'],
workspace_serialized, # Serialized workspace
data['code'],
stats_serialized, # Serialized stats
data.get('public', False),
data.get('fee', 0)
)
) )
def delete_strategy(self, name: str): # If everything is successful, return a success message
return {"success": True, "message": "Strategy created and saved successfully"}
except Exception as e:
# Catch any exceptions and return a failure message
return {"success": False, "message": f"Failed to create strategy: {str(e)}"}
def delete_strategy(self, user_id, name: str):
""" """
Delete a strategy from the cache and database by name. Delete a strategy from the cache and database by name.
:param user_id: The id of the user making the request.
:param name: The name of the strategy to delete. :param name: The name of the strategy to delete.
""" """
obj = self.get_strategy_by_name(name)
if obj:
self.strat_list.remove(obj)
# Remove the strategy from cache and database # Remove the strategy from cache and database
self.data.remove_row_from_datacache( self.data.remove_row_from_datacache(
cache_name='strategies', cache_name='strategies',
filter_vals=[('name', name)] filter_vals=[('creator', user_id), ('name', name)]
) )
def get_all_strategy_names(self) -> list | None: def get_all_strategy_names(self, user_id) -> list | None:
""" """
Return a list of all strategy names stored in the cache or database. Return a list of all strategy names stored in the cache or database.
""" """
# Fetch all strategy names from 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=[]) strategies_df = self.get_all_strategies(user_id, 'df')
if not strategies_df.empty: if not strategies_df.empty:
return strategies_df['name'].tolist() return strategies_df['name'].tolist()
return None return None
def get_strategies(self, form: str): def get_all_strategies(self, user_id: int, form: str):
""" """
Return strategies stored in this instance in various formats. Return strategies stored in this instance in various formats.
:param user_id: the id of the user making the request.
:param form: The desired format ('obj', 'json', or 'dict'). :param form: The desired format ('obj', 'json', or 'dict').
:return: A list of strategies in the requested format. :return: A list of strategies in the requested format.
""" """
if form == 'obj': # Fetch all public strategies and user's strategies from the cache or database
return self.strat_list public_df = self.data.get_rows_from_datacache(cache_name='strategies', filter_vals=[('public', 1)])
elif form == 'json': user_df = self.data.get_rows_from_datacache(cache_name='strategies', filter_vals=[('creator', user_id),
return [strat.to_json() for strat in self.strat_list] ('public', 0)])
elif form == 'dict':
return [strat.__dict__ for strat in self.strat_list] # Concatenate the two DataFrames (rows from public and user-created strategies)
strategies_df = pd.concat([public_df, user_df], ignore_index=True)
# Return None if no strategies found
if strategies_df.empty:
return None return None
def get_strategy_by_name(self, name: str): # Return the strategies in the requested format
if form == 'df':
return strategies_df
elif form == 'json':
return strategies_df.to_json(orient='records')
elif form == 'dict':
return strategies_df.to_dict('records')
return None
def get_strategy_by_name(self, user_id, name: str):
""" """
Retrieve a strategy object by name. Retrieve a strategy object by name.
:param user_id: The ID of the user making the request.
:param name: The name of the strategy to retrieve. :param name: The name of the strategy to retrieve.
:return: The strategy object if found, otherwise False. :return: The strategy DataFrame row if found, otherwise None.
""" """
for obj in self.strat_list: # Fetch all strategies (public and user-specific) as a DataFrame
if obj.name == name: strategies_df = self.get_all_strategies(user_id, 'df')
return obj
return False # Ensure that strategies_df is not None
if strategies_df is None:
return None
# Filter the DataFrame to find the strategy by name (exact match)
name = name
filtered_strategies = strategies_df.query('name == @name')
# Return None if no matching strategy is found
if filtered_strategies.empty:
return None
# Return the filtered strategy row (or a dictionary if needed)
return filtered_strategies.iloc[0].to_dict()
def execute_cmd(self, strategy, action, cmd): def execute_cmd(self, strategy, action, cmd):
order_type = 'LIMIT' order_type = 'LIMIT'
@ -316,10 +367,10 @@ class Strategies:
# Data object returned to function caller. # Data object returned to function caller.
return_obj = {} return_obj = {}
# Loop through all the published strategies. # Loop through all the published strategies.
for strategy in self.strat_list: # for strategy in self.strat_list:
actions = process_strategy(strategy) # actions = process_strategy(strategy)
stat_updates = get_stats(strategy) # stat_updates = get_stats(strategy)
return_obj[strategy.name] = {'actions': actions, 'stats': stat_updates} # return_obj[strategy.name] = {'actions': actions, 'stats': stat_updates}
if len(return_obj) == 0: if len(return_obj) == 0:
return False return False
else: else:

View File

@ -11,6 +11,11 @@ class Strategies {
} }
// Create the Blockly workspace and define custom blocks // Create the Blockly workspace and define custom blocks
createWorkspace() { createWorkspace() {
if (!document.getElementById('blocklyDiv')) {
console.error("blocklyDiv is not loaded.");
return;
}
// Dispose of the existing workspace before creating a new one // Dispose of the existing workspace before creating a new one
if (this.workspace) { if (this.workspace) {
this.workspace.dispose(); this.workspace.dispose();
@ -30,70 +35,71 @@ class Strategies {
// Define Python generators after workspace initialization // Define Python generators after workspace initialization
this.definePythonGenerators(); this.definePythonGenerators();
} }
// Resize the Blockly workspace
resizeWorkspace() { resizeWorkspace() {
const blocklyDiv = document.getElementById('blocklyDiv'); const blocklyDiv = document.getElementById('blocklyDiv');
if (this.workspace) { if (blocklyDiv && this.workspace) {
// Adjust workspace dimensions to match the blocklyDiv
Blockly.svgResize(this.workspace); Blockly.svgResize(this.workspace);
} else {
console.error("Cannot resize workspace: Blockly or blocklyDiv is not loaded.");
} }
} }
// 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 if (!this.workspace) {
console.error("Workspace is not available.");
return;
}
Blockly.Python.init(this.workspace); Blockly.Python.init(this.workspace);
// Generate Python code from the Blockly workspace
const pythonCode = Blockly.Python.workspaceToCode(this.workspace); const pythonCode = Blockly.Python.workspaceToCode(this.workspace);
// Serialize the workspace to XML format
const workspaceXml = Blockly.Xml.workspaceToDom(this.workspace); const workspaceXml = Blockly.Xml.workspaceToDom(this.workspace);
const workspaceXmlText = Blockly.Xml.domToText(workspaceXml); const workspaceXmlText = Blockly.Xml.domToText(workspaceXml);
const json = { return JSON.stringify({
name: document.getElementById('name_box').value, name: document.getElementById('name_box').value,
code: pythonCode, // This is the generated Python code code: pythonCode,
workspace: workspaceXmlText // Serialized workspace XML workspace: workspaceXmlText
}; });
return JSON.stringify(json);
} }
// Restore the Blockly workspace from XML
restoreWorkspaceFromXml(workspaceXmlText) { restoreWorkspaceFromXml(workspaceXmlText) {
// Convert the text back into an XML DOM object try {
const workspaceXml = Blockly.Xml.textToDom(workspaceXmlText); if (!this.workspace) {
console.error("Cannot restore workspace: Blockly workspace is not initialized.");
// Clear the current workspace before loading a new one return;
if (this.workspace) {
this.workspace.clear();
} }
// Load the workspace from the XML DOM object const workspaceXml = Blockly.utils.xml.textToDom(workspaceXmlText);
Blockly.Xml.domToWorkspace(workspaceXml, this.workspace);
this.workspace.clear(); // Clear the current workspace
Blockly.Xml.domToWorkspace(workspaceXml, this.workspace); // Load the new XML into the workspace
} catch (error) {
console.error('Error restoring workspace from XML:', error);
} }
}
// Fetch saved strategies
fetchSavedStrategies() { fetchSavedStrategies() {
fetch('/get_strategies', { if (window.UI.data.comms) {
method: 'GET' window.UI.data.comms.sendToApp('request', { request: 'strategies', user_name: window.UI.data.user_name });
})
.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)
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 { } else {
console.error('Form element "new_strat_form" not found.'); console.error('Comms instance not available.');
} }
} }
// Set data received from server
set_data(data) {
if (typeof data === 'string') {
data = JSON.parse(data);
}
this.strategies = data;
this.update_html(); // Refresh the strategies display
}
// Hide the "Create New Strategy" form // Hide the "Create New Strategy" form
close_form() { close_form() {
const formElement = document.getElementById("new_strat_form"); const formElement = document.getElementById("new_strat_form");
@ -105,71 +111,137 @@ class Strategies {
} }
} }
submit() { // Submit or edit strategy
// Generate the Python code as JSON submitStrategy(action) {
const strategyJson = this.generateStrategyJson(); const strategyJson = this.generateStrategyJson();
// Get the fee and public checkbox values
const fee = parseFloat(document.getElementById('fee_box').value) || 0; const fee = parseFloat(document.getElementById('fee_box').value) || 0;
if (fee < 0) { if (fee < 0) {
alert("Fee cannot be negative"); alert("Fee cannot be negative");
return; return;
} }
const strategyName = document.getElementById('name_box').value.trim();
if (!strategyName) {
alert("Please provide a name for the strategy.");
return;
}
const is_public = document.getElementById('public_checkbox').checked ? 1 : 0; const is_public = document.getElementById('public_checkbox').checked ? 1 : 0;
// Merge additional fields into the strategy data // Prepare the strategy data
const strategyData = { const strategyData = {
...JSON.parse(strategyJson), // Spread the existing strategy JSON data user_name: window.UI.data.user_name, // Include user_name
fee, // Add the fee ...JSON.parse(strategyJson),
public: is_public // Add the public status fee,
public: is_public
}; };
// Send the strategy to the server via WebSocket through the Comms class // Determine if this is a new strategy or an edit
if (window.UI.data.comms) { // Assuming the Comms instance is accessible via window.UI.data.comms const messageType = action === 'new' ? 'new_strategy' : 'edit_strategy';
window.UI.data.comms.sendToApp('new_strategy', strategyData);
// Format the message and send it using the existing sendToApp function
if (window.UI.data.comms && messageType) {
// Adjust here to pass the messageType and data separately
window.UI.data.comms.sendToApp(messageType, strategyData);
this.close_form();
} else { } else {
console.error("Comms instance not available."); console.error("Comms instance not available or invalid action type.");
} }
} }
// Toggle fee input based on public checkbox
toggleFeeBox() { toggleFeeBox() {
const publicCheckbox = document.getElementById('public_checkbox'); const publicCheckbox = document.getElementById('public_checkbox');
const feeBox = document.getElementById('fee_box'); const feeBox = document.getElementById('fee_box');
feeBox.disabled = !publicCheckbox.checked; // Enable feeBox only if publicCheckbox is checked feeBox.disabled = !publicCheckbox.checked;
} }
// Update the UI with saved strategies
// Update strategies UI
update_html() { update_html() {
let stratsHtml = ''; let stratsHtml = '';
const onClick = "window.UI.strats.del(this.value);";
for (let strat of this.strategies) { for (let strat of this.strategies) {
let deleteButton = `<button type='button' name='delete' class='e_btn' value='${strat.name}' onclick='${onClick}'>&#10008;</button>`; stratsHtml += `
stratsHtml += `<li id='${strat.name}_item'>${deleteButton}<pre>${JSON.stringify(strat, null, 2)}</pre></li>`; <div class="strategy-item">
<button class="delete-button" onclick="window.UI.strats.del('${strat.name}')">&#10008;</button>
<div class="strategy-icon" onclick="window.UI.strats.openForm('edit', '${strat.name}')">
<div class="strategy-name">${strat.name}</div>
</div>
<div class="strategy-hover">
<strong>${strat.name}</strong>
<br>Stats: ${JSON.stringify(strat.stats, null, 2)}
</div>
</div>`;
} }
document.getElementById(this.target_id).innerHTML = stratsHtml; document.getElementById(this.target_id).innerHTML = stratsHtml;
} }
// Open form for creating or editing a strategy
openForm(action, strategyName = null) {
const formElement = document.getElementById("new_strat_form");
// Open the form and create the Blockly workspace if (formElement) {
open_stg_form() { if (action === 'new') {
this.open_form(); document.querySelector("#draggable_header h1").textContent = "Create New Strategy";
document.getElementById("submit-create").style.display = "inline-block";
document.getElementById("submit-edit").style.display = "none";
document.getElementById('name_box').value = '';
document.getElementById('public_checkbox').checked = false;
document.getElementById('fee_box').value = 0;
// Always create a fresh workspace for new strategy
this.createWorkspace();
// Ensure the workspace is resized after being displayed
setTimeout(() => this.resizeWorkspace(), 0);
} else if (action === 'edit' && strategyName) {
// Ensure workspace is created if not already initialized
if (!this.workspace) {
this.createWorkspace(); this.createWorkspace();
} }
// Delete a strategy by its name const strategyData = this.strategies.find(s => s.name === strategyName);
del(name) { if (strategyData) {
window.UI.data.comms.sendToApp('delete_strategy', name); document.querySelector("#draggable_header h1").textContent = "Edit Strategy";
// Remove the strategy from the UI document.getElementById("submit-create").style.display = "none";
let child = document.getElementById(name + '_item'); document.getElementById("submit-edit").style.display = "inline-block";
child.parentNode.removeChild(child);
// Populate the form with the strategy data
document.getElementById('name_box').value = strategyData.name;
document.getElementById('public_checkbox').checked = strategyData.public === 1;
document.getElementById('fee_box').value = strategyData.fee || 0;
// Restore the Blockly workspace from the saved XML
this.restoreWorkspaceFromXml(strategyData.workspace);
}
} }
// Initialize the Strategies UI component formElement.style.display = "grid"; // Display the form
} else {
console.error('Form element "new_strat_form" not found.');
}
}
del(name) {
const deleteData = {
user_name: window.UI.data.user_name, // Include the user_name
strategy_name: name // Strategy name to be deleted
};
// Corrected call to send message type and data separately
window.UI.data.comms.sendToApp('delete_strategy', deleteData);
}
// Initialize strategies
initialize() { initialize() {
this.target = document.getElementById(this.target_id); this.target = document.getElementById(this.target_id);
if (!this.target) { if (!this.target) {
console.error('Target element', this.target_id, 'not found.'); console.error('Target element', this.target_id, 'not found.');
return;
} }
this.fetchSavedStrategies();
} }
// Define Blockly blocks dynamically based on indicators // Define Blockly blocks dynamically based on indicators
defineIndicatorBlocks() { defineIndicatorBlocks() {
const indicatorOutputs = window.UI.indicators.getIndicatorOutputs(); const indicatorOutputs = window.UI.indicators.getIndicatorOutputs();
@ -521,8 +593,6 @@ class Strategies {
// Trade Action block to Python code // Trade Action block to Python code
Blockly.Python['trade_action'] = Blockly.Python.forBlock['trade_action'] = function(block) { 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 condition = Blockly.Python.valueToCode(block, 'CONDITION', Blockly.Python.ORDER_ATOMIC);
var tradeType = block.getFieldValue('TRADE_TYPE'); var tradeType = block.getFieldValue('TRADE_TYPE');
var stopLoss = Blockly.Python.valueToCode(block, 'STOP_LOSS', Blockly.Python.ORDER_ATOMIC) || 'None'; var stopLoss = Blockly.Python.valueToCode(block, 'STOP_LOSS', Blockly.Python.ORDER_ATOMIC) || 'None';

View File

@ -207,9 +207,6 @@ class Comms {
} }
/**
* 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');
@ -256,20 +253,42 @@ class Comms {
if (trade_updts) { if (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') {
window.UI.signals.set_data(message.data); window.UI.signals.set_data(message.data);
} else if (message.reply === 'strategies') { } else if (message.reply === 'strategies') {
window.UI.strats.set_data(message.data); window.UI.strats.set_data(message.data);
} else if (message.reply === 'trades') { } else if (message.reply === 'trades') {
window.UI.trade.set_data(message.data); window.UI.trade.set_data(message.data);
} else if (message.reply === 'signal_created') { } else if (message.reply === 'signal_created') {
const list_of_one = [message.data]; const list_of_one = [message.data];
window.UI.signals.set_data(list_of_one); window.UI.signals.set_data(list_of_one);
} else if (message.reply === 'trade_created') { } else if (message.reply === 'trade_created') {
const list_of_one = [message.data]; const list_of_one = [message.data];
window.UI.trade.set_data(list_of_one); window.UI.trade.set_data(list_of_one);
} else if (message.reply === 'Exchange_connection_result') { } else if (message.reply === 'Exchange_connection_result') {
window.UI.exchanges.postConnection(message.data); window.UI.exchanges.postConnection(message.data);
} else if (message.reply === 'strategy_created') {
// Handle the strategy creation response
if (message.data.success) {
// Success - Notify the user and update the UI
alert(message.data.message); // Display a success message
console.log("New strategy data:", message.data); // Log or handle the new strategy data
// Optionally, refresh the list of strategies
window.UI.strats.fetchSavedStrategies();
} else {
// Failure - Notify the user of the error
alert(`Error: ${message.data.message}`);
console.error("Strategy creation error:", message.data.message);
}
} else { } else {
console.log(message.reply); console.log(message.reply);
console.log(message.data); console.log(message.data);
@ -291,6 +310,7 @@ class Comms {
} }
/** /**
* 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.

View File

@ -10,8 +10,13 @@ class Signals {
request_signals(){ request_signals(){
// Requests a list of all the signals from the server. // Requests a list of all the signals from the server.
window.UI.data.comms.sendToApp('request', 'signals'); if (window.UI.data.comms) {
window.UI.data.comms.sendToApp('request', { request: 'signals', user_name: window.UI.data.user_name });
} else {
console.error('Comms instance not available.');
} }
}
delete_signal(signal_name){ delete_signal(signal_name){
// Requests that the server remove a specific signal. // Requests that the server remove a specific signal.
window.UI.data.comms.sendToApp('delete_signal', signal_name); window.UI.data.comms.sendToApp('delete_signal', signal_name);

View File

@ -82,13 +82,22 @@ class Trade {
// Update the trade value display everytime the quantity input changes. // Update the trade value display everytime the quantity input changes.
this.qtyInput_el.addEventListener('change', update_tradeValue); this.qtyInput_el.addEventListener('change', update_tradeValue);
// Send a request to the server for any loaded data. // Send a request to the server for any loaded data.
window.UI.data.comms.sendToApp('request', 'trades'); this.fetchTrades();
} }
// Call to display the 'Create new trade' dialog. // Call to display the 'Create new trade' dialog.
open_tradeForm() { this.formDialog_el.style.display = "grid"; } open_tradeForm() { this.formDialog_el.style.display = "grid"; }
// Call to hide the 'Create new signal' dialog.
// Call to hide the 'Create trade' dialog.
close_tradeForm() { this.formDialog_el.style.display = "none"; } close_tradeForm() { this.formDialog_el.style.display = "none"; }
fetchTrades(){
if (window.UI.data.comms) {
window.UI.data.comms.sendToApp('request', { request: 'trades', user_name: window.UI.data.user_name });
} else {
console.error('Comms instance not available.');
}
}
// Call to display the 'Trade details' dialog. // Call to display the 'Trade details' dialog.
open_tradeDetails_Form() { document.getElementById("trade_details_form").style.display = "grid"; } open_tradeDetails_Form() { document.getElementById("trade_details_form").style.display = "grid"; }
// Call to hide the 'Create new signal' dialog. // Call to hide the 'Create new signal' dialog.

View File

@ -3,14 +3,12 @@
<!-- Draggable Header Section --> <!-- Draggable Header Section -->
<div id="draggable_header" style="cursor: move; padding: 10px; background-color: #3E3AF2; color: white; border-bottom: 1px solid #ccc;"> <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> <h1 id="form-header-create">Create New Strategy</h1>
<h1 id="form-header-edit" style="display: none;">Edit Strategy</h1>
</div> </div>
<!-- Main Content (Scrollable) --> <!-- 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;"> <form class="form-container" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto; overflow-y: auto;">
<!-- Panel 1 of 1 -->
<div id="strat_pan_1" class="form_panels" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto auto; gap: 10px; padding: 10px;">
<!-- Blockly workspace --> <!-- Blockly workspace -->
<div id="blocklyDiv" style="grid-column: 1; height: 300px; width: 100%;"></div> <div id="blocklyDiv" style="grid-column: 1; height: 300px; width: 100%;"></div>
@ -35,12 +33,14 @@
<!-- Buttons --> <!-- Buttons -->
<div style="grid-column: 1; 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> <!-- Create Button -->
</div> <button id="submit-create" type="button" class="btn next" onclick="UI.strats.submitStrategy('new')">Create Strategy</button>
<!-- Edit Button -->
<button id="submit-edit" type="button" class="btn next" onclick="UI.strats.submitStrategy('edit')" style="display:none;">Edit Strategy</button>
</div> </div>
</form> </form>
<!-- Single Resize Handle in Bottom Right --> <!-- Add the missing resize handle here -->
<div class="resize-handle" id="resize-br"></div> <div class="resize-handle" id="resize-br"></div>
</div> </div>
@ -69,7 +69,6 @@
/* Style the form container to be scrollable and take up remaining space */ /* Style the form container to be scrollable and take up remaining space */
.form-container { .form-container {
height: calc(100% - 60px); /* Takes up the remaining height minus header height */
overflow-y: auto; /* Makes it scrollable */ overflow-y: auto; /* Makes it scrollable */
-ms-overflow-style: none; /* IE and Edge */ -ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */ scrollbar-width: none; /* Firefox */
@ -78,7 +77,6 @@
.form-container::-webkit-scrollbar { .form-container::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */ display: none; /* Chrome, Safari, Opera */
} }
/* Label and input field styling */ /* Label and input field styling */
.form_panels label { .form_panels label {
display: inline-block; display: inline-block;

View File

@ -1,6 +1,100 @@
<div class="content" id="strats_content"> <div class="content" id="strats_content">
<button class="btn" id="new_strats_btn" onclick="UI.strats.open_stg_form()">New Strategy</button> <button class="btn" id="new_strats_btn" onclick="UI.strats.openForm('new')">New Strategy</button>
<hr> <hr>
<h3>Strategies</h3> <h3>Strategies</h3>
<div><ul id="strats_display"></ul></div> <div class="strategies-container" id="strats_display"></div>
</div> </div>
<style>
.strategies-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
position: relative;
}
.strategy-item {
position: relative;
width: 100px;
height: 100px;
text-align: center;
transition: transform 0.3s ease;
z-index: 1; /* Set default z-index */
}
.strategy-item:hover {
transform: scale(1.05);
z-index: 10; /* Bring the hovered item to the front */
}
.strategy-icon {
width: 100px;
height: 100px;
background-image: url('/static/trading_strategy_icon.webp'); /* Path to your icon */
background-size: cover;
background-position: center;
border-radius: 10px;
position: relative;
}
.strategy-name {
position: absolute;
top: 60px;
left: 50%;
transform: translateX(-50%);
background-color: white;
padding: 5px 10px;
border-radius: 20px;
font-size: 12px;
color: black;
text-align: center;
width: 80px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.delete-button {
z-index: 20;
position: absolute;
top: 5px;
left: 5px;
background-color: red;
color: white;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
font-size: 14px;
cursor: pointer;
transition: transform 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease;
}
.delete-button:hover {
transform: scale(1.2); /* Increase size slightly on hover */
background-color: darkred; /* Darken background on hover */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Add shadow effect */
}
.strategy-hover {
display: none;
position: absolute;
top: 0;
left: 110px;
width: 200px;
padding: 10px;
background-color: #f0f0f0;
border-radius: 8px;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
font-size: 12px;
color: #333;
z-index: 50; /* Make sure it hovers above all other elements */
}
.strategy-item:hover .strategy-hover {
display: block;
}
</style>