Mostly working now

This commit is contained in:
Rob 2024-09-19 17:10:26 -03:00
parent f1d0f2a4b1
commit 29f30cb358
6 changed files with 425 additions and 215 deletions

View File

@ -895,8 +895,14 @@ class DatabaseInteractions(SnapshotDataCache):
if len(rows) > 1: if len(rows) > 1:
raise ValueError(f"Multiple rows found for {filter_vals}. Please provide a more specific filter.") raise ValueError(f"Multiple rows found for {filter_vals}. Please provide a more specific filter.")
# Modify the specified field # Ensure consistency when storing boolean data like 'visible'
if isinstance(new_data, str): if isinstance(new_data, bool):
# If storing in a system that supports booleans, you can store directly as boolean
updated_value = new_data # For cache systems that support booleans
# If your cache or database only supports strings, convert boolean to string
# updated_value = 'true' if new_data else 'false'
elif isinstance(new_data, str):
updated_value = new_data updated_value = new_data
else: else:
updated_value = json.dumps(new_data) # Convert non-string data to JSON string if necessary updated_value = json.dumps(new_data) # Convert non-string data to JSON string if necessary

View File

@ -309,7 +309,7 @@ class Indicators:
:param only_enabled: bool - If True, return only indicators marked as visible. :param only_enabled: bool - If True, return only indicators marked as visible.
:return: dict - A dictionary of indicator names as keys and their attributes as values. :return: dict - A dictionary of indicator names as keys and their attributes as values.
""" """
user_id = str(self.users.get_id(username)) user_id = self.users.get_id(username)
if not user_id: if not user_id:
raise ValueError(f"Invalid user_name: {username}") raise ValueError(f"Invalid user_name: {username}")
@ -317,7 +317,7 @@ class Indicators:
# Fetch indicators based on visibility status # Fetch indicators based on visibility status
if only_enabled: if only_enabled:
indicators_df = self.cache_manager.get_rows_from_datacache('indicators', indicators_df = self.cache_manager.get_rows_from_datacache('indicators',
[('creator', user_id), ('visible', str(1))]) [('creator', user_id), ('visible', True)])
else: else:
indicators_df = self.cache_manager.get_rows_from_datacache('indicators', [('creator', user_id)]) indicators_df = self.cache_manager.get_rows_from_datacache('indicators', [('creator', user_id)])
@ -333,10 +333,15 @@ class Indicators:
properties = row.get('properties', {}) properties = row.get('properties', {})
properties = json.loads(properties) if isinstance(properties, str) else properties properties = json.loads(properties) if isinstance(properties, str) else properties
# Deserialize the 'source' field if it is a JSON string
source = row.get('source', {})
source = json.loads(source) if isinstance(source, str) else source
# Construct the result dictionary for each indicator # Construct the result dictionary for each indicator
result[row['name']] = { result[row['name']] = {
'type': row['kind'], 'type': row['kind'],
'visible': row['visible'], 'visible': bool(row['visible']),
'source': source,
**properties # Merge in all properties from the properties field **properties # Merge in all properties from the properties field
} }
@ -351,17 +356,17 @@ class Indicators:
:return: None :return: None
""" """
indicators = self.cache_manager.get_rows_from_datacache('indicators', [('creator', user_id)]) indicators = self.cache_manager.get_rows_from_datacache('indicators', [('creator', user_id)])
# Validate inputs
if indicators.empty: if indicators.empty:
return return
# Set visibility for all indicators off # Set visibility for all indicators off
self.cache_manager.modify_datacache_item('indicators', [('creator', user_id)], self.cache_manager.modify_datacache_item('indicators', [('creator', user_id)],
field_name='visible', new_data=0, overwrite='name') field_name='visible', new_data=False, overwrite='name')
# Set visibility for the specified indicators on # Set visibility for the specified indicators on
self.cache_manager.modify_datacache_item('indicators', [('creator', user_id), ('name', indicator_names)], self.cache_manager.modify_datacache_item('indicators', [('creator', user_id), ('name', indicator_names)],
field_name='visible', new_data=1, overwrite='name') field_name='visible', new_data=True, overwrite='name')
def edit_indicator(self, user_name: str, params: dict): def edit_indicator(self, user_name: str, params: dict):
""" """
@ -374,27 +379,58 @@ class Indicators:
raise ValueError("Indicator name is required for editing.") raise ValueError("Indicator name is required for editing.")
# Get the indicator from the user's indicator list # Get the indicator from the user's indicator list
user_id = str(self.users.get_id(user_name)) user_id = self.users.get_id(user_name)
indicator = self.cache_manager.get_rows_from_datacache('indicators', indicator = self.cache_manager.get_rows_from_datacache('indicators',
[('name', indicator_name), ('creator', user_id)]) [('name', indicator_name), ('creator', user_id)])
if indicator.empty: if indicator.empty:
raise ValueError(f"Indicator '{indicator_name}' not found for user '{user_name}'.") raise ValueError(f"Indicator '{indicator_name}' not found for user '{user_name}'.")
# Modify indicator. # Compare existing_properties and new_properties as strings
self.cache_manager.modify_datacache_item('indicators', new_properties = params.get('properties')
[('creator', user_id), ('name', indicator_name)], if new_properties is not None:
field_name='properties', new_data=params.get('properties'), new_properties_str = json.dumps(new_properties, sort_keys=True) # Convert new properties to a JSON string
overwrite='name') existing_properties_str = indicator['properties'].iloc[0] # Existing properties are already a JSON string
new_visible = params.get('visible') # Compare the strings directly
current_visible = indicator['visible'].iloc[0] if existing_properties_str != new_properties_str:
self.cache_manager.modify_datacache_item(
'indicators',
[('creator', user_id), ('name', indicator_name)],
field_name='properties',
new_data=new_properties,
overwrite='name'
)
# Compare existing_source and new_source as strings
new_source = params.get('source')
if new_source is not None:
new_source_str = json.dumps(new_source, sort_keys=True) # Convert new source to a JSON string
existing_source_str = indicator['source'].iloc[
0] if 'source' in indicator else None # Existing source as JSON string
# Compare the strings directly
if existing_source_str != new_source_str and new_source_str is not None:
self.cache_manager.modify_datacache_item(
'indicators',
[('creator', user_id), ('name', indicator_name)],
field_name='source',
new_data=new_source,
overwrite='name'
)
# Convert current_visible to boolean and check if it has changed
current_visible = str(indicator['visible'].iloc[0]).lower() in ['true', '1', 't', 'yes']
new_visible = bool(params.get('visible'))
if current_visible != new_visible: if current_visible != new_visible:
self.cache_manager.modify_datacache_item('indicators', self.cache_manager.modify_datacache_item(
'indicators',
[('creator', user_id), ('name', indicator_name)], [('creator', user_id), ('name', indicator_name)],
field_name='visible', new_data=new_visible, field_name='visible',
overwrite='name') new_data=new_visible,
overwrite='name'
)
def new_indicator(self, user_name: str, params) -> None: def new_indicator(self, user_name: str, params) -> None:
""" """
@ -404,26 +440,22 @@ class Indicators:
:param params: The request parameters containing indicator information. :param params: The request parameters containing indicator information.
:return: None :return: None
""" """
indcr = params['newi_name'] indcr = params['name']
indtyp = params['newi_type'] indtyp = params['type']
# Validate indicator name and type # Validate indicator name and type
if not indcr: if not indcr:
raise ValueError("Indicator name is required.") raise ValueError("Indicator name is required.")
if indtyp not in ['SMA', 'EMA', 'RSI', 'LREG', 'ATR', 'BOLBands', 'MACD', 'Volume']: if indtyp not in indicators_registry:
raise ValueError("Unsupported indicator type.") raise ValueError("Unsupported indicator type.")
# Create a dictionary of properties from the values in request form. # Create a dictionary of properties from the values in request form.
source = { source = params['source']
'symbol': params['ei_symbol'],
'timeframe': params['ei_timeframe'],
'exchange_name': params['ei_exchange_name']
}
# Validate properties (assuming properties is a dictionary) # Validate properties (assuming properties is a dictionary)
properties = {} properties = {}
if params['new_prop_obj']: if params['properties']:
properties = json.loads(params['new_prop_obj']) properties = json.loads(params['properties'])
# Create indicator. # Create indicator.
self.create_indicator(creator=user_name, name=indcr, kind=indtyp, source=source, properties=properties) self.create_indicator(creator=user_name, name=indcr, kind=indtyp, source=source, properties=properties)
@ -438,7 +470,7 @@ class Indicators:
""" """
username = self.users.get_username(indicator.creator) username = self.users.get_username(indicator.creator)
src = indicator.source src = indicator.source
symbol, timeframe, exchange_name = src['symbol'], src['timeframe'], src['exchange_name'] symbol, timeframe, exchange_name = src['market'], src['timeframe'], src['exchange']
# Retrieve necessary details to instantiate the indicator # Retrieve necessary details to instantiate the indicator
name = indicator.name name = indicator.name
@ -502,11 +534,11 @@ class Indicators:
# Extract fields from indicators['source'] and compare directly # Extract fields from indicators['source'] and compare directly
mask = (indicators['source'].apply(lambda s: s.get('timeframe')) == source_timeframe) & \ mask = (indicators['source'].apply(lambda s: s.get('timeframe')) == source_timeframe) & \
(indicators['source'].apply(lambda s: s.get('exchange_name')) == source_exchange) & \ (indicators['source'].apply(lambda s: s.get('exchange')) == source_exchange) & \
(indicators['source'].apply(lambda s: s.get('symbol')) == source_symbol) (indicators['source'].apply(lambda s: s.get('market')) == source_symbol)
# Filter the DataFrame using the mask # Filter the DataFrame using the mask
filtered_indicators = indicators[mask] indicators = indicators[mask]
# If no indicators match the filtered source, return None. # If no indicators match the filtered source, return None.
if indicators.empty: if indicators.empty:
@ -515,7 +547,7 @@ class Indicators:
# Process each indicator, convert DataFrame to JSON-serializable format, and collect the results # Process each indicator, convert DataFrame to JSON-serializable format, and collect the results
json_ready_results = {} json_ready_results = {}
for indicator in filtered_indicators.itertuples(index=False): for indicator in indicators.itertuples(index=False):
indicator_results = self.process_indicator(indicator=indicator, num_results=num_results) indicator_results = self.process_indicator(indicator=indicator, num_results=num_results)
# Convert DataFrame to list of dictionaries if necessary # Convert DataFrame to list of dictionaries if necessary
@ -569,15 +601,22 @@ class Indicators:
if kind not in self.indicator_registry: if kind not in self.indicator_registry:
raise ValueError(f"Requested an unsupported type of indicator: ({kind})") raise ValueError(f"Requested an unsupported type of indicator: ({kind})")
# Instantiate the indicator class from the registry
IndicatorClass = indicators_registry[kind]
indicator_instance = IndicatorClass(name=name, indicator_type=kind, properties=properties)
# Sanitize source and properties by converting them to JSON strings
sanitized_source = json.dumps(source) # Converts the dictionary to a JSON string
sanitized_properties = json.dumps(indicator_instance.properties) # Same for properties
# Add the new indicator to a pandas dataframe. # Add the new indicator to a pandas dataframe.
creator_id = self.users.get_id(creator)
row_data = pd.DataFrame([{ row_data = pd.DataFrame([{
'creator': creator_id, 'creator': creator_id,
'name': name, 'name': name,
'kind': kind, 'kind': kind,
'visible': visible, 'visible': bool(visible),
'source': source, 'source': sanitized_source,
'properties': properties 'properties': sanitized_properties
}]) }])
self.cache_manager.insert_df_into_datacache(df=row_data, cache_name="indicators", skip_cache=False) self.cache_manager.insert_df_into_datacache(df=row_data, cache_name="indicators", skip_cache=False)

