Redesign indicator and backtest displays with icon cards
- Replace indicator table with icon card grid display - Add edit indicator popup dialog for modifying indicator settings - Display indicator type, value, and emoji symbols on cards - Add visibility toggle and delete buttons on hover - Redesign backtest items with CSS-based icons and status indicators - Unify dialog styling across external indicator, signal type, and public strategies - Make edit indicator dialog draggable - Fix button text: "+ Add Public" -> "Add Public" - Remove dark theme from signal_type_popup for consistency Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c9cc0487fe
commit
c8c51841cf
|
|
@ -787,55 +787,54 @@ class StratUIManager {
|
|||
existingModal.remove();
|
||||
}
|
||||
|
||||
// Create modal
|
||||
// Create modal using form-popup style (like other dialogs)
|
||||
const modal = document.createElement('div');
|
||||
modal.id = 'public-strategy-modal';
|
||||
modal.className = 'modal-overlay';
|
||||
modal.className = 'form-popup';
|
||||
modal.style.cssText = 'display: block; position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 500px; border-radius: 10px; z-index: 1000; overflow: hidden;';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content public-strategy-browser">
|
||||
<div class="modal-header">
|
||||
<h2>Public Strategies</h2>
|
||||
<button class="modal-close" onclick="this.closest('.modal-overlay').remove()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="public-strategies-list">
|
||||
${strategies.length === 0
|
||||
? '<p class="no-strategies">No public strategies available.</p>'
|
||||
: strategies.map(s => {
|
||||
const keyRaw = String(s.tbl_key || '');
|
||||
const keyHtml = escapeHtml(keyRaw);
|
||||
const keyJs = escapeHtml(escapeJsString(keyRaw));
|
||||
const nameHtml = escapeHtml(s.name || 'Unnamed Strategy');
|
||||
const creatorHtml = escapeHtml(s.creator_name || 'Unknown');
|
||||
const action = s.is_subscribed ? 'unsubscribeFromStrategy' : 'subscribeToStrategy';
|
||||
const label = s.is_subscribed ? 'Unsubscribe' : 'Subscribe';
|
||||
return `
|
||||
<div class="public-strategy-item ${s.is_subscribed ? 'subscribed' : ''}" data-tbl-key="${keyHtml}">
|
||||
<div class="strategy-info">
|
||||
<strong>${nameHtml}</strong>
|
||||
<span class="creator">by @${creatorHtml}</span>
|
||||
</div>
|
||||
<button class="subscribe-btn ${s.is_subscribed ? 'subscribed' : ''}"
|
||||
onclick="UI.strats.${action}('${keyJs}')">
|
||||
${label}
|
||||
</button>
|
||||
<div class="dialog-header" id="public_strategy_header">
|
||||
<h1>Public Strategies</h1>
|
||||
<button class="dialog-close-btn" onclick="document.getElementById('public-strategy-modal').remove()">×</button>
|
||||
</div>
|
||||
<div class="form-container" style="padding: 15px; max-height: 400px; overflow-y: auto;">
|
||||
<div class="public-strategies-list" style="display: flex; flex-direction: column; gap: 10px;">
|
||||
${strategies.length === 0
|
||||
? '<p class="no-strategies" style="text-align: center; color: #666; padding: 20px;">No public strategies available.</p>'
|
||||
: strategies.map(s => {
|
||||
const keyRaw = String(s.tbl_key || '');
|
||||
const keyHtml = escapeHtml(keyRaw);
|
||||
const keyJs = escapeHtml(escapeJsString(keyRaw));
|
||||
const nameHtml = escapeHtml(s.name || 'Unnamed Strategy');
|
||||
const creatorHtml = escapeHtml(s.creator_name || 'Unknown');
|
||||
const action = s.is_subscribed ? 'unsubscribeFromStrategy' : 'subscribeToStrategy';
|
||||
const label = s.is_subscribed ? 'Unsubscribe' : 'Subscribe';
|
||||
return `
|
||||
<div class="public-strategy-item ${s.is_subscribed ? 'subscribed' : ''}" data-tbl-key="${keyHtml}"
|
||||
style="display: flex; justify-content: space-between; align-items: center; padding: 10px; background: #f8f9fa; border-radius: 6px; border: 1px solid #dee2e6;">
|
||||
<div class="strategy-info">
|
||||
<strong>${nameHtml}</strong>
|
||||
<span class="creator" style="display: block; font-size: 11px; color: #666;">by @${creatorHtml}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join('')
|
||||
}
|
||||
</div>
|
||||
<button class="btn ${s.is_subscribed ? 'cancel' : 'submit'}"
|
||||
style="padding: 6px 12px; font-size: 12px;"
|
||||
onclick="UI.strats.${action}('${keyJs}')">
|
||||
${label}
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}).join('')
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Close on overlay click
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
modal.remove();
|
||||
}
|
||||
});
|
||||
// Make it draggable
|
||||
if (window.UI && window.UI.dragElement) {
|
||||
window.UI.dragElement(modal, 'public_strategy_header');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -470,15 +470,41 @@ class Backtesting {
|
|||
let html = '';
|
||||
for (const test of this.tests) {
|
||||
const statusClass = test.status || 'default'; // Use the status or fallback to 'default'
|
||||
const symbol = this.getBacktestSymbol(statusClass);
|
||||
const statusText = this.getStatusText(statusClass);
|
||||
html += `
|
||||
<div class="backtest-item ${statusClass}" onclick="UI.backtesting.openTestDialog('${test.name}')">
|
||||
<button class="delete-button" onclick="UI.backtesting.deleteTest('${test.name}'); event.stopPropagation();">✘</button>
|
||||
<div class="backtest-name">${test.name}</div>
|
||||
<button class="delete-button" onclick="UI.backtesting.deleteTest('${test.name}'); event.stopPropagation();">×</button>
|
||||
<div class="backtest-icon">
|
||||
<span class="backtest-symbol">${symbol}</span>
|
||||
</div>
|
||||
<div class="backtest-name" title="${test.name}">${test.name}</div>
|
||||
<div class="backtest-status">${statusText}</div>
|
||||
</div>`;
|
||||
}
|
||||
this.backtestDisplay.innerHTML = html;
|
||||
}
|
||||
|
||||
getBacktestSymbol(status) {
|
||||
const symbols = {
|
||||
'running': '⏳',
|
||||
'complete': '✅',
|
||||
'error': '❌',
|
||||
'default': '🧪'
|
||||
};
|
||||
return symbols[status] || symbols['default'];
|
||||
}
|
||||
|
||||
getStatusText(status) {
|
||||
const texts = {
|
||||
'running': 'Running',
|
||||
'complete': 'Complete',
|
||||
'error': 'Error',
|
||||
'default': 'Ready'
|
||||
};
|
||||
return texts[status] || texts['default'];
|
||||
}
|
||||
|
||||
openTestDialog(testName) {
|
||||
const test = this.tests.find(t => t.name === testName);
|
||||
if (!test) {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class User_Interface {
|
|||
this.initializeResizablePopup("new_sig_form", null, "signal_draggable_header", "resize-signal");
|
||||
this.initializeResizablePopup("new_signal_type_form", null, "signal_type_draggable_header", "resize-signal-type");
|
||||
this.initializeResizablePopup("external_indicator_form", null, "external_indicator_header", "resize-external-indicator");
|
||||
this.initializeResizablePopup("edit_indicator_form", null, "edit_indicator_header", null);
|
||||
this.initializeResizablePopup("new_trade_form", null, "trade_draggable_header", "resize-trade");
|
||||
this.initializeResizablePopup("ai_strategy_form", null, "ai_strategy_header", "resize-ai-strategy");
|
||||
|
||||
|
|
|
|||
|
|
@ -706,6 +706,10 @@ class Indicators {
|
|||
// Contains instantiated indicators.
|
||||
this.i_objs = {};
|
||||
this.comms = comms;
|
||||
// Store indicator configurations for editing
|
||||
this.indicators = {};
|
||||
// Store indicator data (values) for card display
|
||||
this.indicator_data = {};
|
||||
}
|
||||
|
||||
create_indicators(indicators, charts) {
|
||||
|
|
@ -766,6 +770,16 @@ class Indicators {
|
|||
// Always set up the visibility form event handler
|
||||
this._setupIndicatorForm();
|
||||
|
||||
// Store indicator configurations for card rendering and editing
|
||||
if (idata.indicators) {
|
||||
this.indicators = idata.indicators;
|
||||
}
|
||||
|
||||
// Store indicator data (values) for card display
|
||||
if (idata.indicator_data) {
|
||||
this.indicator_data = idata.indicator_data;
|
||||
}
|
||||
|
||||
if (idata.indicators && Object.keys(idata.indicators).length > 0) {
|
||||
this.create_indicators(idata.indicators, charts);
|
||||
// Initialize each indicator with the data directly
|
||||
|
|
@ -777,6 +791,9 @@ class Indicators {
|
|||
} else {
|
||||
console.log('No indicators defined for this user.');
|
||||
}
|
||||
|
||||
// Render indicator cards in the UI (after init so values are available)
|
||||
this.renderIndicators();
|
||||
}
|
||||
|
||||
_setupIndicatorForm(){
|
||||
|
|
@ -807,12 +824,39 @@ class Indicators {
|
|||
if (window.UI.indicators.i_objs[name]) {
|
||||
// Use init() to refresh with full data on candle close
|
||||
window.UI.indicators.i_objs[name].init(updates[name]);
|
||||
|
||||
// Update stored data and card display
|
||||
this.indicator_data[name] = updates[name];
|
||||
this._updateCardValue(name, updates[name]);
|
||||
} else {
|
||||
console.warn(`Indicator "${name}" not found in i_objs, skipping update`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a single indicator card's displayed value
|
||||
*/
|
||||
_updateCardValue(name, data) {
|
||||
const valueEl = document.getElementById(`indicator_card_value_${name}`);
|
||||
if (!valueEl) return;
|
||||
|
||||
let displayValue = '--';
|
||||
if (data && Array.isArray(data) && data.length > 0) {
|
||||
const lastPoint = data[data.length - 1];
|
||||
if (lastPoint.value !== undefined && lastPoint.value !== null) {
|
||||
displayValue = this._formatValue(lastPoint.value);
|
||||
} else if (lastPoint.macd !== undefined) {
|
||||
displayValue = this._formatValue(lastPoint.macd);
|
||||
} else if (lastPoint.middle !== undefined) {
|
||||
displayValue = this._formatValue(lastPoint.middle);
|
||||
} else if (lastPoint.upper !== undefined) {
|
||||
displayValue = this._formatValue(lastPoint.upper);
|
||||
}
|
||||
}
|
||||
valueEl.textContent = displayValue;
|
||||
}
|
||||
|
||||
deleteIndicator(indicator, event) {
|
||||
this.comms.deleteIndicator(indicator).then(response => {
|
||||
if (response.success) {
|
||||
|
|
@ -1110,4 +1154,326 @@ class Indicators {
|
|||
});
|
||||
}
|
||||
|
||||
// ==================== Card Display Methods ====================
|
||||
|
||||
/**
|
||||
* Renders all indicators as icon cards in the indicators_list container
|
||||
*/
|
||||
renderIndicators() {
|
||||
const container = document.getElementById('indicators_list');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
for (const [name, indicator] of Object.entries(this.indicators)) {
|
||||
const item = document.createElement('div');
|
||||
const isVisible = indicator.visible !== false;
|
||||
item.className = `indicator-item ${isVisible ? 'visible' : 'hidden'}`;
|
||||
item.setAttribute('data-indicator-name', name);
|
||||
|
||||
// Get indicator type symbol
|
||||
const symbol = this.getIndicatorSymbol(indicator.type);
|
||||
|
||||
// Get current value from indicator_data
|
||||
let displayValue = '--';
|
||||
const data = this.indicator_data[name];
|
||||
if (data && Array.isArray(data) && data.length > 0) {
|
||||
const lastPoint = data[data.length - 1];
|
||||
// Handle different indicator data formats
|
||||
if (lastPoint.value !== undefined && lastPoint.value !== null) {
|
||||
// Simple indicators (RSI, SMA, EMA, etc.)
|
||||
displayValue = this._formatValue(lastPoint.value);
|
||||
} else if (lastPoint.macd !== undefined) {
|
||||
// MACD - show the MACD value
|
||||
displayValue = this._formatValue(lastPoint.macd);
|
||||
} else if (lastPoint.middle !== undefined) {
|
||||
// Bollinger Bands - show middle value
|
||||
displayValue = this._formatValue(lastPoint.middle);
|
||||
} else if (lastPoint.upper !== undefined) {
|
||||
// Other band indicators
|
||||
displayValue = this._formatValue(lastPoint.upper);
|
||||
}
|
||||
}
|
||||
|
||||
const indicatorType = indicator.type || 'Unknown';
|
||||
item.innerHTML = `
|
||||
<button class="visibility-btn" onclick="UI.indicators.toggleVisibility('${name}'); event.stopPropagation();" title="${isVisible ? 'Hide' : 'Show'}">
|
||||
${isVisible ? '👁' : '👁🗨'}
|
||||
</button>
|
||||
<button class="delete-btn" onclick="UI.indicators.deleteIndicatorCard('${name}'); event.stopPropagation();" title="Delete">×</button>
|
||||
<div class="indicator-icon" title="${indicatorType}">
|
||||
<span class="indicator-symbol">${symbol}</span>
|
||||
</div>
|
||||
<div class="indicator-name" title="${name}">${name}</div>
|
||||
<div class="indicator-type">${indicatorType}</div>
|
||||
<div class="indicator-value" id="indicator_card_value_${name}">${displayValue}</div>
|
||||
`;
|
||||
|
||||
// Click to edit
|
||||
item.addEventListener('click', () => this.openEditDialog(name));
|
||||
|
||||
container.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a numeric value for display
|
||||
*/
|
||||
_formatValue(value) {
|
||||
if (value === null || value === undefined) return '--';
|
||||
if (typeof value === 'number') {
|
||||
// For large numbers, show fewer decimals
|
||||
if (Math.abs(value) >= 1000) {
|
||||
return value.toFixed(0);
|
||||
} else if (Math.abs(value) >= 100) {
|
||||
return value.toFixed(1);
|
||||
} else {
|
||||
return value.toFixed(2);
|
||||
}
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an emoji/symbol for the given indicator type
|
||||
*/
|
||||
getIndicatorSymbol(type) {
|
||||
// All keys are uppercase for consistent matching
|
||||
const symbols = {
|
||||
'RSI': '📊',
|
||||
'MACD': '📈',
|
||||
'SMA': '〰️',
|
||||
'EMA': '〰️',
|
||||
'LREG': '➡️',
|
||||
'BOLBANDS': '📉',
|
||||
'BOL%B': '📉',
|
||||
'ATR': '📏',
|
||||
'VOLUME': '📊',
|
||||
};
|
||||
|
||||
if (!type) return '📈';
|
||||
|
||||
const upperType = type.toUpperCase();
|
||||
|
||||
// Check for candlestick patterns (they start with CDL_)
|
||||
if (upperType.startsWith('CDL_')) {
|
||||
return '🕯️';
|
||||
}
|
||||
|
||||
return symbols[upperType] || '📈'; // default chart emoji
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the edit dialog for an indicator
|
||||
*/
|
||||
openEditDialog(indicatorName) {
|
||||
const indicator = this.indicators[indicatorName];
|
||||
if (!indicator) return;
|
||||
|
||||
const form = document.getElementById('edit_indicator_form');
|
||||
if (!form) {
|
||||
console.error('Edit indicator form not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate form fields
|
||||
document.getElementById('edit_indicator_name').value = indicatorName;
|
||||
document.getElementById('edit_ind_display_name').value = indicatorName;
|
||||
document.getElementById('edit_ind_type').value = indicator.type || 'Unknown';
|
||||
document.getElementById('edit_ind_visible').checked = indicator.visible !== false;
|
||||
|
||||
// Source fields
|
||||
const source = indicator.source || {};
|
||||
document.getElementById('edit_ind_market').value = source.market || '';
|
||||
document.getElementById('edit_ind_timeframe').value = source.timeframe || '';
|
||||
document.getElementById('edit_ind_exchange').value = source.exchange || '';
|
||||
|
||||
// Color
|
||||
document.getElementById('edit_ind_color').value = indicator.color || '#667eea';
|
||||
|
||||
// Render dynamic properties
|
||||
const propsContainer = document.getElementById('edit_ind_properties');
|
||||
propsContainer.innerHTML = '';
|
||||
|
||||
// Properties to exclude from dynamic rendering
|
||||
const excludeProps = ['type', 'value', 'color', 'visible', 'source', 'name',
|
||||
'color_1', 'color_2', 'color_3', 'bullish_color', 'bearish_color'];
|
||||
|
||||
for (const [key, value] of Object.entries(indicator)) {
|
||||
if (!excludeProps.includes(key)) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = `
|
||||
<label style="font-size: 12px;">${key}</label>
|
||||
<input type="text" name="${key}" value="${value}" style="width: 100%; margin-top: 3px;">
|
||||
`;
|
||||
propsContainer.appendChild(div);
|
||||
}
|
||||
}
|
||||
|
||||
// Show and position the form
|
||||
form.style.display = 'block';
|
||||
form.style.left = '50%';
|
||||
form.style.top = '50%';
|
||||
form.style.transform = 'translate(-50%, -50%)';
|
||||
form.style.position = 'fixed';
|
||||
form.style.zIndex = '1000';
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the edit dialog
|
||||
*/
|
||||
closeEditDialog() {
|
||||
const form = document.getElementById('edit_indicator_form');
|
||||
if (form) {
|
||||
form.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves changes from the edit dialog
|
||||
*/
|
||||
saveEditDialog() {
|
||||
const indicatorName = document.getElementById('edit_indicator_name').value;
|
||||
|
||||
const formObj = {
|
||||
name: indicatorName,
|
||||
visible: document.getElementById('edit_ind_visible').checked,
|
||||
color: document.getElementById('edit_ind_color').value,
|
||||
source: {
|
||||
market: document.getElementById('edit_ind_market').value,
|
||||
timeframe: document.getElementById('edit_ind_timeframe').value,
|
||||
exchange: document.getElementById('edit_ind_exchange').value
|
||||
},
|
||||
properties: {}
|
||||
};
|
||||
|
||||
// Gather dynamic properties
|
||||
const propsContainer = document.getElementById('edit_ind_properties');
|
||||
propsContainer.querySelectorAll('input').forEach(input => {
|
||||
const value = parseFloat(input.value);
|
||||
formObj.properties[input.name] = isNaN(value) ? input.value : value;
|
||||
});
|
||||
|
||||
this.comms.updateIndicator(formObj).then(response => {
|
||||
if (response.success) {
|
||||
this.closeEditDialog();
|
||||
// Refresh page to reload indicators with new settings
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('Failed to update indicator: ' + (response.message || 'Unknown error'));
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Error updating indicator:', error);
|
||||
alert('An error occurred while updating the indicator.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an indicator card with confirmation
|
||||
*/
|
||||
deleteIndicatorCard(indicatorName) {
|
||||
if (!confirm(`Delete indicator "${indicatorName}"?`)) return;
|
||||
|
||||
this.comms.deleteIndicator(indicatorName).then(response => {
|
||||
if (response.success) {
|
||||
// Remove from chart
|
||||
if (this.i_objs[indicatorName]) {
|
||||
// Determine which chart the indicator is on
|
||||
let chart;
|
||||
const indicator = this.i_objs[indicatorName];
|
||||
const indicatorType = indicator.constructor.name;
|
||||
|
||||
if (indicatorName.includes('RSI') || indicatorType === 'RSI') {
|
||||
chart = window.UI.charts.chart2;
|
||||
} else if (indicatorName.includes('MACD') || indicatorType === 'MACD') {
|
||||
chart = window.UI.charts.chart3;
|
||||
} else if (indicatorName.includes('%B') || indicatorType === 'BollingerPercentB') {
|
||||
chart = window.UI.charts.chart4;
|
||||
} else if (indicatorType === 'CandlestickPattern') {
|
||||
chart = window.UI.charts.chart5;
|
||||
} else {
|
||||
chart = window.UI.charts.chart_1;
|
||||
}
|
||||
|
||||
indicator.removeFromChart(chart);
|
||||
delete this.i_objs[indicatorName];
|
||||
iOutput.clear_legend(indicatorName);
|
||||
}
|
||||
|
||||
// Remove from indicators object
|
||||
delete this.indicators[indicatorName];
|
||||
|
||||
// Re-render the cards
|
||||
this.renderIndicators();
|
||||
} else {
|
||||
alert('Failed to delete indicator: ' + (response.message || 'Unknown error'));
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Error deleting indicator:', error);
|
||||
alert('An error occurred while deleting the indicator.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles visibility of an indicator on the chart
|
||||
*/
|
||||
toggleVisibility(indicatorName) {
|
||||
const indicator = this.indicators[indicatorName];
|
||||
if (!indicator) return;
|
||||
|
||||
// Toggle the visibility
|
||||
indicator.visible = !indicator.visible;
|
||||
|
||||
// Save to server and reload to apply chart changes
|
||||
this.comms.updateIndicator({
|
||||
name: indicatorName,
|
||||
visible: indicator.visible
|
||||
}).then(response => {
|
||||
if (response.success) {
|
||||
// Refresh page to properly update chart display
|
||||
window.location.reload();
|
||||
} else {
|
||||
// Revert the toggle if save failed
|
||||
indicator.visible = !indicator.visible;
|
||||
alert('Failed to update visibility');
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Error updating visibility:', error);
|
||||
indicator.visible = !indicator.visible;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a dialog draggable by its header
|
||||
*/
|
||||
_makeDialogDraggable(dialog, handle) {
|
||||
if (!dialog || !handle) return;
|
||||
|
||||
let offsetX = 0, offsetY = 0, isDragging = false;
|
||||
|
||||
handle.style.cursor = 'move';
|
||||
|
||||
handle.onmousedown = (e) => {
|
||||
if (e.target.tagName === 'BUTTON') return; // Don't drag when clicking buttons
|
||||
isDragging = true;
|
||||
offsetX = e.clientX - dialog.offsetLeft;
|
||||
offsetY = e.clientY - dialog.offsetTop;
|
||||
document.onmousemove = onMouseMove;
|
||||
document.onmouseup = onMouseUp;
|
||||
};
|
||||
|
||||
function onMouseMove(e) {
|
||||
if (!isDragging) return;
|
||||
dialog.style.left = (e.clientX - offsetX) + 'px';
|
||||
dialog.style.top = (e.clientY - offsetY) + 'px';
|
||||
dialog.style.transform = 'none';
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
isDragging = false;
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1500,7 +1500,13 @@ class Signals {
|
|||
const testResult = document.getElementById('ext_ind_test_result');
|
||||
if (testResult) testResult.style.display = 'none';
|
||||
|
||||
// Show and position the form
|
||||
form.style.display = 'block';
|
||||
form.style.left = '50%';
|
||||
form.style.top = '50%';
|
||||
form.style.transform = 'translate(-50%, -50%)';
|
||||
form.style.position = 'fixed';
|
||||
form.style.zIndex = '1000';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,64 +72,134 @@
|
|||
.backtest-item {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
height: 110px;
|
||||
text-align: center;
|
||||
transition: transform 0.3s ease;
|
||||
background-size: cover; /* Ensure the background image covers the entire div */
|
||||
background-position: center; /* Center the image */
|
||||
border: 1px solid #ccc; /* Optional: Add a border for better visibility */
|
||||
border-radius: 8px; /* Optional: Rounded corners */
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(145deg, #f0f0f0, #cacaca);
|
||||
box-shadow: 5px 5px 10px #bebebe, -5px -5px 10px #ffffff;
|
||||
cursor: pointer;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.backtest-item:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
/* Add a default background image */
|
||||
.backtest-item.default {
|
||||
background-image: url('/static/test_running_icon.webp');
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 8px 8px 15px #bebebe, -8px -8px 15px #ffffff;
|
||||
}
|
||||
|
||||
/* Add background for specific statuses if needed */
|
||||
/* Backtest icon area */
|
||||
.backtest-icon {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.backtest-icon::before {
|
||||
content: '';
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.backtest-icon .backtest-symbol {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
/* Status-based icon backgrounds */
|
||||
.backtest-item.default .backtest-icon::before,
|
||||
.backtest-item.running .backtest-icon::before {
|
||||
background: linear-gradient(135deg, #f7b733 0%, #fc4a1a 100%);
|
||||
}
|
||||
|
||||
.backtest-item.complete .backtest-icon::before {
|
||||
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||
}
|
||||
|
||||
.backtest-item.error .backtest-icon::before {
|
||||
background: linear-gradient(135deg, #cb2d3e 0%, #ef473a 100%);
|
||||
}
|
||||
|
||||
/* Status borders */
|
||||
.backtest-item.running {
|
||||
background-image: url('/static/test_running_icon.webp');
|
||||
background-color: #f3f981; /* Highlight running tests with a yellow background */
|
||||
border-color: #ffa500; /* Add an orange border for emphasis */
|
||||
border: 3px solid #ffa500;
|
||||
animation: pulse-orange 2s infinite;
|
||||
}
|
||||
|
||||
.backtest-item.complete {
|
||||
background-image: url('/static/test_complete_icon.webp');
|
||||
background-color: #d4edda; /* Green for completed tests */
|
||||
border-color: #28a745;
|
||||
border: 3px solid #28a745;
|
||||
}
|
||||
|
||||
.backtest-item.error {
|
||||
background-color: #f8d7da; /* Red for tests with errors */
|
||||
border-color: #dc3545;
|
||||
border: 3px solid #dc3545;
|
||||
}
|
||||
|
||||
@keyframes pulse-orange {
|
||||
0% { box-shadow: 0 0 0 0 rgba(255, 165, 0, 0.4); }
|
||||
70% { box-shadow: 0 0 0 10px rgba(255, 165, 0, 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(255, 165, 0, 0); }
|
||||
}
|
||||
|
||||
.backtest-name {
|
||||
position: absolute;
|
||||
top: 80px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: white;
|
||||
padding: 5px;
|
||||
border-radius: 10px;
|
||||
color: black;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
padding: 2px 5px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 94px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
.backtest-status {
|
||||
font-size: 9px;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.backtest-date {
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
.backtest-item .delete-button {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
background-color: red;
|
||||
color: white;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: 2px solid white;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.backtest-item:hover .delete-button {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.backtest-item .delete-button:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
#backtest-progress-container {
|
||||
display: none; /* Initially hidden */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
<!-- Edit Indicator Dialog -->
|
||||
<div class="form-popup" id="edit_indicator_form" style="display: none; overflow: hidden; position: absolute; width: 420px; border-radius: 10px;">
|
||||
|
||||
<!-- Draggable Header Section -->
|
||||
<div class="dialog-header" id="edit_indicator_header">
|
||||
<h1>Edit Indicator</h1>
|
||||
<button class="dialog-close-btn" onclick="UI.indicators.closeEditDialog()">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="form-container" style="padding: 15px; overflow-y: auto; max-height: 450px;">
|
||||
<input type="hidden" id="edit_indicator_name">
|
||||
|
||||
<!-- Indicator Name (readonly) -->
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label><b>Name:</b></label>
|
||||
<input type="text" id="edit_ind_display_name" readonly style="width: 100%; margin-top: 5px; background: #f5f5f5;">
|
||||
</div>
|
||||
|
||||
<!-- Indicator Type (readonly) -->
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label><b>Type:</b></label>
|
||||
<input type="text" id="edit_ind_type" readonly style="width: 100%; margin-top: 5px; background: #f5f5f5;">
|
||||
</div>
|
||||
|
||||
<!-- Visibility -->
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||||
<input type="checkbox" id="edit_ind_visible">
|
||||
<span>Show on Chart</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Source Settings -->
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label><b>Data Source:</b></label>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; margin-top: 5px;">
|
||||
<div>
|
||||
<label style="font-size: 12px;">Symbol</label>
|
||||
<input type="text" id="edit_ind_market" style="width: 100%; margin-top: 3px;">
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size: 12px;">Timeframe</label>
|
||||
<input type="text" id="edit_ind_timeframe" style="width: 100%; margin-top: 3px;">
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size: 12px;">Exchange</label>
|
||||
<input type="text" id="edit_ind_exchange" style="width: 100%; margin-top: 3px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic Properties -->
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label><b>Parameters:</b></label>
|
||||
<div id="edit_ind_properties" style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 5px;">
|
||||
<!-- Properties will be dynamically inserted here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Color Picker -->
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label><b>Color:</b></label>
|
||||
<input type="color" id="edit_ind_color" style="width: 60px; height: 30px; margin-left: 10px; vertical-align: middle;">
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div style="text-align: center; margin-top: 15px;">
|
||||
<button class="btn cancel" onclick="UI.indicators.closeEditDialog()">Cancel</button>
|
||||
<button class="btn submit" onclick="UI.indicators.saveEditDialog()">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,63 +1,87 @@
|
|||
<div class="form-popup" id="external_indicator_form" style="display: none; width: 500px;">
|
||||
<!-- External Indicator Dialog -->
|
||||
<div class="form-popup" id="external_indicator_form" style="display: none; overflow: hidden; position: absolute; width: 500px; border-radius: 10px;">
|
||||
|
||||
<!-- Draggable Header Section -->
|
||||
<div class="dialog-header" id="external_indicator_header">
|
||||
<h1>Add External Indicator</h1>
|
||||
<button class="dialog-close-btn" onclick="UI.signals.closeExternalIndicatorForm()">×</button>
|
||||
</div>
|
||||
|
||||
<form class="form-container" onsubmit="return false;">
|
||||
<!-- Main Content -->
|
||||
<div class="form-container" style="padding: 15px; overflow-y: auto; max-height: 500px;">
|
||||
<p style="color: #888; font-size: 12px; margin-bottom: 15px;">
|
||||
Configure an external API that provides historical data for backtesting.
|
||||
</p>
|
||||
|
||||
<!-- Name -->
|
||||
<label for="ext_ind_name"><b>Indicator Name:</b></label>
|
||||
<input type="text" id="ext_ind_name" placeholder="e.g., Fear & Greed Index" required>
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label><b>Indicator Name:</b></label>
|
||||
<input type="text" id="ext_ind_name" placeholder="e.g., Fear & Greed Index" required
|
||||
style="width: 100%; margin-top: 5px;">
|
||||
</div>
|
||||
|
||||
<!-- Historical URL -->
|
||||
<label for="ext_ind_url"><b>Historical Data URL:</b></label>
|
||||
<input type="text" id="ext_ind_url"
|
||||
placeholder="https://api.example.com/data?start={start_date}&limit={limit}" required>
|
||||
<p style="color: #666; font-size: 11px; margin: 5px 0 15px 0;">
|
||||
Use placeholders: <code>{start_date}</code>, <code>{end_date}</code>, <code>{limit}</code>
|
||||
</p>
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label><b>Historical Data URL:</b></label>
|
||||
<input type="text" id="ext_ind_url"
|
||||
placeholder="https://api.example.com/data?start={start_date}&limit={limit}" required
|
||||
style="width: 100%; margin-top: 5px;">
|
||||
<p style="color: #666; font-size: 11px; margin: 5px 0 0 0;">
|
||||
Use placeholders: <code>{start_date}</code>, <code>{end_date}</code>, <code>{limit}</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Auth Header -->
|
||||
<label for="ext_ind_auth_header"><b>Auth Header Name:</b> (optional)</label>
|
||||
<input type="text" id="ext_ind_auth_header" placeholder="e.g., X-CMC_PRO_API_KEY">
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label><b>Auth Header Name:</b> <span style="font-weight: normal; color: #888;">(optional)</span></label>
|
||||
<input type="text" id="ext_ind_auth_header" placeholder="e.g., X-CMC_PRO_API_KEY"
|
||||
style="width: 100%; margin-top: 5px;">
|
||||
</div>
|
||||
|
||||
<!-- API Key -->
|
||||
<label for="ext_ind_auth_key"><b>API Key:</b> (optional)</label>
|
||||
<input type="password" id="ext_ind_auth_key" placeholder="Your API key">
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label><b>API Key:</b> <span style="font-weight: normal; color: #888;">(optional)</span></label>
|
||||
<input type="password" id="ext_ind_auth_key" placeholder="Your API key"
|
||||
style="width: 100%; margin-top: 5px;">
|
||||
</div>
|
||||
|
||||
<!-- JSONPath for Values -->
|
||||
<label for="ext_ind_value_jsonpath"><b>Value JSONPath:</b></label>
|
||||
<input type="text" id="ext_ind_value_jsonpath" value="$.data[*].value" required>
|
||||
<p style="color: #666; font-size: 11px; margin: 5px 0 15px 0;">
|
||||
JSONPath expression to extract array of numeric values
|
||||
</p>
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label><b>Value JSONPath:</b></label>
|
||||
<input type="text" id="ext_ind_value_jsonpath" value="$.data[*].value" required
|
||||
style="width: 100%; margin-top: 5px;">
|
||||
<p style="color: #666; font-size: 11px; margin: 5px 0 0 0;">
|
||||
JSONPath expression to extract array of numeric values
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- JSONPath for Timestamps -->
|
||||
<label for="ext_ind_timestamp_jsonpath"><b>Timestamp JSONPath:</b></label>
|
||||
<input type="text" id="ext_ind_timestamp_jsonpath" value="$.data[*].timestamp" required>
|
||||
<p style="color: #666; font-size: 11px; margin: 5px 0 15px 0;">
|
||||
JSONPath expression to extract array of timestamps
|
||||
</p>
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label><b>Timestamp JSONPath:</b></label>
|
||||
<input type="text" id="ext_ind_timestamp_jsonpath" value="$.data[*].timestamp" required
|
||||
style="width: 100%; margin-top: 5px;">
|
||||
<p style="color: #666; font-size: 11px; margin: 5px 0 0 0;">
|
||||
JSONPath expression to extract array of timestamps
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Date Format in Response -->
|
||||
<label for="ext_ind_date_format"><b>Timestamp Format in Response:</b></label>
|
||||
<select id="ext_ind_date_format">
|
||||
<option value="ISO">ISO (2024-01-15 or 2024-01-15T12:00:00Z)</option>
|
||||
<option value="UNIX">Unix Timestamp (seconds or milliseconds)</option>
|
||||
</select>
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label><b>Timestamp Format in Response:</b></label>
|
||||
<select id="ext_ind_date_format" style="width: 100%; margin-top: 5px;">
|
||||
<option value="ISO">ISO (2024-01-15 or 2024-01-15T12:00:00Z)</option>
|
||||
<option value="UNIX">Unix Timestamp (seconds or milliseconds)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Date Format for URL Parameters -->
|
||||
<label for="ext_ind_date_param_format"><b>Date Format for URL Parameters:</b></label>
|
||||
<select id="ext_ind_date_param_format">
|
||||
<option value="ISO">ISO (2024-01-15)</option>
|
||||
<option value="UNIX">Unix Timestamp</option>
|
||||
</select>
|
||||
|
||||
<hr style="margin: 20px 0;">
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label><b>Date Format for URL Parameters:</b></label>
|
||||
<select id="ext_ind_date_param_format" style="width: 100%; margin-top: 5px;">
|
||||
<option value="ISO">ISO (2024-01-15)</option>
|
||||
<option value="UNIX">Unix Timestamp</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Test Result Area -->
|
||||
<div id="ext_ind_test_result" style="display: none; padding: 10px; border-radius: 5px; margin-bottom: 15px;">
|
||||
|
|
@ -69,92 +93,32 @@
|
|||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div style="display: flex; gap: 10px; justify-content: flex-end;">
|
||||
<div style="text-align: center; margin-top: 15px;">
|
||||
<button type="button" class="btn" onclick="UI.signals.testExternalIndicator()"
|
||||
style="background: #17a2b8;">Test Connection</button>
|
||||
<button type="button" class="btn" onclick="UI.signals.submitExternalIndicator()"
|
||||
style="background: #28a745;">Save Indicator</button>
|
||||
<button type="button" class="btn submit" onclick="UI.signals.submitExternalIndicator()">Save Indicator</button>
|
||||
<button type="button" class="btn cancel" onclick="UI.signals.closeExternalIndicatorForm()">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#external_indicator_form {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1000;
|
||||
background: #2a2a2a;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
#external_indicator_form .form-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#external_indicator_form label {
|
||||
color: #e0e0e0;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#external_indicator_form input[type="text"],
|
||||
#external_indicator_form input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
background: #1e1e1e;
|
||||
color: #e0e0e0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#external_indicator_form select {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
padding: 8px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
background: #1e1e1e;
|
||||
color: #e0e0e0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#external_indicator_form code {
|
||||
background: #1a1a1a;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
color: #17a2b8;
|
||||
}
|
||||
|
||||
#external_indicator_form .btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#external_indicator_form .btn.cancel {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#ext_ind_test_result.success {
|
||||
background: rgba(40, 167, 69, 0.2);
|
||||
background: rgba(40, 167, 69, 0.1);
|
||||
border: 1px solid #28a745;
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
#ext_ind_test_result.error {
|
||||
background: rgba(220, 53, 69, 0.2);
|
||||
background: rgba(220, 53, 69, 0.1);
|
||||
border: 1px solid #dc3545;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
#external_indicator_form code {
|
||||
background: #e9ecef;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
{% include "new_indicator_popup.html" %}
|
||||
{% include "trade_details_popup.html" %}
|
||||
{% include "indicator_popup.html" %}
|
||||
{% include "edit_indicator_popup.html" %}
|
||||
{% include "exchange_config_popup.html" %}
|
||||
{% include "account_settings_dialog.html" %}
|
||||
<!-- Container for the whole user app -->
|
||||
|
|
|
|||
|
|
@ -1,244 +1,246 @@
|
|||
<div class="content" id="indicator_panel">
|
||||
<!-- Indicator Panel Section -->
|
||||
|
||||
<div id="edit_indcr_panel" style="display: grid; grid-template-columns: 1fr 1fr;">
|
||||
<!-- Button for adding a new indicator -->
|
||||
<!-- Buttons for adding indicators -->
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr;">
|
||||
<div class="section" style="grid-column: 1;">
|
||||
<button class="btn" onclick="UI.indicators.open_form()">New Indicator</button>
|
||||
</div>
|
||||
<!-- Button for adding an external API indicator -->
|
||||
<div class="section" style="grid-column: 2;">
|
||||
<button class="btn" onclick="UI.signals.openExternalIndicatorForm()" style="width: 200px;">+ External Indicator</button>
|
||||
<button class="btn" style="width: 200px;" onclick="UI.signals.openExternalIndicatorForm()">+ External Indicator</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<!-- External Indicators Section -->
|
||||
<div id="external_indicators_section" style="display: none; margin: 15px 0; padding: 10px; border-radius: 8px;">
|
||||
<!-- External Indicators Section (Historical API - works with backtesting) -->
|
||||
<div id="external_indicators_section" style="display: none; margin-bottom: 15px;">
|
||||
<h3 style="color: #9f7aea; margin-bottom: 10px;">External Indicators <span style="font-size: 11px; color: #888;">(Historical API - works with backtesting)</span></h3>
|
||||
<div class="external-indicators-container" id="external_indicators_list" style="display: flex; flex-wrap: wrap; gap: 10px;"></div>
|
||||
<div class="external-indicators-container" id="external_indicators_list"></div>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<!-- 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">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 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 9; align-items: center; grid-template-columns: 75px 200px 100px 150px 150px 75px 100px 100px auto; gap: 10px; padding-left: 10px;">
|
||||
|
||||
<!-- 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 edit" onclick="UI.indicators.updateIndicator(event)">✔</button>
|
||||
</div>
|
||||
|
||||
<!-- Indicator Name -->
|
||||
<div style="text-align: center;">{{indicator}}</div>
|
||||
|
||||
<!-- Fixed property: Value -->
|
||||
<div style="text-align: center;">
|
||||
<!-- Create a generic value container for JavaScript to populate -->
|
||||
<textarea class="ie_value" id="{{indicator}}_value" name="value" readonly style="resize: none; overflow: hidden; height: auto; width: 100%; border: none; background: transparent; text-align: center;">N/A</textarea>
|
||||
</div>
|
||||
|
||||
<!-- Fixed property: Type -->
|
||||
<div style="text-align: center;">
|
||||
{% if 'type' in indicator_list[indicator] %}
|
||||
<span id="{{indicator}}_type">{{indicator_list[indicator]['type']}}</span>
|
||||
{% else %}
|
||||
<span>-</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
{% else %}
|
||||
<span>-</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 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'] if indicator_list[indicator]['color'] else '#000000' }}"
|
||||
name="color">
|
||||
{% else %}
|
||||
<input class="ietextbox" type="color" id="{{indicator}}_color" value="#000000" name="color">
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 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', 'source'] %}
|
||||
<div style="margin-bottom: 17px;">
|
||||
<label for="{{indicator}}_{{property}}" class="ietextbox-label">{{property}}</label>
|
||||
{% if 'color' in property %}
|
||||
<input class="ietextbox" type="color" id="{{indicator}}_{{property}}" value="{{value}}" name="{{property}}">
|
||||
{% else %}
|
||||
<input class="ietextbox" type="text" id="{{indicator}}_{{property}}" value="{{value}}" name="{{property}}">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
<!-- Regular Indicators Section (Icon Cards) -->
|
||||
<h3>Indicators</h3>
|
||||
<div class="indicators-container" id="indicators_list">
|
||||
<!-- Indicator cards will be rendered here by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Styles -->
|
||||
<style>
|
||||
/* 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;
|
||||
/* Indicators container - flex grid for cards */
|
||||
.indicators-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Style for Edit and Remove buttons */
|
||||
.e_btn {
|
||||
/* Individual indicator card */
|
||||
.indicator-item {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 120px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(145deg, #f0f0f0, #cacaca);
|
||||
box-shadow: 5px 5px 10px #bebebe, -5px -5px 10px #ffffff;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.indicator-item:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 8px 8px 15px #bebebe, -8px -8px 15px #ffffff;
|
||||
}
|
||||
|
||||
/* Visibility-based styling */
|
||||
.indicator-item.visible {
|
||||
border: 3px solid #28a745;
|
||||
}
|
||||
|
||||
.indicator-item.hidden {
|
||||
border: 3px solid #6c757d;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Icon area */
|
||||
.indicator-icon {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
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;
|
||||
position: relative;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* Green background for Edit button */
|
||||
.e_btn.edit {
|
||||
background-color: #4CAF50;
|
||||
}
|
||||
|
||||
/* Hover effect for buttons */
|
||||
.e_btn:hover {
|
||||
opacity: 0.9;
|
||||
box-shadow: 0px 6px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.e_btn:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Color picker input */
|
||||
input[type="color"] {
|
||||
border: 1px solid black;
|
||||
padding: 5px;
|
||||
width: 60px;
|
||||
.indicator-icon::before {
|
||||
content: '';
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 17px;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 8px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* Hover effect for color picker */
|
||||
input[type="color"]:hover {
|
||||
box-shadow: 0px 6px 8px rgba(0, 0, 0, 0.15);
|
||||
/* Indicator type symbol */
|
||||
.indicator-symbol {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* Styling for input fields and textboxes */
|
||||
.ietextbox {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
border-radius: 15px;
|
||||
box-sizing: border-box;
|
||||
/* Indicator name */
|
||||
.indicator-name {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
padding: 2px 5px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 94px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Alternating row colors */
|
||||
.indicator-row:nth-child(even) {
|
||||
background-color: #E0E0E0;
|
||||
/* Indicator type label */
|
||||
.indicator-item .indicator-type {
|
||||
font-size: 9px;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
padding: 0 5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.indicator-row:nth-child(odd) {
|
||||
background-color: #F7E1C1;
|
||||
/* Indicator value */
|
||||
.indicator-value {
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
padding: 2px 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Opposite colors for textboxes */
|
||||
.indicator-row:nth-child(even) .ietextbox {
|
||||
background-color: #F7E1C1;
|
||||
/* Delete button (top-right, shown on hover) */
|
||||
.indicator-item .delete-btn {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: 2px solid white;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
transition: transform 0.2s;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.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 */
|
||||
.indicator-item:hover .delete-btn {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.indicator-item .delete-btn:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
/* Visibility toggle button (top-left) */
|
||||
.indicator-item .visibility-btn {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: -8px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border: 2px solid white;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.indicator-item:hover .visibility-btn {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.indicator-item .visibility-btn:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.indicator-item.hidden .visibility-btn {
|
||||
background: #6c757d;
|
||||
}
|
||||
|
||||
/* External indicators container */
|
||||
.external-indicators-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.external-indicator-item {
|
||||
position: relative;
|
||||
padding: 10px 15px;
|
||||
background: linear-gradient(145deg, #3d2a5a, #2a1e3e);
|
||||
border: 1px solid #6f42c1;
|
||||
border-radius: 8px;
|
||||
min-width: 150px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.external-indicator-item:hover {
|
||||
border-color: #9f7aea;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.external-indicator-item .indicator-name {
|
||||
font-weight: bold;
|
||||
color: #e0e0e0;
|
||||
font-size: 13px;
|
||||
margin-bottom: 5px;
|
||||
max-width: none;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.external-indicator-item .indicator-type {
|
||||
font-size: 11px;
|
||||
color: #9f7aea;
|
||||
}
|
||||
|
||||
.external-indicator-item .delete-indicator-btn {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.external-indicator-item:hover .delete-indicator-btn {
|
||||
display: flex;
|
||||
}
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Authentication Section -->
|
||||
<div style="background: #f5f5f5; padding: 12px; border-radius: 6px;">
|
||||
<div style="background: #f8f9fa; padding: 12px; border-radius: 6px; border: 1px solid #eee;">
|
||||
<label style="display: block; margin-bottom: 8px;"><b>Authentication (optional):</b></label>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
|
||||
<!-- Loading indicator -->
|
||||
<div id="signal_type_loading" style="display: none; text-align: center; padding: 10px;">
|
||||
<div style="border: 3px solid #444; border-top: 3px solid #3E3AF2; border-radius: 50%; width: 24px; height: 24px; animation: spin 1s linear infinite; margin: 0 auto;"></div>
|
||||
<div style="border: 3px solid #ddd; border-top: 3px solid #3E3AF2; border-radius: 50%; width: 24px; height: 24px; animation: spin 1s linear infinite; margin: 0 auto;"></div>
|
||||
<span style="color: #666; font-size: 12px;">Testing API connection...</span>
|
||||
</div>
|
||||
|
||||
|
|
@ -105,71 +105,14 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
#new_signal_type_form {
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #444;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
#new_signal_type_form input[type="text"],
|
||||
#new_signal_type_form input[type="password"],
|
||||
#new_signal_type_form input[type="number"] {
|
||||
background: #2a2a2a;
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
|
||||
#new_signal_type_form input::placeholder {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#new_signal_type_form .btn.cancel {
|
||||
background: #444;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#new_signal_type_form .btn.cancel:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
#new_signal_type_form .btn.submit {
|
||||
background: linear-gradient(135deg, #3E3AF2 0%, #6366f1 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#new_signal_type_form .btn.submit:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
#new_signal_type_form label b {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
#new_signal_type_form small {
|
||||
color: #888 !important;
|
||||
}
|
||||
|
||||
#new_signal_type_form div[style*="background: #f5f5f5"] {
|
||||
background: #2a2a2a !important;
|
||||
}
|
||||
|
||||
#signal_type_test_result.success {
|
||||
background: #1e3a1e;
|
||||
background: rgba(40, 167, 69, 0.1);
|
||||
border: 1px solid #28a745;
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
#signal_type_test_result.error {
|
||||
background: #3a1e1e;
|
||||
background: rgba(220, 53, 69, 0.1);
|
||||
border: 1px solid #dc3545;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<button class="btn" id="new_strats_btn" onclick="UI.strats.uiManager.displayForm('new')">New Strategy</button>
|
||||
</div>
|
||||
<div class="section" style="grid-column: 2;">
|
||||
<button class="btn" id="browse_public_btn" onclick="UI.strats.uiManager.showPublicStrategyBrowser()">+ Add Public</button>
|
||||
<button class="btn" id="browse_public_btn" onclick="UI.strats.uiManager.showPublicStrategyBrowser()">Add Public</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
|
|
|||
Loading…
Reference in New Issue