Everything is working I am just about to refactor indicator storage.

This commit is contained in:
Rob 2024-09-09 15:43:16 -03:00
parent 73ed1a092a
commit f2b7621b6d
6 changed files with 247 additions and 109 deletions

View File

@ -481,6 +481,7 @@ class UserIndicatorManagement(UserExchangeManagement):
:param indicators: A DataFrame containing indicator attributes and properties. :param indicators: A DataFrame containing indicator attributes and properties.
""" """
for _, indicator in indicators.iterrows(): for _, indicator in indicators.iterrows():
try:
# Convert necessary fields to JSON strings # Convert necessary fields to JSON strings
src_string = json.dumps(indicator['source']) src_string = json.dumps(indicator['source'])
prop_string = json.dumps(indicator['properties']) prop_string = json.dumps(indicator['properties'])
@ -493,6 +494,9 @@ class UserIndicatorManagement(UserExchangeManagement):
# Insert the row into the database and cache using DataCache # Insert the row into the database and cache using DataCache
self.data.insert_row(cache_name='indicators', columns=columns, values=values) 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: def remove_indicator(self, indicator_name: str, user_name: str) -> None:
""" """
Removes a specific indicator from the database and cache. Removes a specific indicator from the database and cache.

View File

@ -166,19 +166,19 @@ def settings():
return jsonify({"success": False, "message": "User not logged in"}), 401 return jsonify({"success": False, "message": "User not logged in"}), 401
# Get the setting that the application wants to change. # Get the setting that the application wants to change.
# todo: migrate everything to ajax and clean all this up!!! try:
params = request.form # Handle JSON or form submissions
if not (setting := request.form.get('setting')):
if request.is_json: if request.is_json:
setting = request.json.get('setting') data = request.json
params = request.json.get('indicator') setting = data.get('setting')
params = data.get('indicator', {})
else:
setting = request.form.get('setting')
params = request.form.to_dict()
if not setting: if not setting:
return jsonify({"success": False, "message": "No setting provided"}), 400 return jsonify({"success": False, "message": "No setting provided"}), 400
else:
# Redirect if this is a form submission (non-async request)
return redirect('/')
try:
# Change the setting. # Change the setting.
brighter_trades.adjust_setting(user_name=user_name, setting=setting, params=params) brighter_trades.adjust_setting(user_name=user_name, setting=setting, params=params)

View File

@ -10,6 +10,11 @@ import talib
indicators_registry = {} indicators_registry = {}
# Function to generate random hex color
def generate_random_color():
return f"#{random.randrange(0x1000000):06x}"
class Indicator: class Indicator:
def __init__(self, name: str, indicator_type: str, properties: dict): def __init__(self, name: str, indicator_type: str, properties: dict):
self.name = name self.name = name
@ -88,7 +93,7 @@ class SMA(Indicator):
def __init__(self, name: str, indicator_type: str, properties: dict): def __init__(self, name: str, indicator_type: str, properties: dict):
super().__init__(name, indicator_type, properties) super().__init__(name, indicator_type, properties)
# Default display properties for SMA # 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('thickness', 1) # Default line thickness
self.properties.setdefault('period', 20) 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('devup', 2) # Standard deviation for the upper band
self.properties.setdefault('devdn', 2) # Standard deviation for the lower 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('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: 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('fast_p', 12)
self.properties.setdefault('slow_p', 26) self.properties.setdefault('slow_p', 26)
self.properties.setdefault('signal_p', 9) 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: def calculate(self, candles: pd.DataFrame, user_name: str, num_results: int = 1) -> pd.DataFrame:
""" """
@ -346,11 +357,11 @@ class Indicators:
# Create the dictionary # Create the dictionary
result = {} result = {}
for _, row in indicators_df.iterrows(): for _, row in indicators_df.iterrows():
# Include all properties from the properties dictionary, not just a limited subset.
result[row['name']] = { result[row['name']] = {
'type': row['kind'], 'type': row['kind'],
'visible': row['visible'], 'visible': row['visible'],
'value': row['properties'].get('value', ''), **row['properties'] # This will include all properties in the dictionary
'color': row['properties'].get('color', '')
} }
return result return result
@ -374,22 +385,46 @@ class Indicators:
# Set visibility for the specified indicator names # Set visibility for the specified indicator names
self.indicators.loc[self.indicators['name'].isin(indicator_names), 'visible'] = 1 self.indicators.loc[self.indicators['name'].isin(indicator_names), 'visible'] = 1
def edit_indicator(self, user_name: str, params: Any): def edit_indicator(self, user_name: str, params: dict):
# if 'submit' in request.form: """
# # Get the name of the indicator Edits an existing indicator's properties.
# indicator = request.form['submit']
# # Drop the name and action from our received data :param user_name: The name of the user.
# attributes = dict(list(request.form.items())[2:]) :param params: The updated properties of the indicator.
# # All the numbers are string now so turn them back to (int) """
# for a in attributes: indicator_name = params.get('name')
# if attributes[a].isdigit(): if not indicator_name:
# attributes[a] = int(attributes[a]) raise ValueError("Indicator name is required for editing.")
# # if visible is unchecked it doesn't get sent by the form
# if 'visible' not in attributes: # Get the indicator from the user's indicator list
# attributes.update({'visible': False}) user_id = self.users.get_id(user_name)
# # Set the data in indicators according to <attributes> indicator_row = self.indicators.query('name == @indicator_name and creator == @user_id')
# brighter_trades.indicators.indicator_list[indicator] = attributes
pass 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: 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. :param visible: Whether to display it in the chart view.
:return: None :return: None
""" """
# Todo: Possible refactor to save without storing the indicator instance
self.indicators = self.indicators.reset_index(drop=True) self.indicators = self.indicators.reset_index(drop=True)
creator_id = self.users.get_id(creator) creator_id = self.users.get_id(creator)

