diff --git a/src/Users.py b/src/Users.py index 19da2ef..570d14c 100644 --- a/src/Users.py +++ b/src/Users.py @@ -481,17 +481,21 @@ class UserIndicatorManagement(UserExchangeManagement): :param indicators: A DataFrame containing indicator attributes and properties. """ for _, indicator in indicators.iterrows(): - # Convert necessary fields to JSON strings - src_string = json.dumps(indicator['source']) - prop_string = json.dumps(indicator['properties']) + try: + # Convert necessary fields to JSON strings + src_string = json.dumps(indicator['source']) + prop_string = json.dumps(indicator['properties']) - # Prepare the values and columns for insertion - values = (indicator['creator'], indicator['name'], indicator['visible'], - indicator['kind'], src_string, prop_string) - columns = ('creator', 'name', 'visible', 'kind', 'source', 'properties') + # Prepare the values and columns for insertion + values = (indicator['creator'], indicator['name'], indicator['visible'], + indicator['kind'], src_string, prop_string) + columns = ('creator', 'name', 'visible', 'kind', 'source', 'properties') - # Insert the row into the database and cache using DataCache - self.data.insert_row(cache_name='indicators', columns=columns, values=values) + # Insert the row into the database and cache using DataCache + self.data.insert_row(cache_name='indicators', columns=columns, values=values) + + except Exception as e: + print(f"Error saving indicator {indicator['name']} for creator {indicator['creator']}: {str(e)}") def remove_indicator(self, indicator_name: str, user_name: str) -> None: """ diff --git a/src/app.py b/src/app.py index 229a3cd..ca577b8 100644 --- a/src/app.py +++ b/src/app.py @@ -166,19 +166,19 @@ def settings(): return jsonify({"success": False, "message": "User not logged in"}), 401 # Get the setting that the application wants to change. - # todo: migrate everything to ajax and clean all this up!!! - params = request.form - if not (setting := request.form.get('setting')): - if request.is_json: - setting = request.json.get('setting') - params = request.json.get('indicator') - if not setting: - return jsonify({"success": False, "message": "No setting provided"}), 400 - else: - # Redirect if this is a form submission (non-async request) - return redirect('/') - try: + # Handle JSON or form submissions + if request.is_json: + data = request.json + setting = data.get('setting') + params = data.get('indicator', {}) + else: + setting = request.form.get('setting') + params = request.form.to_dict() + + if not setting: + return jsonify({"success": False, "message": "No setting provided"}), 400 + # Change the setting. brighter_trades.adjust_setting(user_name=user_name, setting=setting, params=params) diff --git a/src/indicators.py b/src/indicators.py index 47d1233..543a3bc 100644 --- a/src/indicators.py +++ b/src/indicators.py @@ -10,6 +10,11 @@ import talib indicators_registry = {} +# Function to generate random hex color +def generate_random_color(): + return f"#{random.randrange(0x1000000):06x}" + + class Indicator: def __init__(self, name: str, indicator_type: str, properties: dict): self.name = name @@ -88,7 +93,7 @@ class SMA(Indicator): def __init__(self, name: str, indicator_type: str, properties: dict): super().__init__(name, indicator_type, properties) # Default display properties for SMA - self.properties.setdefault('color', f"#{random.randrange(0x1000000):06x}") + self.properties.setdefault('color', generate_random_color()) self.properties.setdefault('thickness', 1) # Default line thickness self.properties.setdefault('period', 20) @@ -158,6 +163,9 @@ class BolBands(Indicator): self.properties.setdefault('devup', 2) # Standard deviation for the upper band self.properties.setdefault('devdn', 2) # Standard deviation for the lower band self.properties.setdefault('ma', 0) # Moving average type (0 = Simple Moving Average in TA-Lib) + self.properties.setdefault('color_1', generate_random_color()) # Upper band + self.properties.setdefault('color_2', generate_random_color()) # Middle band + self.properties.setdefault('color_3', generate_random_color()) # Lower band def calculate(self, candles: pd.DataFrame, user_name: str, num_results: int = 1) -> pd.DataFrame: """ @@ -199,6 +207,9 @@ class MACD(Indicator): self.properties.setdefault('fast_p', 12) self.properties.setdefault('slow_p', 26) self.properties.setdefault('signal_p', 9) + self.properties.setdefault('color_1', generate_random_color()) # Upper band + self.properties.setdefault('color_2', generate_random_color()) # Middle band + self.properties.setdefault('color_3', generate_random_color()) # Lower band def calculate(self, candles: pd.DataFrame, user_name: str, num_results: int = 1) -> pd.DataFrame: """ @@ -346,11 +357,11 @@ class Indicators: # Create the dictionary result = {} for _, row in indicators_df.iterrows(): + # Include all properties from the properties dictionary, not just a limited subset. result[row['name']] = { 'type': row['kind'], 'visible': row['visible'], - 'value': row['properties'].get('value', ''), - 'color': row['properties'].get('color', '') + **row['properties'] # This will include all properties in the dictionary } return result @@ -374,22 +385,46 @@ class Indicators: # Set visibility for the specified indicator names self.indicators.loc[self.indicators['name'].isin(indicator_names), 'visible'] = 1 - def edit_indicator(self, user_name: str, params: Any): - # if 'submit' in request.form: - # # Get the name of the indicator - # indicator = request.form['submit'] - # # Drop the name and action from our received data - # attributes = dict(list(request.form.items())[2:]) - # # All the numbers are string now so turn them back to (int) - # for a in attributes: - # if attributes[a].isdigit(): - # attributes[a] = int(attributes[a]) - # # if visible is unchecked it doesn't get sent by the form - # if 'visible' not in attributes: - # attributes.update({'visible': False}) - # # Set the data in indicators according to - # brighter_trades.indicators.indicator_list[indicator] = attributes - pass + def edit_indicator(self, user_name: str, params: dict): + """ + Edits an existing indicator's properties. + + :param user_name: The name of the user. + :param params: The updated properties of the indicator. + """ + indicator_name = params.get('name') + if not indicator_name: + raise ValueError("Indicator name is required for editing.") + + # Get the indicator from the user's indicator list + user_id = self.users.get_id(user_name) + indicator_row = self.indicators.query('name == @indicator_name and creator == @user_id') + + if indicator_row.empty: + raise ValueError(f"Indicator '{indicator_name}' not found for user '{user_name}'.") + + # Update the top-level fields + top_level_keys = ['name', 'visible', 'kind'] # Top-level keys, expand this if needed + for key, value in params.items(): + if key in top_level_keys and key in indicator_row.columns: + self.indicators.at[indicator_row.index[0], key] = value + + # Update 'source' dictionary fields + if 'source' in indicator_row.columns and isinstance(indicator_row['source'].iloc[0], dict): + source_dict = indicator_row['source'].iloc[0] # Direct reference, no need for reassignment later + for key, value in params.items(): + if key in source_dict: + source_dict[key] = value + + # Update 'properties' dictionary fields + if 'properties' in indicator_row.columns and isinstance(indicator_row['properties'].iloc[0], dict): + properties_dict = indicator_row['properties'].iloc[0] # No copy, modify directly + for key, value in params.items(): + if key in properties_dict: + properties_dict[key] = value + + # Save the updated indicator for the user in the database. + self.users.save_indicators(indicator_row) def new_indicator(self, user_name: str, params) -> None: """ @@ -548,6 +583,7 @@ class Indicators: :param visible: Whether to display it in the chart view. :return: None """ + # Todo: Possible refactor to save without storing the indicator instance self.indicators = self.indicators.reset_index(drop=True) creator_id = self.users.get_id(creator) diff --git a/src/static/communication.js b/src/static/communication.js index 8051fad..be4af7a 100644 --- a/src/static/communication.js +++ b/src/static/communication.js @@ -135,7 +135,10 @@ class Comms { const response = await fetch('/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(indicatorData) + body: JSON.stringify({ + setting: 'edit_indicator', + indicator: indicatorData + }) }); return await response.json(); } catch (error) { diff --git a/src/static/indicators.js b/src/static/indicators.js index 29f2254..3f61cc1 100644 --- a/src/static/indicators.js +++ b/src/static/indicators.js @@ -100,9 +100,13 @@ class Indicator { } updateDisplay(name, priceValue, value_name) { -// let rounded_value = (Math.round(priceValue * 100) / 100).toFixed(2); -// // Update the data in the edit and view indicators panel -// document.getElementById(this.name + '_' + value_name).value = rounded_value; + // Update the data in the edit and view indicators panel + let element = document.getElementById(this.name + '_' + value_name) + if (element){ + element.value = (Math.round(priceValue * 100) / 100).toFixed(2); + } else { + console.warn(`Element with ID ${this.name}_${value_name} not found.`); + } } setHist(name, data) { @@ -217,15 +221,15 @@ class RSI extends Indicator { indicatorMap.set("RSI", RSI); class MACD extends Indicator { - constructor(name, charts, color_m, color_s, lineWidth = 2) { + constructor(name, charts, color_1, color_2, lineWidth = 2) { super(name); if (!charts.hasOwnProperty('chart3')) { charts.create_MACD_chart(); } let chart = charts.chart3; - this.addLine('line_m', chart, color_m, lineWidth); - this.addLine('line_s', chart, color_s, lineWidth); + this.addLine('line_m', chart, color_1, lineWidth); + this.addLine('line_s', chart, color_2, lineWidth); this.addHist(name, chart); } @@ -254,7 +258,7 @@ class MACD extends Indicator { })), 'signal'); // Set the histogram - this.setHist('hist', filteredData.map(row => ({ + this.setHist(this.name, filteredData.map(row => ({ time: row.time, value: row.hist }))); @@ -316,11 +320,11 @@ class Volume extends Indicator { indicatorMap.set("Volume", Volume); class Bolenger extends Indicator { - constructor(name, chart, color_u, color_m, color_l, lineWidth = 2) { + constructor(name, chart, color_1, color_2, color_3, lineWidth = 2) { super(name); - this.addLine('line_u', chart, color_u, lineWidth); - this.addLine('line_m', chart, color_m, lineWidth); - this.addLine('line_l', chart, color_l, lineWidth); + this.addLine('line_u', chart, color_1, lineWidth); + this.addLine('line_m', chart, color_2, lineWidth); + this.addLine('line_l', chart, color_3, lineWidth); } static getIndicatorConfig() { @@ -365,7 +369,7 @@ class Indicators { this.comms = comms; } - create_indicators(indicators, charts, bt_data) { + create_indicators(indicators, charts) { for (let name in indicators) { if (!indicators[name].visible) continue; @@ -380,9 +384,9 @@ class Indicators { if (arg === 'charts') return charts; if (arg === 'chart_1') return charts.chart_1; if (arg === 'color') return indicators[name].color; - if (arg === 'color_1') return '#FF0000'; //bt_data.indicators[name].color_1 || red; - if (arg === 'color_2') return 'red'; // bt_data.indicators[name].color_2 || white; - if (arg === 'color_3') return 'red'; // bt_data.indicators[name].color_3 || blue; + if (arg === 'color_1') return indicators[name].color_1 || 'red'; + if (arg === 'color_2') return indicators[name].color_2 || 'white'; + if (arg === 'color_3') return indicators[name].color_3 || 'blue'; }); this.i_objs[name] = new IndicatorConstructor(...preparedArgs); @@ -460,15 +464,20 @@ class Indicators { const row = event.target.closest('.indicator-row'); const inputs = row.querySelectorAll('input, select'); + // Gather the indicator name from the row + const nameDiv = row.querySelector('div:nth-child(2)'); // Second
contains the name + const indicatorName = nameDiv.innerText.trim(); // Get the indicator name + // Gather input data - const formObj = {}; + const formObj = { name: indicatorName }; // Initialize formObj with the name inputs.forEach(input => { formObj[input.name] = input.type === 'checkbox' ? input.checked : input.value; }); + // Call comms to send data to the server this.comms.updateIndicator(formObj).then(response => { if (response.success) { - alert('Indicator updated successfully.'); + window.location.reload(); // This triggers a full page refresh } else { alert('Failed to update the indicator.'); } diff --git a/src/templates/indicators_hud.html b/src/templates/indicators_hud.html index 86a0b0d..6d3846c 100644 --- a/src/templates/indicators_hud.html +++ b/src/templates/indicators_hud.html @@ -1,69 +1,155 @@
- +
+
+ +
-
-