View File

@ -147,6 +147,28 @@ class Comms {
} }
} }
/**
* 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 submitIndicator(indicatorData) {
try {
const response = await fetch('/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
setting: 'new_indicator',
indicator: 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

@ -460,7 +460,7 @@ class Indicators {
} }
// This updates a specific indicator // This updates a specific indicator
updateIndicator(event) { updateIndicator(event) {
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');
@ -468,12 +468,12 @@ class Indicators {
const nameDiv = row.querySelector('div:nth-child(2)'); // Second <div> contains the name const nameDiv = row.querySelector('div:nth-child(2)'); // Second <div> contains the name
const indicatorName = nameDiv.innerText.trim(); // Get the indicator name const indicatorName = nameDiv.innerText.trim(); // Get the indicator name
// Gather input data
// Initialize formObj with the name of the indicator // Initialize formObj with the name of the indicator
const formObj = { const formObj = {
name: indicatorName, name: indicatorName,
visible: false, // Default value for visible (will be updated based on the checkbox input) visible: false, // Default value for visible (will be updated based on the checkbox input)
properties: {} source: {}, // Initialize the source object directly at the top level
properties: {} // Initialize the properties object
}; };
// Iterate over each input (text, checkbox, select) and add its name and value to formObj // Iterate over each input (text, checkbox, select) and add its name and value to formObj
@ -481,6 +481,9 @@ class Indicators {
if (input.name === 'visible') { if (input.name === 'visible') {
// Handle the visible checkbox separately // Handle the visible checkbox separately
formObj.visible = input.checked; formObj.visible = input.checked;
} else if (input.name === 'market' || input.name === 'timeframe' || input.name === 'exchange') {
// Directly map inputs to source object fields
formObj.source[input.name] = input.value;
} else { } else {
// Add all other inputs (type, period, color) to the properties object // Add all other inputs (type, period, color) to the properties object
formObj.properties[input.name] = input.type === 'checkbox' ? input.checked : input.value; formObj.properties[input.name] = input.type === 'checkbox' ? input.checked : input.value;
@ -495,16 +498,23 @@ class Indicators {
alert('Failed to update the indicator.'); 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. // Collect the property name and value input by the user
let n = document.getElementById("new_prop_name").value; let n = document.getElementById("new_prop_name").value.trim();
let v = document.getElementById("new_prop_value").value; let v = document.getElementById("new_prop_value").value.trim();
// Ensure the property name and value are not empty
if (!n || !v) {
alert("Property name and value are required.");
return;
}
// Converts css color name to hex // Converts css color name to hex
if (n == 'color'){ if (n === 'color'){
// list of valid css colors // list of valid css colors
let colours = { let colours = {
"aliceblue":"#f0f8ff", "antiquewhite":"#faebd7", "aqua":"#00ffff", "aquamarine":"#7fffd4", "azure":"#f0ffff", "beige":"#f5f5dc", "bisque":"#ffe4c4", "black":"#000000", "blanchedalmond":"#ffebcd", "blue":"#0000ff", "blueviolet":"#8a2be2", "brown":"#a52a2a", "burlywood":"#deb887", "cadetblue":"#5f9ea0", "chartreuse":"#7fff00", "chocolate":"#d2691e", "coral":"#ff7f50", "cornflowerblue":"#6495ed", "cornsilk":"#fff8dc", "crimson":"#dc143c", "cyan":"#00ffff", "darkblue":"#00008b", "darkcyan":"#008b8b", "darkgoldenrod":"#b8860b", "darkgray":"#a9a9a9", "darkgreen":"#006400", "darkkhaki":"#bdb76b", "darkmagenta":"#8b008b", "darkolivegreen":"#556b2f", "darkorange":"#ff8c00", "darkorchid":"#9932cc", "darkred":"#8b0000", "darksalmon":"#e9967a", "darkseagreen":"#8fbc8f", "darkslateblue":"#483d8b", "darkslategray":"#2f4f4f", "darkturquoise":"#00ced1", "darkviolet":"#9400d3", "deeppink":"#ff1493", "deepskyblue":"#00bfff", "dimgray":"#696969", "dodgerblue":"#1e90ff", "firebrick":"#b22222", "floralwhite":"#fffaf0", "forestgreen":"#228b22", "fuchsia":"#ff00ff", "gainsboro":"#dcdcdc", "ghostwhite":"#f8f8ff", "gold":"#ffd700", "goldenrod":"#daa520", "gray":"#808080", "green":"#008000", "greenyellow":"#adff2f", "aliceblue":"#f0f8ff", "antiquewhite":"#faebd7", "aqua":"#00ffff", "aquamarine":"#7fffd4", "azure":"#f0ffff", "beige":"#f5f5dc", "bisque":"#ffe4c4", "black":"#000000", "blanchedalmond":"#ffebcd", "blue":"#0000ff", "blueviolet":"#8a2be2", "brown":"#a52a2a", "burlywood":"#deb887", "cadetblue":"#5f9ea0", "chartreuse":"#7fff00", "chocolate":"#d2691e", "coral":"#ff7f50", "cornflowerblue":"#6495ed", "cornsilk":"#fff8dc", "crimson":"#dc143c", "cyan":"#00ffff", "darkblue":"#00008b", "darkcyan":"#008b8b", "darkgoldenrod":"#b8860b", "darkgray":"#a9a9a9", "darkgreen":"#006400", "darkkhaki":"#bdb76b", "darkmagenta":"#8b008b", "darkolivegreen":"#556b2f", "darkorange":"#ff8c00", "darkorchid":"#9932cc", "darkred":"#8b0000", "darksalmon":"#e9967a", "darkseagreen":"#8fbc8f", "darkslateblue":"#483d8b", "darkslategray":"#2f4f4f", "darkturquoise":"#00ced1", "darkviolet":"#9400d3", "deeppink":"#ff1493", "deepskyblue":"#00bfff", "dimgray":"#696969", "dodgerblue":"#1e90ff", "firebrick":"#b22222", "floralwhite":"#fffaf0", "forestgreen":"#228b22", "fuchsia":"#ff00ff", "gainsboro":"#dcdcdc", "ghostwhite":"#f8f8ff", "gold":"#ffd700", "goldenrod":"#daa520", "gray":"#808080", "green":"#008000", "greenyellow":"#adff2f",
@ -512,28 +522,52 @@ class Indicators {
"palegreen":"#98fb98", "paleturquoise":"#afeeee", "palevioletred":"#d87093", "papayawhip":"#ffefd5", "peachpuff":"#ffdab9", "peru":"#cd853f", "pink":"#ffc0cb", "plum":"#dda0dd", "powderblue":"#b0e0e6", "purple":"#800080", "rebeccapurple":"#663399", "red":"#ff0000", "rosybrown":"#bc8f8f", "royalblue":"#4169e1", "saddlebrown":"#8b4513", "salmon":"#fa8072", "sandybrown":"#f4a460", "seagreen":"#2e8b57", "seashell":"#fff5ee", "sienna":"#a0522d", "silver":"#c0c0c0", "skyblue":"#87ceeb", "slateblue":"#6a5acd", "slategray":"#708090", "snow":"#fffafa", "springgreen":"#00ff7f", "steelblue":"#4682b4", "tan":"#d2b48c", "teal":"#008080", "thistle":"#d8bfd8", "tomato":"#ff6347", "turquoise":"#40e0d0", "violet":"#ee82ee", "wheat":"#f5deb3", "white":"#ffffff", "whitesmoke":"#f5f5f5", "yellow":"#ffff00", "yellowgreen":"#9acd32" "palegreen":"#98fb98", "paleturquoise":"#afeeee", "palevioletred":"#d87093", "papayawhip":"#ffefd5", "peachpuff":"#ffdab9", "peru":"#cd853f", "pink":"#ffc0cb", "plum":"#dda0dd", "powderblue":"#b0e0e6", "purple":"#800080", "rebeccapurple":"#663399", "red":"#ff0000", "rosybrown":"#bc8f8f", "royalblue":"#4169e1", "saddlebrown":"#8b4513", "salmon":"#fa8072", "sandybrown":"#f4a460", "seagreen":"#2e8b57", "seashell":"#fff5ee", "sienna":"#a0522d", "silver":"#c0c0c0", "skyblue":"#87ceeb", "slateblue":"#6a5acd", "slategray":"#708090", "snow":"#fffafa", "springgreen":"#00ff7f", "steelblue":"#4682b4", "tan":"#d2b48c", "teal":"#008080", "thistle":"#d8bfd8", "tomato":"#ff6347", "turquoise":"#40e0d0", "violet":"#ee82ee", "wheat":"#f5deb3", "white":"#ffffff", "whitesmoke":"#f5f5f5", "yellow":"#ffff00", "yellowgreen":"#9acd32"
}; };
// if the value is in the list of colors convert it. // if the value is in the list of colors convert it.
if (typeof colours[v.toLowerCase()] != 'undefined') if (v.toLowerCase() in colours) {
v = colours[v.toLowerCase()]; v = colours[v.toLowerCase()];
} }
let p={};
p[n] = v;
if (document.getElementById("new_prop_list").innerHTML ==""){
document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', JSON.stringify(p));
}else{
document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', ',' + JSON.stringify(p)); }
} }
// Create a new property row with a clear button
const newPropHTML = `
<div class="property-item" style="display:flex;align-items:center;">
<button style="color:darkred;margin-right:5px;" type="button" onclick="UI.indicators.remove_prop(this)">&#10008;</button>
<span>${n}: ${v}</span>
</div>`;
// Insert the new property into the property list
document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', newPropHTML);
// Update the hidden property object with the new property
const propObj = JSON.parse(document.getElementById("new_prop_obj").value || '{}');
propObj[n] = v;
document.getElementById("new_prop_obj").value = JSON.stringify(propObj);
// Clear the input fields
document.getElementById("new_prop_name").value = '';
document.getElementById("new_prop_value").value = '';
}
// Function to remove a property from the list
remove_prop(buttonElement) {
const propertyDiv = buttonElement.parentElement;
const propertyText = propertyDiv.querySelector('span').textContent;
const [propName] = propertyText.split(':');
// Remove the property div from the DOM
propertyDiv.remove();
// Remove the property from the hidden input object
const propObj = JSON.parse(document.getElementById("new_prop_obj").value || '{}');
delete propObj[propName.trim()];
document.getElementById("new_prop_obj").value = JSON.stringify(propObj);
}
// Call to display Create new signal dialog. // Call to display Create new signal dialog.
open_form() { open_form() {
// Show the form // Show the form
document.getElementById("new_ind_form").style.display = "grid"; document.getElementById("new_ind_form").style.display = "grid";
// Prefill the form fields with the current chart data (if available) // Prefill the form fields with the current chart data (if available)
const marketField = document.querySelector('[name="ei_symbol"]'); const marketField = document.getElementById('ei_symbol');
const timeframeField = document.querySelector('[name="ei_timeframe"]'); const timeframeField = document.getElementById('ei_timeframe');
const exchangeField = document.querySelector('[name="ei_exchange_name"]'); const exchangeField = document.getElementById('ei_exchange_name');
// Set default values if fields are empty // Set default values if fields are empty
if (!marketField.value) { if (!marketField.value) {
@ -553,11 +587,11 @@ class Indicators {
Used in the create indicator panel.*/ Used in the create indicator panel.*/
// Perform validation // Perform validation
const name = document.querySelector('[name="newi_name"]').value; const name = document.getElementById('newi_name').value;
const type = document.querySelector('[name="newi_type"]').value; const type = document.getElementById('newi_type').value;
let market = document.querySelector('[name="ei_symbol"]').value; let market = document.getElementById('ei_symbol').value;
const timeframe = document.querySelector('[name="ei_timeframe"]').value; const timeframe = document.getElementById('ei_timeframe').value;
const exchange = document.querySelector('[name="ei_exchange_name"]').value; const exchange = document.getElementById('ei_exchange_name').value;
let errorMsg = ''; let errorMsg = '';
@ -568,8 +602,7 @@ class Indicators {
errorMsg += 'Indicator type is required.\n'; errorMsg += 'Indicator type is required.\n';
} }
if (!market) { if (!market) {
market = window.UI.data.trading_pair; errorMsg += 'Indicator market is required.\n';
document.querySelector('[name="ei_symbol"]').value = market; // Set the form field
} }
if (!timeframe) { if (!timeframe) {
errorMsg += 'Timeframe is required.\n'; errorMsg += 'Timeframe is required.\n';
@ -583,13 +616,24 @@ class Indicators {
return; // Stop form submission if there are errors return; // Stop form submission if there are errors
} }
// If validation passes, proceed with form submission // Collect and update properties
let pl = document.getElementById("new_prop_list").innerHTML; const propObj = {}
if (pl) {
pl = '[' + pl + ']'; // Optionally add name, type, market, timeframe, and exchange to the properties if needed
propObj["name"] = name;
propObj["type"] = type;
propObj["source"] = {'market':market,'timeframe': timeframe,'exchange':exchange};
propObj["properties"] = document.getElementById("new_prop_obj").value;
// Call comms to send data to the server
this.comms.submitIndicator(propObj).then(response => {
if (response.success) {
window.location.reload(); // This triggers a full page refresh
} else {
alert('Failed to create a new Indicator.');
} }
document.getElementById("new_prop_obj").value = pl; });
document.getElementById("new_i_form").submit();
} }
} }

