got all the indicators working again

This commit is contained in:
Rob 2024-09-08 04:04:58 -03:00
parent b666ec22af
commit 73ed1a092a
7 changed files with 309 additions and 106 deletions

View File

@ -533,6 +533,9 @@ class BrighterTrades:
elif setting == 'edit_indicator': elif setting == 'edit_indicator':
self.indicators.edit_indicator(user_name=user_name, params=params) self.indicators.edit_indicator(user_name=user_name, params=params)
elif setting == 'delete_indicator':
self.indicators.delete_indicator(user_name=user_name, indicator_name=params)
elif setting == 'new_indicator': elif setting == 'new_indicator':
self.indicators.new_indicator(user_name=user_name, params=params) self.indicators.new_indicator(user_name=user_name, params=params)

View File

@ -155,24 +155,43 @@ def settings():
edit_indicator: Edits the properties of specific indicators. edit_indicator: Edits the properties of specific indicators.
toggle_indicator: Enables or disables display of indicators in the viewport. toggle_indicator: Enables or disables display of indicators in the viewport.
:return: None - Redirects to index. :return: None - Redirects to index, or returns JSON for async requests.
""" """
# Request the user_name from the client session. # Request the user_name from the client session.
if not (user_name := session.get('user')): if not (user_name := session.get('user')):
return redirect('/') return jsonify({"success": False, "message": "Not logged in"}), 401
# Return if the user is not logged in. # Return if the user is not logged in.
if not brighter_trades.get_user_info(user_name=user_name, info='Is logged in?'): if not brighter_trades.get_user_info(user_name=user_name, info='Is logged in?'):
return redirect('/') return jsonify({"success": False, "message": "User not logged in"}), 401
# Get the setting that the web application wants to change. # 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 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('/') return redirect('/')
try:
# Change the setting. # Change the setting.
brighter_trades.adjust_setting(user_name=user_name, setting=setting, params=request.form) brighter_trades.adjust_setting(user_name=user_name, setting=setting, params=params)
# Return success as JSON if called via an async request
if request.is_json:
return jsonify({"success": True}), 200
# Redirect if this is a form submission (non-async request)
return redirect('/') return redirect('/')
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
@app.route('/api/history', methods=['POST', 'GET']) @app.route('/api/history', methods=['POST', 'GET'])
def history(): def history():

View File

@ -151,6 +151,14 @@ class ATR(SMA):
class BolBands(Indicator): class BolBands(Indicator):
def __init__(self, name: str, indicator_type: str, properties: dict):
super().__init__(name, indicator_type, properties)
# Default display properties
self.properties.setdefault('period', 20) # The default period for the moving average
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)
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:
""" """
Calculate the Bollinger Bands indicator for the given candles. Calculate the Bollinger Bands indicator for the given candles.
@ -177,11 +185,21 @@ class BolBands(Indicator):
'lower': lower 'lower': lower
}) })
# Round all values at once
df = df.round({'upper': 2, 'middle': 2, 'lower': 2})
# Slice the DataFrame to skip initial rows where the indicator might be undefined # Slice the DataFrame to skip initial rows where the indicator might be undefined
return df.iloc[self.properties['period']:] return df.iloc[self.properties['period']:]
class MACD(Indicator): class MACD(Indicator):
def __init__(self, name: str, indicator_type: str, properties: dict):
super().__init__(name, indicator_type, properties)
# Default display properties
self.properties.setdefault('fast_p', 12)
self.properties.setdefault('slow_p', 26)
self.properties.setdefault('signal_p', 9)
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:
""" """
Calculate the MACD indicator for the given candles. Calculate the MACD indicator for the given candles.
@ -207,6 +225,9 @@ class MACD(Indicator):
'hist': hist 'hist': hist
}) })
# Replace NaN values with None for JSON compatibility
df = df.replace({np.nan: None})
# Slice the DataFrame to skip initial rows where the indicator will be undefined # Slice the DataFrame to skip initial rows where the indicator will be undefined
return df.iloc[self.properties['signal_p']:] return df.iloc[self.properties['signal_p']:]
@ -368,11 +389,7 @@ class Indicators:
# attributes.update({'visible': False}) # attributes.update({'visible': False})
# # Set the data in indicators according to <attributes> # # Set the data in indicators according to <attributes>
# brighter_trades.indicators.indicator_list[indicator] = attributes # brighter_trades.indicators.indicator_list[indicator] = attributes
# pass
if 'delete' in params:
indicator = params['delete']
# This will delete in both indicators and config.
self.delete_indicator(indicator_name=indicator, user_name=user_name)
def new_indicator(self, user_name: str, params) -> None: def new_indicator(self, user_name: str, params) -> None:
""" """
@ -505,10 +522,16 @@ class Indicators:
""" """
if not indicator_name: if not indicator_name:
raise ValueError("No indicator name provided.") raise ValueError("No indicator name provided.")
self.users.remove_indicator(indicator_name=indicator_name, user_name=user_name)
# Force reload from database to refresh cache # Get the user ID to filter the indicators belonging to the user
self.load_indicators(user_name=user_name) user_id = self.users.get_id(user_name)
# Remove the indicator from the DataFrame where the name matches and the creator is the user
self.indicators = self.indicators[
~((self.indicators['name'] == indicator_name) & (self.indicators['creator'] == user_id))
]
self.users.remove_indicator(indicator_name=indicator_name, user_name=user_name)
def create_indicator(self, creator: str, name: str, kind: str, def create_indicator(self, creator: str, name: str, kind: str,
source: dict, properties: dict, visible: bool = True): source: dict, properties: dict, visible: bool = True):

