Spaces:
Running
Running
gimme a nie indicator panel that allows me to script a custom indicator in javascript
aa8bc9a verified | class CustomIndicator extends HTMLElement { | |
| connectedCallback() { | |
| this.attachShadow({ mode: 'open' }); | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| display: block; | |
| background: #1e293b; | |
| border-radius: 0.5rem; | |
| padding: 1rem; | |
| margin-top: 1rem; | |
| border: 1px solid #334155; | |
| } | |
| .header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1rem; | |
| padding-bottom: 0.5rem; | |
| border-bottom: 1px solid #334155; | |
| } | |
| .title { | |
| font-weight: bold; | |
| font-size: 1.125rem; | |
| } | |
| .controls { | |
| display: flex; | |
| gap: 0.5rem; | |
| } | |
| .editor-container { | |
| position: relative; | |
| height: 300px; | |
| margin-bottom: 1rem; | |
| } | |
| #editor { | |
| width: 100%; | |
| height: 100%; | |
| border-radius: 0.375rem; | |
| background: #0f172a; | |
| color: #f8fafc; | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.875rem; | |
| padding: 0.5rem; | |
| border: 1px solid #334155; | |
| resize: none; | |
| } | |
| .btn { | |
| padding: 0.5rem 1rem; | |
| border-radius: 0.375rem; | |
| font-weight: 500; | |
| cursor: pointer; | |
| border: none; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .btn-run { | |
| background: #3b82f6; | |
| color: white; | |
| } | |
| .btn-reset { | |
| background: #334155; | |
| color: white; | |
| } | |
| .output { | |
| margin-top: 1rem; | |
| padding: 0.75rem; | |
| background: #0f172a; | |
| border-radius: 0.375rem; | |
| border: 1px solid #334155; | |
| font-family: monospace; | |
| font-size: 0.875rem; | |
| white-space: pre-wrap; | |
| min-height: 100px; | |
| max-height: 200px; | |
| overflow-y: auto; | |
| } | |
| .error { | |
| color: #ef4444; | |
| } | |
| .success { | |
| color: #10b981; | |
| } | |
| </style> | |
| <div class="header"> | |
| <div class="title">Custom Indicator</div> | |
| <div class="controls"> | |
| <button class="btn btn-reset" id="reset-btn"> | |
| <i data-feather="trash-2"></i> Reset | |
| </button> | |
| <button class="btn btn-run" id="run-btn"> | |
| <i data-feather="play"></i> Run | |
| </button> | |
| </div> | |
| </div> | |
| <div class="editor-container"> | |
| <textarea id="editor">// Custom Indicator Script | |
| // Access price data with: data.close, data.open, data.high, data.low | |
| // Must return an object with format: { value: number, color?: string, text?: string } | |
| function calculateIndicator(data) { | |
| // Example: Simple Moving Average (SMA) | |
| if (data.length < 14) return null; | |
| const closePrices = data.map(d => d.close); | |
| const sum = closePrices.slice(-14).reduce((a, b) => a + b, 0); | |
| const sma = sum / 14; | |
| return { | |
| value: sma, | |
| color: sma > data[data.length-1].close ? '#ef4444' : '#10b981', | |
| text: \`SMA(14): \${sma.toFixed(2)}\` | |
| }; | |
| } | |
| return calculateIndicator(data);</textarea> | |
| </div> | |
| <div class="output" id="output"></div> | |
| `; | |
| if (window.feather) { | |
| window.feather.replace({ class: 'feather-inline' }); | |
| } | |
| this.shadowRoot.getElementById('run-btn').addEventListener('click', () => this.runScript()); | |
| this.shadowRoot.getElementById('reset-btn').addEventListener('click', () => this.resetScript()); | |
| // Sample data for testing | |
| this.sampleData = Array.from({ length: 30 }, (_, i) => ({ | |
| close: 25000 + Math.sin(i/3) * 1000 + Math.random() * 500, | |
| open: 25000 + Math.sin(i/3) * 1000 + Math.random() * 500, | |
| high: 25000 + Math.sin(i/3) * 1000 + Math.random() * 600, | |
| low: 25000 + Math.sin(i/3) * 1000 - Math.random() * 400, | |
| volume: 1000 + Math.random() * 5000 | |
| })); | |
| } | |
| runScript() { | |
| const editor = this.shadowRoot.getElementById('editor'); | |
| const output = this.shadowRoot.getElementById('output'); | |
| try { | |
| output.innerHTML = ''; | |
| output.classList.remove('error'); | |
| // Create a function from the editor content | |
| const indicatorFn = new Function('data', ` | |
| ${editor.value} | |
| `); | |
| // Execute with sample data | |
| const result = indicatorFn(this.sampleData); | |
| if (result === null || result === undefined) { | |
| output.innerHTML = 'Indicator returned no data (null)'; | |
| } else if (typeof result === 'object' && 'value' in result) { | |
| output.classList.add('success'); | |
| output.innerHTML = `Indicator value: ${result.value}\n\n`; | |
| if (result.text) { | |
| output.innerHTML += `Display text: ${result.text}\n\n`; | |
| } | |
| if (result.color) { | |
| output.innerHTML += `Color: <span style="color:${result.color}">${result.color}</span>`; | |
| } | |
| // Emit event with the indicator function | |
| this.dispatchEvent(new CustomEvent('indicator-created', { | |
| detail: { | |
| fn: indicatorFn, | |
| config: result | |
| } | |
| })); | |
| } else { | |
| throw new Error('Indicator must return an object with at least a "value" property'); | |
| } | |
| } catch (err) { | |
| output.classList.add('error'); | |
| output.innerHTML = err.message; | |
| console.error(err); | |
| } | |
| } | |
| resetScript() { | |
| const editor = this.shadowRoot.getElementById('editor'); | |
| const output = this.shadowRoot.getElementById('output'); | |
| editor.value = `// Custom Indicator Script | |
| // Access price data with: data.close, data.open, data.high, data.low | |
| // Must return an object with format: { value: number, color?: string, text?: string } | |
| function calculateIndicator(data) { | |
| // Example: Simple Moving Average (SMA) | |
| if (data.length < 14) return null; | |
| const closePrices = data.map(d => d.close); | |
| const sum = closePrices.slice(-14).reduce((a, b) => a + b, 0); | |
| const sma = sum / 14; | |
| return { | |
| value: sma, | |
| color: sma > data[data.length-1].close ? '#ef4444' : '#10b981', | |
| text: \`SMA(14): \${sma.toFixed(2)}\` | |
| }; | |
| } | |
| return calculateIndicator(data);`; | |
| output.innerHTML = ''; | |
| output.classList.remove('error', 'success'); | |
| this.dispatchEvent(new CustomEvent('indicator-removed')); | |
| } | |
| } | |
| customElements.define('custom-indicator', CustomIndicator); |