Mostly working now
This commit is contained in:
parent
f1d0f2a4b1
commit
29f30cb358
|
|
@ -895,8 +895,14 @@ class DatabaseInteractions(SnapshotDataCache):
|
|||
if len(rows) > 1:
|
||||
raise ValueError(f"Multiple rows found for {filter_vals}. Please provide a more specific filter.")
|
||||
|
||||
# Modify the specified field
|
||||
if isinstance(new_data, str):
|
||||
# Ensure consistency when storing boolean data like 'visible'
|
||||
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
|
||||
else:
|
||||
updated_value = json.dumps(new_data) # Convert non-string data to JSON string if necessary
|
||||
|
|
|
|||
|
|
@ -309,7 +309,7 @@ class Indicators:
|
|||
: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.
|
||||
"""
|
||||
user_id = str(self.users.get_id(username))
|
||||
user_id = self.users.get_id(username)
|
||||
|
||||
if not user_id:
|
||||
raise ValueError(f"Invalid user_name: {username}")
|
||||
|
|
@ -317,7 +317,7 @@ class Indicators:
|
|||
# Fetch indicators based on visibility status
|
||||
if only_enabled:
|
||||
indicators_df = self.cache_manager.get_rows_from_datacache('indicators',
|
||||
[('creator', user_id), ('visible', str(1))])
|
||||
[('creator', user_id), ('visible', True)])
|
||||
else:
|
||||
indicators_df = self.cache_manager.get_rows_from_datacache('indicators', [('creator', user_id)])
|
||||
|
||||
|
|
@ -333,10 +333,15 @@ class Indicators:
|
|||
properties = row.get('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
|
||||
result[row['name']] = {
|
||||
'type': row['kind'],
|
||||
'visible': row['visible'],
|
||||
'visible': bool(row['visible']),
|
||||
'source': source,
|
||||
**properties # Merge in all properties from the properties field
|
||||
}
|
||||
|
||||
|
|
@ -344,24 +349,24 @@ class Indicators:
|
|||
|
||||
def toggle_indicators(self, user_id: int, indicator_names: list) -> None:
|
||||
"""
|
||||
Set the visibility of indicators for a user.
|
||||
Set the visibility of indicators for a user.
|
||||
|
||||
:param user_id: The id of the user.
|
||||
:param indicator_names: List of indicator names to set as visible.
|
||||
:return: None
|
||||
:param user_id: The id of the user.
|
||||
:param indicator_names: List of indicator names to set as visible.
|
||||
:return: None
|
||||
"""
|
||||
indicators = self.cache_manager.get_rows_from_datacache('indicators', [('creator', user_id)])
|
||||
# Validate inputs
|
||||
|
||||
if indicators.empty:
|
||||
return
|
||||
|
||||
# Set visibility for all indicators off
|
||||
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
|
||||
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):
|
||||
"""
|
||||
|
|
@ -374,27 +379,58 @@ class Indicators:
|
|||
raise ValueError("Indicator name is required for editing.")
|
||||
|
||||
# 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',
|
||||
[('name', indicator_name), ('creator', user_id)])
|
||||
|
||||
if indicator.empty:
|
||||
raise ValueError(f"Indicator '{indicator_name}' not found for user '{user_name}'.")
|
||||
|
||||
# Modify indicator.
|
||||
self.cache_manager.modify_datacache_item('indicators',
|
||||
[('creator', user_id), ('name', indicator_name)],
|
||||
field_name='properties', new_data=params.get('properties'),
|
||||
overwrite='name')
|
||||
# Compare existing_properties and new_properties as strings
|
||||
new_properties = params.get('properties')
|
||||
if new_properties is not None:
|
||||
new_properties_str = json.dumps(new_properties, sort_keys=True) # Convert new properties to a JSON string
|
||||
existing_properties_str = indicator['properties'].iloc[0] # Existing properties are already a JSON string
|
||||
|
||||
new_visible = params.get('visible')
|
||||
current_visible = indicator['visible'].iloc[0]
|
||||
# Compare the strings directly
|
||||
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:
|
||||
self.cache_manager.modify_datacache_item('indicators',
|
||||
[('creator', user_id), ('name', indicator_name)],
|
||||
field_name='visible', new_data=new_visible,
|
||||
overwrite='name')
|
||||
self.cache_manager.modify_datacache_item(
|
||||
'indicators',
|
||||
[('creator', user_id), ('name', indicator_name)],
|
||||
field_name='visible',
|
||||
new_data=new_visible,
|
||||
overwrite='name'
|
||||
)
|
||||
|
||||
def new_indicator(self, user_name: str, params) -> None:
|
||||
"""
|
||||
|
|
@ -404,26 +440,22 @@ class Indicators:
|
|||
:param params: The request parameters containing indicator information.
|
||||
:return: None
|
||||
"""
|
||||
indcr = params['newi_name']
|
||||
indtyp = params['newi_type']
|
||||
indcr = params['name']
|
||||
indtyp = params['type']
|
||||
|
||||
# Validate indicator name and type
|
||||
if not indcr:
|
||||
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.")
|
||||
|
||||
# Create a dictionary of properties from the values in request form.
|
||||
source = {
|
||||
'symbol': params['ei_symbol'],
|
||||
'timeframe': params['ei_timeframe'],
|
||||
'exchange_name': params['ei_exchange_name']
|
||||
}
|
||||
source = params['source']
|
||||
|
||||
# Validate properties (assuming properties is a dictionary)
|
||||
properties = {}
|
||||
if params['new_prop_obj']:
|
||||
properties = json.loads(params['new_prop_obj'])
|
||||
if params['properties']:
|
||||
properties = json.loads(params['properties'])
|
||||
|
||||
# Create indicator.
|
||||
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)
|
||||
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
|
||||
name = indicator.name
|
||||
|
|
@ -502,11 +534,11 @@ class Indicators:
|
|||
|
||||
# Extract fields from indicators['source'] and compare directly
|
||||
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('symbol')) == source_symbol)
|
||||
(indicators['source'].apply(lambda s: s.get('exchange')) == source_exchange) & \
|
||||
(indicators['source'].apply(lambda s: s.get('market')) == source_symbol)
|
||||
|
||||
# Filter the DataFrame using the mask
|
||||
filtered_indicators = indicators[mask]
|
||||
indicators = indicators[mask]
|
||||
|
||||
# If no indicators match the filtered source, return None.
|
||||
if indicators.empty:
|
||||
|
|
@ -515,7 +547,7 @@ class Indicators:
|
|||
# Process each indicator, convert DataFrame to JSON-serializable format, and collect the 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)
|
||||
|
||||
# Convert DataFrame to list of dictionaries if necessary
|
||||
|
|
@ -569,15 +601,22 @@ class Indicators:
|
|||
if kind not in self.indicator_registry:
|
||||
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.
|
||||
creator_id = self.users.get_id(creator)
|
||||
row_data = pd.DataFrame([{
|
||||
'creator': creator_id,
|
||||
'name': name,
|
||||
'kind': kind,
|
||||
'visible': visible,
|
||||
'source': source,
|
||||
'properties': properties
|
||||
'visible': bool(visible),
|
||||
'source': sanitized_source,
|
||||
'properties': sanitized_properties
|
||||
}])
|
||||
self.cache_manager.insert_df_into_datacache(df=row_data, cache_name="indicators", skip_cache=False)
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
* @param {string} messageType - The type of the message.
|
||||
|
|
|
|||
|
|
@ -460,51 +460,61 @@ class Indicators {
|
|||
}
|
||||
|
||||
// This updates a specific indicator
|
||||
updateIndicator(event) {
|
||||
const row = event.target.closest('.indicator-row');
|
||||
const inputs = row.querySelectorAll('input, select');
|
||||
updateIndicator(event) {
|
||||
const row = event.target.closest('.indicator-row');
|
||||
const inputs = row.querySelectorAll('input, select');
|
||||
|
||||
// Gather the indicator name from the row
|
||||
const nameDiv = row.querySelector('div:nth-child(2)'); // Second <div> contains the name
|
||||
const indicatorName = nameDiv.innerText.trim(); // Get the indicator name
|
||||
// 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
|
||||
// Initialize formObj with the name of the indicator
|
||||
const formObj = {
|
||||
name: indicatorName,
|
||||
visible: false, // Default value for visible (will be updated based on the checkbox input)
|
||||
properties: {}
|
||||
};
|
||||
// Initialize formObj with the name of the indicator
|
||||
const formObj = {
|
||||
name: indicatorName,
|
||||
visible: false, // Default value for visible (will be updated based on the checkbox input)
|
||||
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
|
||||
inputs.forEach(input => {
|
||||
if (input.name === 'visible') {
|
||||
// Handle the visible checkbox separately
|
||||
formObj.visible = input.checked;
|
||||
} else {
|
||||
// Add all other inputs (type, period, color) to the properties object
|
||||
formObj.properties[input.name] = input.type === 'checkbox' ? input.checked : input.value;
|
||||
}
|
||||
});
|
||||
// Iterate over each input (text, checkbox, select) and add its name and value to formObj
|
||||
inputs.forEach(input => {
|
||||
if (input.name === 'visible') {
|
||||
// Handle the visible checkbox separately
|
||||
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 {
|
||||
// Add all other inputs (type, period, color) to the properties object
|
||||
formObj.properties[input.name] = input.type === 'checkbox' ? input.checked : input.value;
|
||||
}
|
||||
});
|
||||
|
||||
// Call comms to send data to the server
|
||||
this.comms.updateIndicator(formObj).then(response => {
|
||||
if (response.success) {
|
||||
window.location.reload(); // This triggers a full page refresh
|
||||
} else {
|
||||
alert('Failed to update the indicator.');
|
||||
}
|
||||
});
|
||||
}
|
||||
// Call comms to send data to the server
|
||||
this.comms.updateIndicator(formObj).then(response => {
|
||||
if (response.success) {
|
||||
window.location.reload(); // This triggers a full page refresh
|
||||
} 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.
|
||||
let n = document.getElementById("new_prop_name").value;
|
||||
let v = document.getElementById("new_prop_value").value;
|
||||
// Collect the property name and value input by the user
|
||||
let n = document.getElementById("new_prop_name").value.trim();
|
||||
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
|
||||
if (n == 'color'){
|
||||
if (n === 'color'){
|
||||
// list of valid css colors
|
||||
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",
|
||||
|
|
@ -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"
|
||||
};
|
||||
// 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()];
|
||||
}
|
||||
}
|
||||
// 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)">✘</button>
|
||||
<span>${n}: ${v}</span>
|
||||
</div>`;
|
||||
|
||||
// Insert the new property into the property list
|
||||
document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', newPropHTML);
|
||||
|
||||
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)); }
|
||||
// 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.
|
||||
open_form() {
|
||||
// Show the form
|
||||
document.getElementById("new_ind_form").style.display = "grid";
|
||||
|
||||
// Prefill the form fields with the current chart data (if available)
|
||||
const marketField = document.querySelector('[name="ei_symbol"]');
|
||||
const timeframeField = document.querySelector('[name="ei_timeframe"]');
|
||||
const exchangeField = document.querySelector('[name="ei_exchange_name"]');
|
||||
const marketField = document.getElementById('ei_symbol');
|
||||
const timeframeField = document.getElementById('ei_timeframe');
|
||||
const exchangeField = document.getElementById('ei_exchange_name');
|
||||
|
||||
// Set default values if fields are empty
|
||||
if (!marketField.value) {
|
||||
|
|
@ -553,11 +587,11 @@ class Indicators {
|
|||
Used in the create indicator panel.*/
|
||||
|
||||
// Perform validation
|
||||
const name = document.querySelector('[name="newi_name"]').value;
|
||||
const type = document.querySelector('[name="newi_type"]').value;
|
||||
let market = document.querySelector('[name="ei_symbol"]').value;
|
||||
const timeframe = document.querySelector('[name="ei_timeframe"]').value;
|
||||
const exchange = document.querySelector('[name="ei_exchange_name"]').value;
|
||||
const name = document.getElementById('newi_name').value;
|
||||
const type = document.getElementById('newi_type').value;
|
||||
let market = document.getElementById('ei_symbol').value;
|
||||
const timeframe = document.getElementById('ei_timeframe').value;
|
||||
const exchange = document.getElementById('ei_exchange_name').value;
|
||||
|
||||
let errorMsg = '';
|
||||
|
||||
|
|
@ -568,8 +602,7 @@ class Indicators {
|
|||
errorMsg += 'Indicator type is required.\n';
|
||||
}
|
||||
if (!market) {
|
||||
market = window.UI.data.trading_pair;
|
||||
document.querySelector('[name="ei_symbol"]').value = market; // Set the form field
|
||||
errorMsg += 'Indicator market is required.\n';
|
||||
}
|
||||
if (!timeframe) {
|
||||
errorMsg += 'Timeframe is required.\n';
|
||||
|
|
@ -583,13 +616,24 @@ class Indicators {
|
|||
return; // Stop form submission if there are errors
|
||||
}
|
||||
|
||||
// If validation passes, proceed with form submission
|
||||
let pl = document.getElementById("new_prop_list").innerHTML;
|
||||
if (pl) {
|
||||
pl = '[' + pl + ']';
|
||||
}
|
||||
document.getElementById("new_prop_obj").value = pl;
|
||||
document.getElementById("new_i_form").submit();
|
||||
// Collect and update properties
|
||||
const propObj = {}
|
||||
|
||||
// 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.');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,55 +6,41 @@
|
|||
<div class="section" style="grid-column: 1;">
|
||||
<button class="btn" onclick="UI.indicators.open_form()">New Indicator</button>
|
||||
</div>
|
||||
|
||||
<!-- Button for displaying indicator options -->
|
||||
<div class="section" style="grid-column: 2;">
|
||||
<button class="btn">Indicator Options</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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;">
|
||||
<!-- 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 100px 150px 150px 75px 100px 100px auto; gap: 10px; padding-left: 10px;">
|
||||
<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="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">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">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;">
|
||||
<hr style="grid-column: 1 / span 9; width: 100%; height: 1px; border: 1px solid #ccc; margin: 5px 0;">
|
||||
</div>
|
||||
|
||||
<!-- Loop through each 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;">
|
||||
<button type="button" class="e_btn" onclick="UI.indicators.deleteIndicator('{{indicator}}', event)">✘</button>
|
||||
<button type="button" class="e_btn" style="color:darkgreen;" onclick="UI.indicators.updateIndicator(event)">✔</button>
|
||||
<button type="button" class="e_btn edit" onclick="UI.indicators.updateIndicator(event)">✔</button>
|
||||
</div>
|
||||
|
||||
<!-- Display the indicator name -->
|
||||
<!-- Indicator Name -->
|
||||
<div style="text-align: center;">{{indicator}}</div>
|
||||
|
||||
<!-- Fixed property: Type (Dropdown) -->
|
||||
<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) -->
|
||||
<!-- Fixed property: Value -->
|
||||
<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>
|
||||
|
|
@ -63,16 +49,49 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Fixed property: Visible (Checkbox) -->
|
||||
<!-- Fixed property: Type -->
|
||||
<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 %}>
|
||||
{% if 'type' in indicator_list[indicator] %}
|
||||
<span id="{{indicator}}_type">{{indicator_list[indicator]['type']}}</span>
|
||||
{% else %}
|
||||
<span>-</span>
|
||||
{% endif %}
|
||||
</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;">
|
||||
{% if 'period' in indicator_list[indicator] %}
|
||||
<input class="ietextbox" type="number" id="{{indicator}}_period" value="{{indicator_list[indicator]['period']}}" name="period">
|
||||
|
|
@ -81,7 +100,7 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Fixed property: Color (Color Picker) -->
|
||||
<!-- 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">
|
||||
|
|
@ -90,17 +109,15 @@
|
|||
{% endif %}
|
||||
</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;">
|
||||
{% 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;">
|
||||
<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>
|
||||
|
|
@ -111,29 +128,68 @@
|
|||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Styles -->
|
||||
<style>
|
||||
.section .column {
|
||||
padding: 10px;
|
||||
max-width: 200px; /* Set max-width for all columns */
|
||||
/* General shadow styling for all elements except checkboxes */
|
||||
.ietextbox, .e_btn, input[type="color"] {
|
||||
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.section h3 {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
/* Style for Edit and Remove buttons */
|
||||
.e_btn {
|
||||
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 {
|
||||
max-width: 75px; /* Buttons column */
|
||||
/* Green background for Edit button */
|
||||
.e_btn.edit {
|
||||
background-color: #4CAF50;
|
||||
}
|
||||
|
||||
.section .visible-column {
|
||||
max-width: 75px; /* Visible checkbox column */
|
||||
/* Hover effect for buttons */
|
||||
.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) {
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
|
|
@ -142,14 +198,42 @@
|
|||
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);
|
||||
/* Opposite colors for textboxes */
|
||||
.indicator-row:nth-child(even) .ietextbox {
|
||||
background-color: #F7E1C1;
|
||||
}
|
||||
|
||||
/* Drop shadow for header text */
|
||||
.header-text {
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
|
||||
.indicator-row:nth-child(odd) .ietextbox {
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
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>
|
||||
|
||||
<!-- 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>
|
||||
|
|
|
|||
|
|
@ -1,66 +1,81 @@
|
|||
<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;">
|
||||
<!-- 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 class="form-popup" id="new_ind_form" style="overflow: 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) -->
|
||||
<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)-->
|
||||
<h1 style="grid-column: 1 / span 2; grid-row: 1;">Create New Indicator</h1>
|
||||
<!-- Panel title (row 1/5)-->
|
||||
<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 --!>
|
||||
<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_type">Type</label>
|
||||
<select id="newi_type" name="newi_type">
|
||||
{% for i_type in indicator_types %}
|
||||
<option value="{{i_type}}">{{i_type}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<br><br>
|
||||
<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=''">✘</button>
|
||||
<!-- Create indicator div container -->
|
||||
<div style="grid-column: 1 / span 2; grid-row: 2 / span 3;">
|
||||
<label for="newi_name">Indicator Name</label><input type="text" id="newi_name" value="New Indicator">
|
||||
<label for="newi_type">Type</label>
|
||||
<select id="newi_type">
|
||||
{% for i_type in indicator_types %}
|
||||
<option value="{{i_type}}">{{i_type}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<br><br>
|
||||
<span>Properties: <span id="new_prop_list"></span></span>
|
||||
|
||||
<br><br>
|
||||
<div id="add_prop_container" name="add_prop_container">
|
||||
<label for "new_prop_name">Property Name</label>
|
||||
<input type="text" id="new_prop_name" name="new_prop_name" value="name">
|
||||
<br>
|
||||
<label for "new_prop_value">Property Value</label>
|
||||
<input type="text" id="new_prop_value" name="new_prop_value" value="value">
|
||||
<button name="add_prop" id="add_prop" type="button" value="add_prop" style="color:darkgreen;" onclick="UI.indicators.add_to_list()">✙</button>
|
||||
<label for "add_prop">Add Property</label>
|
||||
<br><br>
|
||||
<div id="add_prop_container">
|
||||
<label for="new_prop_name">Property Name</label>
|
||||
<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>
|
||||
<label for="new_prop_value">Property Value</label>
|
||||
<input type="text" id="new_prop_value" name="new_prop_value" value="value" onclick="this.value='';">
|
||||
<button id="add_prop" type="button" style="color:darkgreen;" onclick="UI.indicators.add_to_list()">✙</button>
|
||||
<label for="add_prop">Add Property</label>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
</div>
|
||||
<br>
|
||||
<!-- Trading pair selector -->
|
||||
<label for="ei_symbols" >Trading Pair</label>
|
||||
<input list="ei_symbols" name="ei_symbol" id="ei_symbol" style="width: 96px;">
|
||||
<datalist id="ei_symbols">
|
||||
{% for symbol in symbols %}
|
||||
<option>{{ symbol }}</option>
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
<!-- Time timeframe selector -->
|
||||
<select id="ei_timeframe" name="ei_timeframe">
|
||||
{% for time_frame in intervals %}
|
||||
<option {% if time_frame == interval_state %} selected="selected" {%endif%}>{{ time_frame }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<!-- Exchange selector -->
|
||||
<select id="ei_exchange_name" name="ei_exchange_name">
|
||||
{% for exchange in exchanges %}
|
||||
<option {% if exchange == selected_exchange %} selected="selected" {%endif%}>{{ exchange }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="hidden" id="new_prop_obj" name="new_prop_obj" value="">
|
||||
</div>
|
||||
<!-- Trading pair selector -->
|
||||
<label for="ei_symbols">Trading Pair</label>
|
||||
<input list="ei_symbols" id="ei_symbol" style="width: 96px;">
|
||||
<datalist id="ei_symbols">
|
||||
{% for symbol in symbols %}
|
||||
<option value="{{symbol}}"></option>
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
|
||||
<!-- Buttons (row 5/5)-->
|
||||
<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 submit" onclick="UI.indicators.submit_new_i()">Create Indicator</button>
|
||||
</div>
|
||||
<!-- Time timeframe selector -->
|
||||
<select id="ei_timeframe">
|
||||
{% for time_frame in intervals %}
|
||||
<option {% if time_frame == interval_state %} selected="selected" {% endif %}>{{ time_frame }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<!-- Exchange selector -->
|
||||
<select id="ei_exchange_name">
|
||||
{% for exchange in exchanges %}
|
||||
<option {% if exchange == selected_exchange %} selected="selected" {% endif %}>{{ exchange }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<input type="hidden" id="new_prop_obj" value="">
|
||||
</div>
|
||||
|
||||
<!-- Buttons (row 5/5) -->
|
||||
<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 submit" onclick="UI.indicators.submit_new_i()">Create Indicator</button>
|
||||
</div>
|
||||
|
||||
</div><!----End panel 1--------->
|
||||
</form>
|
||||
</div>
|
||||
<!-- End panel 1 -->
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue