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:
parent
4e3e8e5abf
commit
adedaaa540
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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}'>✘</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}')">✘</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';
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue