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':
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':
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.
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.
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.
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 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:
# 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('/')
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
@app.route('/api/history', methods=['POST', 'GET'])
def history():

View File

@ -151,6 +151,14 @@ class ATR(SMA):
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:
"""
Calculate the Bollinger Bands indicator for the given candles.
@ -177,11 +185,21 @@ class BolBands(Indicator):
'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
return df.iloc[self.properties['period']:]
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:
"""
Calculate the MACD indicator for the given candles.
@ -207,6 +225,9 @@ class MACD(Indicator):
'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
return df.iloc[self.properties['signal_p']:]
@ -368,11 +389,7 @@ class Indicators:
# attributes.update({'visible': False})
# # Set the data in indicators according to <attributes>
# brighter_trades.indicators.indicator_list[indicator] = attributes
#
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)
pass
def new_indicator(self, user_name: str, params) -> None:
"""
@ -505,10 +522,16 @@ class Indicators:
"""
if not indicator_name:
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
self.load_indicators(user_name=user_name)
# Get the user ID to filter the indicators belonging to the user
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,
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.
* @param {string} messageType - The type of the message.

View File

@ -1,11 +1,4 @@
//
//class Header {
// constructor() {
// this.height = height;
// }
//}
class User_Interface{
/* This contains all the code for our User interface.
The code is separated into classes that maintain and
@ -45,7 +38,7 @@ class User_Interface{
/* Indicators contains methods that interact with indicator
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. */
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>';
}
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();
@ -91,9 +100,9 @@ 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;
// 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;
}
setHist(name, data) {
@ -112,6 +121,37 @@ class Indicator {
updateHist(name, 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 {
@ -142,7 +182,7 @@ indicatorMap.set("SMA", SMA);
class Linear_Regression extends SMA {
// Inherits getIndicatorConfig from SMA
}
indicatorMap.set("Linear_Regression", Linear_Regression);
indicatorMap.set("LREG", Linear_Regression);
class EMA extends SMA {
// Inherits getIndicatorConfig from SMA
@ -197,15 +237,41 @@ class MACD extends Indicator {
}
init(data) {
this.setLine('line_m', data[0], 'macd');
this.setLine('line_s', data[1], 'signal');
this.setHist(name, data[2]);
// Filter out rows where macd, signal, or hist are null
const filteredData = data.filter(row => row.macd !== null && row.signal !== null && row.hist !== null);
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) {
this.updateLine('line_m', data[0][0], 'macd');
this.updateLine('line_s', data[1][0], 'signal');
this.updateHist(name, data[2][0]);
// Update the 'macd' line
this.updateLine('line_m', {time: data[0].time, value: data[0].macd }, 'macd');
// 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);
@ -265,9 +331,23 @@ class Bolenger extends Indicator {
}
init(data) {
this.setLine('line_u', data[0], 'value');
this.setLine('line_m', data[1], 'value2');
this.setLine('line_l', data[2], 'value3');
// Set the 'line_u' for the upper line
this.setLine('line_u', data.map(row => ({
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) {
@ -276,12 +356,13 @@ class Bolenger extends Indicator {
this.updateLine('line_l', data[2][0], 'value3');
}
}
indicatorMap.set("Bolenger", Bolenger);
indicatorMap.set("BOLBands", Bolenger);
class Indicators {
constructor() {
constructor(comms) {
// Contains instantiated indicators.
this.i_objs = {};
this.comms = comms;
}
create_indicators(indicators, charts, bt_data) {
@ -333,12 +414,68 @@ class Indicators {
}
}
// This updates all the indicator data
update(updates){
for (name in updates){
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(){
// Adds user input to a list and displays it in a HTML element.
// Used in the Create indicator panel (!)Called from inline html.
@ -365,6 +502,7 @@ class Indicators {
}else{
document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', ',' + JSON.stringify(p)); }
}
// Call to display Create new signal dialog.
open_form() {
// Show the form

View File

@ -20,13 +20,13 @@
<div style="grid-column: 4 / span 8;">
<h3>Properties</h3>
</div>
<!-- Edit Indicator Rows of individual forms to edit each indicator --!>
<!-- Edit Indicator Rows without form -->
{% 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" />
<div id="edit_indctr_controls" style="grid-column: 1;">
<button type="submit" name="delete" class="e_btn" value="{{indicator}}">&#10008;</button>
<button type="submit" name="submit" style ="color:darkgreen;" class="e_btn" value="{{indicator}}">&#x2714;</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>
</div>
<div class="iename" style="grid-column: 2;">{{indicator}}</div>
@ -48,32 +48,21 @@
{% endfor %}
</select>
{% elif property=='color' or list1[0]=='color' %}
<input class="ietextbox" type="color" id="{{indicator}}_{{property}}"
value="{{indicator_list[indicator][property]}}"
name="{{property}}">
<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}}">
<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%}>
<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>
<input class="ie_value" type="number" id="{{indicator}}_{{property}}" value="{{indicator_list[indicator][property]}}" name="{{property}}" readonly>
{% else %}
<input class="ietextbox" type="text" id="{{indicator}}_{{property}}"
value="{{indicator_list[indicator][property]}}"
name="{{property}}">
<input class="ietextbox" type="text" id="{{indicator}}_{{property}}" value="{{indicator_list[indicator][property]}}" name="{{property}}">
{% endif %}
</div>
{% endfor %}
</form>
</div>
{% endfor %}
<!-- End of Rows of individual forms to edit each indicator --!>
<!-- End of Rows for each indicator -->
</div>
</div>