View File

@ -106,6 +106,44 @@ class Comms {
} }
} }
/**
* Sends a request to delete an indicator.
* @param {string} indicatorName - The name of the indicator to be deleted.
* @returns {Promise<Object>} - The response from the server.
*/
async deleteIndicator(indicatorName) {
try {
const response = await fetch('/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ setting: 'delete_indicator', indicator: indicatorName })
});
return await response.json();
} catch (error) {
console.error('Error deleting indicator:', error);
return { success: false };
}
}
/**
* Sends a request to update an indicator's properties.
* @param {Object} indicatorData - An object containing the updated properties of the indicator.
* @returns {Promise<Object>} - The response from the server.
*/
async updateIndicator(indicatorData) {
try {
const response = await fetch('/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(indicatorData)
});
return await response.json();
} catch (error) {
console.error('Error updating indicator:', error);
return { success: false };
}
}
/** /**
* Sends a message to the application server. * Sends a message to the application server.
* @param {string} messageType - The type of the message. * @param {string} messageType - The type of the message.

View File

@ -1,11 +1,4 @@
//
//class Header {
// constructor() {
// this.height = height;
// }
//}
class User_Interface{ class User_Interface{
/* This contains all the code for our User interface. /* This contains all the code for our User interface.
The code is separated into classes that maintain and The code is separated into classes that maintain and
@ -45,7 +38,7 @@ class User_Interface{
/* Indicators contains methods that interact with indicator /* Indicators contains methods that interact with indicator
related menus and update indicator output in charts and panels. */ related menus and update indicator output in charts and panels. */
this.indicators = new Indicators(); this.indicators = new Indicators(this.data.comms);
/* Register an Indicator callback method to receive updates from the data class. */ /* Register an Indicator callback method to receive updates from the data class. */
this.data.registerCallback_i_updates(this.indicators.update); this.data.registerCallback_i_updates(this.indicators.update);

View File

