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:
rob 2026-03-09 23:22:15 -03:00
parent c9cc0487fe
commit c8c51841cf
12 changed files with 904 additions and 453 deletions

View File

@ -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()">&times;</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()">&times;</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');
}
}
}

View File

@ -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) {

View File

@ -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");

View File

@ -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;
}
}
}

View File

@ -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';
}
}

View File

@ -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 */

View File

@ -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()">&times;</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>

View File

@ -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()">&times;</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>

View File

@ -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 -->

View File

@ -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)">&#10008;</button>
<button type="button" class="e_btn edit" onclick="UI.indicators.updateIndicator(event)">&#x2714;</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>

View File

@ -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;
}

View File

@ -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>