View File

@ -135,7 +135,10 @@ class Comms {
const response = await fetch('/settings', { const response = await fetch('/settings', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(indicatorData) body: JSON.stringify({
setting: 'edit_indicator',
indicator: indicatorData
})
}); });
return await response.json(); return await response.json();
} catch (error) { } catch (error) {

View File

@ -100,9 +100,13 @@ class Indicator {
} }
updateDisplay(name, priceValue, value_name) { 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
// // Update the data in the edit and view indicators panel let element = document.getElementById(this.name + '_' + value_name)
// document.getElementById(this.name + '_' + value_name).value = rounded_value; 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) { setHist(name, data) {
@ -217,15 +221,15 @@ class RSI extends Indicator {
indicatorMap.set("RSI", RSI); indicatorMap.set("RSI", RSI);
class MACD extends Indicator { class MACD extends Indicator {
constructor(name, charts, color_m, color_s, lineWidth = 2) { constructor(name, charts, color_1, color_2, lineWidth = 2) {
super(name); super(name);
if (!charts.hasOwnProperty('chart3')) { if (!charts.hasOwnProperty('chart3')) {
charts.create_MACD_chart(); charts.create_MACD_chart();
} }
let chart = charts.chart3; let chart = charts.chart3;
this.addLine('line_m', chart, color_m, lineWidth); this.addLine('line_m', chart, color_1, lineWidth);
this.addLine('line_s', chart, color_s, lineWidth); this.addLine('line_s', chart, color_2, lineWidth);
this.addHist(name, chart); this.addHist(name, chart);
} }
@ -254,7 +258,7 @@ class MACD extends Indicator {
})), 'signal'); })), 'signal');
// Set the histogram // Set the histogram
this.setHist('hist', filteredData.map(row => ({ this.setHist(this.name, filteredData.map(row => ({
time: row.time, time: row.time,
value: row.hist value: row.hist
}))); })));
@ -316,11 +320,11 @@ class Volume extends Indicator {
indicatorMap.set("Volume", Volume); indicatorMap.set("Volume", Volume);
class Bolenger extends Indicator { 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); super(name);
this.addLine('line_u', chart, color_u, lineWidth); this.addLine('line_u', chart, color_1, lineWidth);
this.addLine('line_m', chart, color_m, lineWidth); this.addLine('line_m', chart, color_2, lineWidth);
this.addLine('line_l', chart, color_l, lineWidth); this.addLine('line_l', chart, color_3, lineWidth);
} }
static getIndicatorConfig() { static getIndicatorConfig() {
@ -365,7 +369,7 @@ class Indicators {
this.comms = comms; this.comms = comms;
} }
create_indicators(indicators, charts, bt_data) { create_indicators(indicators, charts) {
for (let name in indicators) { for (let name in indicators) {
if (!indicators[name].visible) continue; if (!indicators[name].visible) continue;
@ -380,9 +384,9 @@ class Indicators {
if (arg === 'charts') return charts; if (arg === 'charts') return charts;
if (arg === 'chart_1') return charts.chart_1; if (arg === 'chart_1') return charts.chart_1;
if (arg === 'color') return indicators[name].color; if (arg === 'color') return indicators[name].color;
if (arg === 'color_1') return '#FF0000'; //bt_data.indicators[name].color_1 || red; if (arg === 'color_1') return indicators[name].color_1 || 'red';
if (arg === 'color_2') return 'red'; // bt_data.indicators[name].color_2 || white; if (arg === 'color_2') return indicators[name].color_2 || 'white';
if (arg === 'color_3') return 'red'; // bt_data.indicators[name].color_3 || blue; if (arg === 'color_3') return indicators[name].color_3 || 'blue';
}); });
this.i_objs[name] = new IndicatorConstructor(...preparedArgs); this.i_objs[name] = new IndicatorConstructor(...preparedArgs);
@ -460,15 +464,20 @@ class Indicators {
const row = event.target.closest('.indicator-row'); const row = event.target.closest('.indicator-row');
const inputs = row.querySelectorAll('input, select'); const inputs = row.querySelectorAll('input, select');
// Gather the indicator name from the row
const nameDiv = row.querySelector('div:nth-child(2)'); // Second <div> contains the name
const indicatorName = nameDiv.innerText.trim(); // Get the indicator name
// Gather input data // Gather input data
const formObj = {}; const formObj = { name: indicatorName }; // Initialize formObj with the name
inputs.forEach(input => { inputs.forEach(input => {
formObj[input.name] = input.type === 'checkbox' ? input.checked : input.value; formObj[input.name] = input.type === 'checkbox' ? input.checked : input.value;
}); });
// Call comms to send data to the server
this.comms.updateIndicator(formObj).then(response => { this.comms.updateIndicator(formObj).then(response => {
if (response.success) { if (response.success) {
alert('Indicator updated successfully.'); window.location.reload(); // This triggers a full page refresh
} else { } else {
alert('Failed to update the indicator.'); alert('Failed to update the indicator.');
} }

View File

@ -1,69 +1,155 @@
<div class="content" id="indicator_panel" style="max-height: 70px;"> <div class="content" id="indicator_panel" style="max-height: 70px;">
<!-- Edit Indicator Panel --> <!-- Indicator Panel Section -->
<div id="edit_indcr_panel" style="display: grid; grid-template-columns: 1fr 1fr;"> <div id="edit_indcr_panel" style="display: grid; grid-template-columns: 1fr 1fr;">
<!-- Button for adding a new indicator -->
<div class="section" style="grid-column: 1;"> <div class="section" style="grid-column: 1;">
<button class="btn" onclick="UI.indicators.open_form()">New Indicator</button> <button class="btn" onclick="UI.indicators.open_form()">New Indicator</button>
</div> </div>
<!-- Button for displaying indicator options -->
<div class="section" style="grid-column: 2;"> <div class="section" style="grid-column: 2;">
<button class="btn">Indicator Options</button> <button class="btn">Indicator Options</button>
</div> </div>
<div class="section" style="display: grid; grid-row: 2; grid-column: 1 / span 2; grid-template-columns: 75px 200px 200px auto; overflow-x: scroll;">
<h3 style="grid-column: 1 / span 2; text-align: left;">Indicators</h3>
<hr style="width: 100%; grid-column: 1 / span 10;">
<div style="grid-column: 2;">
<h3>Name</h3>
</div> </div>
<div style="grid-column: 3;">
<h3>Type</h3> <!-- Section for displaying the list of indicators -->
<div class="section" style="margin-top: 15px; overflow-x: auto; display: grid; grid-auto-rows: minmax(80px, auto); grid-template-columns: 75px 200px 150px 100px 75px 100px 100px auto; gap: 10px; padding-left: 10px;">
<!-- Background color change for this section -->
<div style="background-color: #F7E1C1; grid-column: 1 / span 8; padding: 5px 0; display: grid; grid-template-columns: 75px 200px 150px 100px 75px 100px 100px auto; gap: 10px;">
<div style="text-align: center;"><h3 class="header-text">Remove or Edit</h3></div>
<div style="text-align: center;"><h3 class="header-text">Name</h3></div>
<div style="text-align: center;"><h3 class="header-text">Type</h3></div>
<div style="text-align: center;"><h3 class="header-text">Value</h3></div>
<div style="text-align: center;"><h3 class="header-text">Visible</h3></div>
<div style="text-align: center;"><h3 class="header-text">Period</h3></div>
<div style="text-align: center;"><h3 class="header-text">Color</h3></div>
<div style="text-align: center;"><h3 class="header-text">Properties</h3></div>
<hr style="grid-column: 1 / span 8;width: 100%; height: 1px; border: 1px solid #ccc; margin: 5px 0;">
</div> </div>
<div style="grid-column: 4 / span 8;">
<h3>Properties</h3> <!-- Loop through each indicator in indicator_list -->
</div>
<!-- Edit Indicator Rows without form -->
{% for indicator in indicator_list %} {% for indicator in indicator_list %}
<div class="indicator-row" style="display: grid; grid-column: 1 / span 10; grid-template-columns: 75px 200px 200px repeat(7, 1fr);"> <div class="indicator-row" style="display: grid; grid-column: 1 / span 8; align-items: center; grid-template-columns: 75px 200px 150px 100px 75px 100px 100px auto; gap: 10px; padding-left: 10px;">
<input type="hidden" name="setting" value="edit_indicator" />
<div id="edit_indctr_controls" style="grid-column: 1;"> <!-- Buttons to delete or update the indicator -->
<div style="text-align: center;">
<button type="button" class="e_btn" onclick="UI.indicators.deleteIndicator('{{indicator}}', event)">&#10008;</button> <button type="button" class="e_btn" onclick="UI.indicators.deleteIndicator('{{indicator}}', event)">&#10008;</button>
<button type="button" class="e_btn" style="color:darkgreen;" onclick="UI.indicators.updateIndicator(event)">&#x2714;</button> <button type="button" class="e_btn" style="color:darkgreen;" onclick="UI.indicators.updateIndicator(event)">&#x2714;</button>
</div> </div>
<div class="iename" style="grid-column: 2;">{{indicator}}</div> <!-- Display the indicator name -->
<div style="text-align: center;">{{indicator}}</div>
{% for property in indicator_list[indicator] %} <!-- Fixed property: Type (Dropdown) -->
{% set list1 = property.split('_') %} <div style="text-align: center;">
<div class="ieprop"> {% if 'type' in indicator_list[indicator] %}
<label class="ietextbox" for="{{indicator}}_{{property}}">{{property}}</label> <select class="ietextbox" id="{{indicator}}_type" name="type">
{% if property=='type' %}
<select class="ietextbox" id="{{indicator}}_{{property}}" name="{{property}}">
{% for i_type in indicator_types %} {% for i_type in indicator_types %}
<option value="{{i_type}}" {% if indicator_list[indicator][property] == i_type %} selected="selected"{%endif%}>{{i_type}}</option> <option value="{{i_type}}" {% if indicator_list[indicator]['type'] == i_type %} selected="selected"{% endif %}>{{i_type}}</option>
{% endfor %} {% endfor %}
</select> </select>
{% elif property=='ma' %}
<select class="ietextbox" id="{{indicator}}_{{property}}" name="{{property}}">
{% for ma_val in ma_vals %}
<option value="{{ma_vals[ma_val]}}" {% if indicator_list[indicator][property] == ma_vals[ma_val] %} selected="selected"{%endif%}>{{ma_val}}</option>
{% endfor %}
</select>
{% elif property=='color' or list1[0]=='color' %}
<input class="ietextbox" type="color" id="{{indicator}}_{{property}}" value="{{indicator_list[indicator][property]}}" name="{{property}}">
{% elif property=='period' %}
<input class="ietextbox" type="number" id="{{indicator}}_{{property}}" value="{{indicator_list[indicator][property]}}" name="{{property}}">
{% elif property=='visible' %}
<input class="ietextbox" type="checkbox" id="{{indicator}}_{{property}}" value="{{indicator_list[indicator][property]}}" name="{{property}}" {% if indicator in checked %} checked {%endif%}>
{% elif property=='value' %}
<input class="ie_value" type="number" id="{{indicator}}_{{property}}" value="{{indicator_list[indicator][property]}}" name="{{property}}" readonly>
{% else %} {% else %}
<input class="ietextbox" type="text" id="{{indicator}}_{{property}}" value="{{indicator_list[indicator][property]}}" name="{{property}}"> <span>-</span>
{% endif %} {% endif %}
</div> </div>
<!-- Fixed property: Value (Readonly Number) -->
<div style="text-align: center;">
{% if 'value' in indicator_list[indicator] %}
<input class="ie_value" type="number" id="{{indicator}}_value" value="{{indicator_list[indicator]['value']}}" name="value" readonly>
{% else %}
<span>-</span>
{% endif %}
</div>
<!-- Fixed property: Visible (Checkbox) -->
<div style="text-align: center;">
{% if 'visible' in indicator_list[indicator] %}
<input class="ietextbox" type="checkbox" id="{{indicator}}_visible" value="{{indicator_list[indicator]['visible']}}" name="visible" {% if indicator in checked %} checked {% endif %}>
{% else %}
<span>-</span>
{% endif %}
</div>
<!-- Fixed property: Period (Number Input) -->
<div style="text-align: center;">
{% if 'period' in indicator_list[indicator] %}
<input class="ietextbox" type="number" id="{{indicator}}_period" value="{{indicator_list[indicator]['period']}}" name="period">
{% else %}
<span>-</span>
{% endif %}
</div>
<!-- Fixed property: Color (Color Picker) -->
<div style="text-align: center;">
{% if 'color' in indicator_list[indicator] %}
<input class="ietextbox" type="color" id="{{indicator}}_color" value="{{indicator_list[indicator]['color']}}" name="color">
{% else %}
<span>-</span>
{% endif %}
</div>
<!-- Dynamic properties (remaining ones not covered by known properties) -->
<div style="grid-column: span 1; text-align: left; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;">
{% for property, value in indicator_list[indicator].items() %}
{% if property not in ['type', 'value', 'color', 'period', 'visible'] %}
<div style="margin-bottom: 17px;">
<label for="{{indicator}}_{{property}}" class="ietextbox-label">{{property}}</label>
{% if 'color' in property %}
<!-- Color picker for properties with 'color' in the name -->
<input class="ietextbox" type="color" id="{{indicator}}_{{property}}" value="{{value}}" name="{{property}}">
{% else %}
<!-- Regular text input for other properties -->
<input class="ietextbox" type="text" id="{{indicator}}_{{property}}" value="{{value}}" name="{{property}}">
{% endif %}
</div>
{% endif %}
{% endfor %} {% endfor %}
</div> </div>
{% endfor %}
<!-- End of Rows for each indicator -->
</div> </div>
{% endfor %}
</div> </div>
</div> </div>
<style>
.section .column {
padding: 10px;
max-width: 200px; /* Set max-width for all columns */
}
.section h3 {
margin-bottom: 0;
margin-top: 0;
}
.section .buttons-column {
max-width: 75px; /* Buttons column */
}
.section .visible-column {
max-width: 75px; /* Visible checkbox column */
}
/* Alternate row background colors */
.indicator-row:nth-child(even) {
background-color: #E0E0E0;
}
.indicator-row:nth-child(odd) {
background-color: #F7E1C1;
}
/* Bold labels with drop shadow for dynamic properties */
.ietextbox-label {
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}
/* Drop shadow for header text */
.header-text {
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}
</style>