@ -26,6 +26,15 @@ class Indicator_Output {
} }
this.legend[name].innerHTML = name + ' <span style="color:rgba(4, 111, 232, 1)">' + val + '</span>'; this.legend[name].innerHTML = name + ' <span style="color:rgba(4, 111, 232, 1)">' + val + '</span>';
} }
clear_legend(name) {
// Remove the legend div from the DOM
if (this.legend[name]) {
this.legend[name].remove(); // Remove the legend from the DOM
delete this.legend[name]; // Remove the reference from the object
} else {
console.warn(`Legend for ${name} not found.`);
}
}
} }
iOutput = new Indicator_Output(); iOutput = new Indicator_Output();
@ -91,9 +100,9 @@ class Indicator {
} }
updateDisplay(name, priceValue, value_name) { updateDisplay(name, priceValue, value_name) {
let rounded_value = (Math.round(priceValue * 100) / 100).toFixed(2); // 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
document.getElementById(this.name + '_' + value_name).value = rounded_value; // document.getElementById(this.name + '_' + value_name).value = rounded_value;
} }
setHist(name, data) { setHist(name, data) {
@ -112,6 +121,37 @@ class Indicator {
updateHist(name, data) { updateHist(name, data) {
this.hist[name].update(data); this.hist[name].update(data);
} }
removeFromChart(chart) {
// Ensure the chart object is passed
if (!chart) {
console.error("Chart object is missing.");
return;
}
// Remove all line series associated with this indicator
for (let lineName in this.lines) {
if (this.lines[lineName]) {
chart.removeSeries(this.lines[lineName]);
delete this.lines[lineName];
}
}
// Remove all histogram series associated with this indicator
for (let histName in this.hist) {
if (this.hist[histName]) {
chart.removeSeries(this.hist[histName]);
delete this.hist[histName];
}
}
// Remove the legend from the crosshair (if any)
if (iOutput.legend[this.name]) {
iOutput.legend[this.name].remove();
delete iOutput.legend[this.name];
}
}
} }
class SMA extends Indicator { class SMA extends Indicator {
@ -142,7 +182,7 @@ indicatorMap.set("SMA", SMA);
class Linear_Regression extends SMA { class Linear_Regression extends SMA {
// Inherits getIndicatorConfig from SMA // Inherits getIndicatorConfig from SMA
} }
indicatorMap.set("Linear_Regression", Linear_Regression); indicatorMap.set("LREG", Linear_Regression);
class EMA extends SMA { class EMA extends SMA {
// Inherits getIndicatorConfig from SMA // Inherits getIndicatorConfig from SMA
@ -197,15 +237,41 @@ class MACD extends Indicator {
} }
init(data) { init(data) {
this.setLine('line_m', data[0], 'macd'); // Filter out rows where macd, signal, or hist are null
this.setLine('line_s', data[1], 'signal'); const filteredData = data.filter(row => row.macd !== null && row.signal !== null && row.hist !== null);
this.setHist(name, data[2]);
if (filteredData.length > 0) {
// Set the 'line_m' for the MACD line
this.setLine('line_m', filteredData.map(row => ({
time: row.time,
value: row.macd
})), 'macd');
// Set the 'line_s' for the signal line
this.setLine('line_s', filteredData.map(row => ({
time: row.time,
value: row.signal
})), 'signal');
// Set the histogram
this.setHist('hist', filteredData.map(row => ({
time: row.time,
value: row.hist
})));
} else {
console.error('No valid MACD data found.');
}
} }
update(data) { update(data) {
this.updateLine('line_m', data[0][0], 'macd'); // Update the 'macd' line
this.updateLine('line_s', data[1][0], 'signal'); this.updateLine('line_m', {time: data[0].time, value: data[0].macd }, 'macd');
this.updateHist(name, data[2][0]);
// Update the 'signal' line
this.updateLine('line_s', { time: data[0].time, value: data[0].signal }, 'signal');
// Update the 'hist' (histogram) bar
this.updateHist('hist', {time: data[0].time, value: data[0].hist });
} }
} }
indicatorMap.set("MACD", MACD); indicatorMap.set("MACD", MACD);
@ -265,9 +331,23 @@ class Bolenger extends Indicator {
} }
init(data) { init(data) {
this.setLine('line_u', data[0], 'value'); // Set the 'line_u' for the upper line
this.setLine('line_m', data[1], 'value2'); this.setLine('line_u', data.map(row => ({
this.setLine('line_l', data[2], 'value3'); time: row.time,
value: row.upper
})), 'value');
// Set the 'line_m' for the middle line
this.setLine('line_m', data.map(row => ({
time: row.time,
value: row.middle
})), 'value2');
// Set the 'line_l' for the lower line
this.setLine('line_l', data.map(row => ({
time: row.time,
value: row.lower
})), 'value3');
} }
update(data) { update(data) {
@ -276,12 +356,13 @@ class Bolenger extends Indicator {
this.updateLine('line_l', data[2][0], 'value3'); this.updateLine('line_l', data[2][0], 'value3');
} }
} }
indicatorMap.set("Bolenger", Bolenger); indicatorMap.set("BOLBands", Bolenger);
class Indicators { class Indicators {
constructor() { constructor(comms) {
// Contains instantiated indicators. // Contains instantiated indicators.
this.i_objs = {}; this.i_objs = {};
this.comms = comms;
} }
create_indicators(indicators, charts, bt_data) { create_indicators(indicators, charts, bt_data) {
@ -333,12 +414,68 @@ class Indicators {
} }
} }
// This updates all the indicator data
update(updates){ update(updates){
for (name in updates){ for (name in updates){
window.UI.indicators.i_objs[name].update(updates[name]); window.UI.indicators.i_objs[name].update(updates[name]);
} }
} }
deleteIndicator(indicator, event) {
this.comms.deleteIndicator(indicator).then(response => {
if (response.success) {
const indicatorElement = event.target.closest('.indicator-row');
indicatorElement.remove(); // Remove from DOM
// Remove the indicator from the chart and legend
if (this.i_objs[indicator]) {
let chart;
// Determine which chart the indicator is on, based on its type
if (indicator.includes('RSI')) {
chart = window.UI.charts.chart2; // Assume RSI is on chart2
} else if (indicator.includes('MACD')) {
chart = window.UI.charts.chart3; // Assume MACD is on chart3
} else {
chart = window.UI.charts.chart_1; // Default to the main chart
}
// Pass the correct chart object when removing the indicator
this.i_objs[indicator].removeFromChart(chart);
// Remove the indicator object from i_objs
delete this.i_objs[indicator];
// Optionally: Clear the legend entry
iOutput.clear_legend(indicator);
}
} else {
alert('Failed to delete the indicator.');
}
});
}
// This updates a specific indicator
updateIndicator(event) {
const row = event.target.closest('.indicator-row');
const inputs = row.querySelectorAll('input, select');
// Gather input data
const formObj = {};
inputs.forEach(input => {
formObj[input.name] = input.type === 'checkbox' ? input.checked : input.value;
});
this.comms.updateIndicator(formObj).then(response => {
if (response.success) {
alert('Indicator updated successfully.');
} else {
alert('Failed to update the indicator.');
}
});
}
add_to_list(){ add_to_list(){
// Adds user input to a list and displays it in a HTML element. // Adds user input to a list and displays it in a HTML element.
// Used in the Create indicator panel (!)Called from inline html. // Used in the Create indicator panel (!)Called from inline html.
@ -365,6 +502,7 @@ class Indicators {
}else{ }else{
document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', ',' + JSON.stringify(p)); } document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', ',' + JSON.stringify(p)); }
} }
// Call to display Create new signal dialog. // Call to display Create new signal dialog.
open_form() { open_form() {
// Show the form // Show the form

View File

@ -20,13 +20,13 @@
<div style="grid-column: 4 / span 8;"> <div style="grid-column: 4 / span 8;">
<h3>Properties</h3> <h3>Properties</h3>
</div> </div>
<!-- Edit Indicator Rows of individual forms to edit each indicator --!> <!-- Edit Indicator Rows without form -->
{% for indicator in indicator_list %} {% for indicator in indicator_list %}
<form action="/settings" method="post" 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 10; grid-template-columns: 75px 200px 200px repeat(7, 1fr);">
<input type="hidden" name="setting" value="edit_indicator" /> <input type="hidden" name="setting" value="edit_indicator" />
<div id="edit_indctr_controls" style="grid-column: 1;"> <div id="edit_indctr_controls" style="grid-column: 1;">
<button type="submit" name="delete" class="e_btn" value="{{indicator}}">&#10008;</button> <button type="button" class="e_btn" onclick="UI.indicators.deleteIndicator('{{indicator}}', event)">&#10008;</button>
<button type="submit" name="submit" style ="color:darkgreen;" class="e_btn" value="{{indicator}}">&#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> <div class="iename" style="grid-column: 2;">{{indicator}}</div>
@ -48,32 +48,21 @@
{% endfor %} {% endfor %}
</select> </select>
{% elif property=='color' or list1[0]=='color' %} {% elif property=='color' or list1[0]=='color' %}
<input class="ietextbox" type="color" id="{{indicator}}_{{property}}" <input class="ietextbox" type="color" id="{{indicator}}_{{property}}" value="{{indicator_list[indicator][property]}}" name="{{property}}">
value="{{indicator_list[indicator][property]}}"
name="{{property}}">
{% elif property=='period' %} {% elif property=='period' %}
<input class="ietextbox" type="number" id="{{indicator}}_{{property}}" <input class="ietextbox" type="number" id="{{indicator}}_{{property}}" value="{{indicator_list[indicator][property]}}" name="{{property}}">
value="{{indicator_list[indicator][property]}}"
name="{{property}}">
{% elif property=='visible' %} {% elif property=='visible' %}
<input class="ietextbox" type="checkbox" id="{{indicator}}_{{property}}" <input class="ietextbox" type="checkbox" id="{{indicator}}_{{property}}" value="{{indicator_list[indicator][property]}}" name="{{property}}" {% if indicator in checked %} checked {%endif%}>
value="{{indicator_list[indicator][property]}}"
name="{{property}}"
{% if indicator in checked %} checked{%endif%}>
{% elif property=='value' %} {% elif property=='value' %}
<input class="ie_value" type="number" id="{{indicator}}_{{property}}" <input class="ie_value" type="number" id="{{indicator}}_{{property}}" value="{{indicator_list[indicator][property]}}" name="{{property}}" readonly>
value="{{indicator_list[indicator][property]}}"
name="{{property}}" readonly>
{% else %} {% else %}
<input class="ietextbox" type="text" id="{{indicator}}_{{property}}" <input class="ietextbox" type="text" id="{{indicator}}_{{property}}" value="{{indicator_list[indicator][property]}}" name="{{property}}">
value="{{indicator_list[indicator][property]}}"
name="{{property}}">
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}
</form> </div>
{% endfor %} {% endfor %}
<!-- End of Rows of individual forms to edit each indicator --!> <!-- End of Rows for each indicator -->
</div> </div>
</div> </div>