Indicators

-
-
-

Name

-
-
-

Type

-
-
-

Properties

-
- - {% for indicator in indicator_list %} -
- -
- - -
+
-
{{indicator}}
+ +
+ +
+

Remove or Edit

+

Name

+

Type

+

Value

+

Visible

+

Period

+

Color

+

Properties

+
+
- {% for property in indicator_list[indicator] %} - {% set list1 = property.split('_') %} -
- - {% if property=='type' %} - - {% elif property=='ma' %} - - {% elif property=='color' or list1[0]=='color' %} - - {% elif property=='period' %} - - {% elif property=='visible' %} - - {% elif property=='value' %} - - {% else %} - + + {% for indicator in indicator_list %} +
+ + +
+ + +
+ + +
{{indicator}}
+ + +
+ {% if 'type' in indicator_list[indicator] %} + + {% else %} + - + {% endif %} +
+ + +
+ {% if 'value' in indicator_list[indicator] %} + + {% else %} + - + {% endif %} +
+ + +
+ {% if 'visible' in indicator_list[indicator] %} + + {% else %} + - + {% endif %} +
+ + +
+ {% if 'period' in indicator_list[indicator] %} + + {% else %} + - + {% endif %} +
+ + +
+ {% if 'color' in indicator_list[indicator] %} + + {% else %} + - + {% endif %} +
+ + +
+ {% for property, value in indicator_list[indicator].items() %} + {% if property not in ['type', 'value', 'color', 'period', 'visible'] %} +
+ + {% if 'color' in property %} + + + {% else %} + + + {% endif %} +
{% endif %} -
{% endfor %}
- {% endfor %} - +
+ {% endfor %}
+ +