View File

@ -6,55 +6,41 @@
<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 --> <!-- 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> </div>
<!-- Section for displaying the list of indicators --> <!-- Indicator list display section -->
<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;"> <div class="section" style="margin-top: 15px; overflow-x: auto; display: grid; grid-auto-rows: minmax(80px, auto); grid-template-columns: 75px 200px 100px 150px 150px 75px 100px 100px auto; gap: 10px; padding-left: 10px;">
<!-- Background color change for this section --> <div style="background-color: #F7E1C1; grid-column: 1 / span 9; padding: 5px 0; display: grid; grid-template-columns: 75px 200px 100px 150px 150px 75px 100px 100px auto; gap: 10px;">
<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">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">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">Value</h3></div>
<div style="text-align: center;"><h3 class="header-text">Type</h3></div>
<div style="text-align: center;"><h3 class="header-text">Source</h3></div>
<div style="text-align: center;"><h3 class="header-text">Visible</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">Period</h3></div>
<div style="text-align: center;"><h3 class="header-text">Color</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> <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;"> <hr style="grid-column: 1 / span 9; width: 100%; height: 1px; border: 1px solid #ccc; margin: 5px 0;">
</div> </div>
<!-- Loop through each indicator in indicator_list --> <!-- Loop through each indicator in indicator_list -->
{% for indicator in indicator_list %} {% for indicator in indicator_list %}
<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;"> <div class="indicator-row" style="display: grid; grid-column: 1 / span 9; align-items: center; grid-template-columns: 75px 200px 100px 150px 150px 75px 100px 100px auto; gap: 10px; padding-left: 10px;">
<!-- Buttons to delete or update the indicator --> <!-- Edit and Remove buttons -->
<div style="text-align: center;"> <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 edit" onclick="UI.indicators.updateIndicator(event)">&#x2714;</button>
</div> </div>
<!-- Display the indicator name --> <!-- Indicator Name -->
<div style="text-align: center;">{{indicator}}</div> <div style="text-align: center;">{{indicator}}</div>
<!-- Fixed property: Type (Dropdown) --> <!-- Fixed property: Value -->
<div style="text-align: center;">
{% if 'type' in indicator_list[indicator] %}
<select class="ietextbox" id="{{indicator}}_type" name="type">
{% for i_type in indicator_types %}
<option value="{{i_type}}" {% if indicator_list[indicator]['type'] == i_type %} selected="selected"{% endif %}>{{i_type}}</option>
{% endfor %}
</select>
{% else %}
<span>-</span>
{% endif %}
</div>
<!-- Fixed property: Value (Readonly Number) -->
<div style="text-align: center;"> <div style="text-align: center;">
{% if 'value' in indicator_list[indicator] %} {% if 'value' in indicator_list[indicator] %}
<input class="ie_value" type="number" id="{{indicator}}_value" value="{{indicator_list[indicator]['value']}}" name="value" readonly> <input class="ie_value" type="number" id="{{indicator}}_value" value="{{indicator_list[indicator]['value']}}" name="value" readonly>
@ -63,16 +49,49 @@
{% endif %} {% endif %}
</div> </div>
<!-- Fixed property: Visible (Checkbox) --> <!-- Fixed property: Type -->
<div style="text-align: center;"> <div style="text-align: center;">
{% if 'visible' in indicator_list[indicator] %} {% if 'type' 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 %}> <span id="{{indicator}}_type">{{indicator_list[indicator]['type']}}</span>
{% else %} {% else %}
<span>-</span> <span>-</span>
{% endif %} {% endif %}
</div> </div>
<!-- Fixed property: Period (Number Input) --> <!-- Source fields for Symbol, Timeframe, Exchange -->
<div style="text-align: center;">
<input list="symbols" class="ietextbox" id="{{indicator}}_source_symbol" name="market" value="{{indicator_list[indicator]['source']['market']}}">
<datalist id="symbols">
{% for symbol in symbols %}
<option value="{{symbol}}"></option>
{% endfor %}
</datalist>
<input list="timeframes" class="ietextbox" id="{{indicator}}_source_timeframe" name="timeframe" value="{{indicator_list[indicator]['source']['timeframe']}}">
<datalist id="timeframes">
{% for timeframe in intervals %}
<option value="{{timeframe}}"></option>
{% endfor %}
</datalist>
<input list="exchanges" class="ietextbox" id="{{indicator}}_source_exchange_name" name="exchange" value="{{indicator_list[indicator]['source']['exchange']}}">
<datalist id="exchanges">
{% for exchange in exchanges %}
<option value="{{exchange}}"></option>
{% endfor %}
</datalist>
</div>
<!-- Checkbox for Visibility -->
<div style="text-align: center;">
{% if 'visible' in indicator_list[indicator] %}
<input type="checkbox" id="{{indicator}}_visible" value="{{indicator_list[indicator]['visible']}}" name="visible" {% if indicator in checked %} checked {% endif %}>
{% else %}
<span>-</span>
{% endif %}
</div>
<!-- Period field -->
<div style="text-align: center;"> <div style="text-align: center;">
{% if 'period' in indicator_list[indicator] %} {% if 'period' in indicator_list[indicator] %}
<input class="ietextbox" type="number" id="{{indicator}}_period" value="{{indicator_list[indicator]['period']}}" name="period"> <input class="ietextbox" type="number" id="{{indicator}}_period" value="{{indicator_list[indicator]['period']}}" name="period">
@ -81,7 +100,7 @@
{% endif %} {% endif %}
</div> </div>
<!-- Fixed property: Color (Color Picker) --> <!-- Color Picker -->
<div style="text-align: center;"> <div style="text-align: center;">
{% if 'color' in indicator_list[indicator] %} {% if 'color' in indicator_list[indicator] %}
<input class="ietextbox" type="color" id="{{indicator}}_color" value="{{indicator_list[indicator]['color']}}" name="color"> <input class="ietextbox" type="color" id="{{indicator}}_color" value="{{indicator_list[indicator]['color']}}" name="color">
@ -90,17 +109,15 @@
{% endif %} {% endif %}
</div> </div>
<!-- Dynamic properties (remaining ones not covered by known properties) --> <!-- Dynamic properties -->
<div style="grid-column: span 1; text-align: left; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;"> <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() %} {% for property, value in indicator_list[indicator].items() %}
{% if property not in ['type', 'value', 'color', 'period', 'visible'] %} {% if property not in ['type', 'value', 'color', 'period', 'visible', 'source'] %}
<div style="margin-bottom: 17px;"> <div style="margin-bottom: 17px;">
<label for="{{indicator}}_{{property}}" class="ietextbox-label">{{property}}</label> <label for="{{indicator}}_{{property}}" class="ietextbox-label">{{property}}</label>
{% if 'color' in property %} {% 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}}"> <input class="ietextbox" type="color" id="{{indicator}}_{{property}}" value="{{value}}" name="{{property}}">
{% else %} {% else %}
<!-- Regular text input for other properties -->
<input class="ietextbox" type="text" id="{{indicator}}_{{property}}" value="{{value}}" name="{{property}}"> <input class="ietextbox" type="text" id="{{indicator}}_{{property}}" value="{{value}}" name="{{property}}">
{% endif %} {% endif %}
</div> </div>
@ -111,29 +128,68 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<!-- Styles -->
<style> <style>
.section .column { /* General shadow styling for all elements except checkboxes */
padding: 10px; .ietextbox, .e_btn, input[type="color"] {
max-width: 200px; /* Set max-width for all columns */ box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
} }
.section h3 { /* Style for Edit and Remove buttons */
margin-bottom: 0; .e_btn {
margin-top: 0; display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
padding: 8px 12px;
background-color: #f44336; /* Red for remove */
color: white;
border: 1px solid black;
cursor: pointer;
} }
.section .buttons-column { /* Green background for Edit button */
max-width: 75px; /* Buttons column */ .e_btn.edit {
background-color: #4CAF50;
} }
.section .visible-column { /* Hover effect for buttons */
max-width: 75px; /* Visible checkbox column */ .e_btn:hover {
opacity: 0.9;
box-shadow: 0px 6px 8px rgba(0, 0, 0, 0.15);
} }
/* Alternate row background colors */ .e_btn:focus {
outline: none;
}
/* Color picker input */
input[type="color"] {
border: 1px solid black;
padding: 5px;
width: 60px;
height: 40px;
border-radius: 17px;
box-sizing: border-box;
}
/* Hover effect for color picker */
input[type="color"]:hover {
box-shadow: 0px 6px 8px rgba(0, 0, 0, 0.15);
}
/* Styling for input fields and textboxes */
.ietextbox {
width: 100%;
padding: 5px;
border-radius: 15px;
box-sizing: border-box;
}
/* Alternating row colors */
.indicator-row:nth-child(even) { .indicator-row:nth-child(even) {
background-color: #E0E0E0; background-color: #E0E0E0;
} }
@ -142,14 +198,42 @@
background-color: #F7E1C1; background-color: #F7E1C1;
} }
/* Bold labels with drop shadow for dynamic properties */ /* Opposite colors for textboxes */
.ietextbox-label { .indicator-row:nth-child(even) .ietextbox {
font-weight: bold; background-color: #F7E1C1;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
} }
/* Drop shadow for header text */ .indicator-row:nth-child(odd) .ietextbox {
.header-text { background-color: #E0E0E0;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
} }
input[type="checkbox"] {
width: 20px; /* Increase the size of the checkbox */
height: 20px; /* Adjust the height to match */
cursor: pointer; /* Add a pointer cursor for better UX */
margin: 0; /* Reset margin */
}
</style> </style>
<!-- JavaScript -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Handle focus and restore behavior for each dropdown
document.querySelectorAll('.ietextbox').forEach(function(input) {
let originalValue = input.value;
// Clear the input when focused
input.addEventListener('focus', function() {
originalValue = input.value;
input.value = ''; // Clear the input
});
// Restore the original value if no new selection is made
input.addEventListener('blur', function() {
if (!input.value) {
input.value = originalValue; // Restore the original value if input is empty
}
});
});
});
</script>

View File

@ -1,66 +1,81 @@
<div class="form-popup" id="new_ind_form" style="overflow: auto;"> <div class="form-popup" id="new_ind_form" style="overflow: auto;">
<form id="new_i_form" action="/settings" method="POST" class="form-container" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto;"> <div id="new_i_form" class="form-container" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto;">
<!-- Panel 1 of 1 (5 rows, 2 columns) --> <!-- Panel 1 of 1 (5 rows, 2 columns) -->
<div id="indicator_pan_1" class="form_panels" style="display: grid; grid-template-columns:repeat(2,1fr); grid-template-rows: repeat(5,1fr); gap: 10px;"> <div id="indicator_pan_1" class="form_panels" style="display: grid; grid-template-columns:repeat(2,1fr); grid-template-rows: repeat(5,1fr); gap: 10px;">
<!-- Panel title (row 1/5)--> <!-- Panel title (row 1/5)-->
<h1 style="grid-column: 1 / span 2; grid-row: 1;">Create New Indicator</h1> <h1 style="grid-column: 1 / span 2; grid-row: 1;">Create New Indicator</h1>
<input type="hidden" name="setting" value="new_indicator"/> <!-- Create indicator div container -->
<!-- Create indicator div container --!>
<div style="grid-column: 1 / span 2; grid-row: 2 / span 3;"> <div style="grid-column: 1 / span 2; grid-row: 2 / span 3;">
<label for "newi_name">Indicator Name</label><input type ="text" name="newi_name" value="New Indicator"> <label for="newi_name">Indicator Name</label><input type="text" id="newi_name" value="New Indicator">
<label for "newi_type">Type</label> <label for="newi_type">Type</label>
<select id="newi_type" name="newi_type"> <select id="newi_type">
{% for i_type in indicator_types %} {% for i_type in indicator_types %}
<option value="{{i_type}}">{{i_type}}</option> <option value="{{i_type}}">{{i_type}}</option>
{% endfor %} {% endfor %}
</select> </select>
<br><br> <br><br>
<span>Properties: <span id="new_prop_list"></span></span> <span>Properties: <span id="new_prop_list"></span></span>
<button name="add_prop_clear" id="add_prop_clear" style="color:darkred;" type="button" value="add_prop_clear" onclick="document.getElementById('new_prop_list').innerHTML=''">&#10008;</button>
<br><br> <br><br>
<div id="add_prop_container" name="add_prop_container"> <div id="add_prop_container">
<label for "new_prop_name">Property Name</label> <label for="new_prop_name">Property Name</label>
<input type="text" id="new_prop_name" name="new_prop_name" value="name"> <input type="text" id="new_prop_name" name="new_prop_name" list="default_properties" value="name" onclick="this.value='';">
<datalist id="default_properties">
<option value="period">
<option value="devup">
<option value="devdn">
<option value="ma">
<option value="fast_p">
<option value="slow_p">
<option value="signal_p">
<option value="color">
<option value="color_1">
<option value="color_2">
<option value="color_3">
</datalist>
<br> <br>
<label for "new_prop_value">Property Value</label> <label for="new_prop_value">Property Value</label>
<input type="text" id="new_prop_value" name="new_prop_value" value="value"> <input type="text" id="new_prop_value" name="new_prop_value" value="value" onclick="this.value='';">
<button name="add_prop" id="add_prop" type="button" value="add_prop" style="color:darkgreen;" onclick="UI.indicators.add_to_list()">&#10009;</button> <button id="add_prop" type="button" style="color:darkgreen;" onclick="UI.indicators.add_to_list()">&#10009;</button>
<label for "add_prop">Add Property</label> <label for="add_prop">Add Property</label>
</div> </div>
<br> <br>
<!-- Trading pair selector --> <!-- Trading pair selector -->
<label for="ei_symbols" >Trading Pair</label> <label for="ei_symbols">Trading Pair</label>
<input list="ei_symbols" name="ei_symbol" id="ei_symbol" style="width: 96px;"> <input list="ei_symbols" id="ei_symbol" style="width: 96px;">
<datalist id="ei_symbols"> <datalist id="ei_symbols">
{% for symbol in symbols %} {% for symbol in symbols %}
<option>{{ symbol }}</option> <option value="{{symbol}}"></option>
{% endfor %} {% endfor %}
</datalist> </datalist>
<!-- Time timeframe selector --> <!-- Time timeframe selector -->
<select id="ei_timeframe" name="ei_timeframe"> <select id="ei_timeframe">
{% for time_frame in intervals %} {% for time_frame in intervals %}
<option {% if time_frame == interval_state %} selected="selected" {%endif%}>{{ time_frame }}</option> <option {% if time_frame == interval_state %} selected="selected" {% endif %}>{{ time_frame }}</option>
{% endfor %} {% endfor %}
</select> </select>
<!-- Exchange selector --> <!-- Exchange selector -->
<select id="ei_exchange_name" name="ei_exchange_name"> <select id="ei_exchange_name">
{% for exchange in exchanges %} {% for exchange in exchanges %}
<option {% if exchange == selected_exchange %} selected="selected" {%endif%}>{{ exchange }}</option> <option {% if exchange == selected_exchange %} selected="selected" {% endif %}>{{ exchange }}</option>
{% endfor %} {% endfor %}
</select> </select>
<input type="hidden" id="new_prop_obj" name="new_prop_obj" value="">
<input type="hidden" id="new_prop_obj" value="">
</div> </div>
<!-- Buttons (row 5/5)--> <!-- Buttons (row 5/5) -->
<div style="grid-column: 1 / span 2; grid-row: 5; text-align: center;"> <div style="grid-column: 1 / span 2; grid-row: 5; text-align: center;">
<button type="button" class="btn cancel" onclick="UI.indicators.close_form()">Close</button> <button type="button" class="btn cancel" onclick="UI.indicators.close_form()">Close</button>
<button type="button" class="btn submit" onclick="UI.indicators.submit_new_i()">Create Indicator</button> <button type="button" class="btn submit" onclick="UI.indicators.submit_new_i()">Create Indicator</button>
</div> </div>
</div><!----End panel 1--------->
</form>
</div> </div>
<!-- End panel 1 -->
</div>
</div>