Indicator Alerts
Create custom alerts triggered by indicator conditions like RSI overbought/oversold, MACD crossovers, and price levels.
Basic Alert System
class AlertManager {
constructor(chart) {
this.chart = chart
this.alerts = []
this.activeAlerts = new Set()
}
addAlert(config) {
const alert = {
id: `alert_${Date.now()}`,
...config,
triggered: false
}
this.alerts.push(alert)
return alert.id
}
checkAlerts(data) {
this.alerts.forEach(alert => {
if (!alert.triggered && this.evaluateCondition(alert, data)) {
this.triggerAlert(alert)
}
})
}
evaluateCondition(alert, data) {
const { type, indicator, condition, value } = alert
switch (type) {
case 'indicator':
return this.checkIndicatorCondition(indicator, condition, value, data)
case 'price':
return this.checkPriceCondition(condition, value, data)
default:
return false
}
}
checkIndicatorCondition(indicator, condition, value, data) {
const indicatorValue = this.getIndicatorValue(indicator, data)
switch (condition) {
case 'above':
return indicatorValue > value
case 'below':
return indicatorValue < value
case 'crosses_above':
return this.checkCrossAbove(indicator, value, data)
case 'crosses_below':
return this.checkCrossBelow(indicator, value, data)
default:
return false
}
}
triggerAlert(alert) {
alert.triggered = true
this.activeAlerts.add(alert.id)
// Execute callback
if (alert.onTrigger) {
alert.onTrigger(alert)
}
// Show notification
this.showNotification(alert)
// Play sound
if (alert.sound) {
this.playSound(alert.sound)
}
}
showNotification(alert) {
if ('Notification' in window && Notification.permission === 'granted') {
new Notification('Chart Alert', {
body: alert.message,
icon: '/chart-icon.png'
})
}
}
playSound(soundUrl) {
const audio = new Audio(soundUrl)
audio.play()
}
removeAlert(id) {
this.alerts = this.alerts.filter(a => a.id !== id)
this.activeAlerts.delete(id)
}
clearAll() {
this.alerts = []
this.activeAlerts.clear()
}
}
// Usage
const alertManager = new AlertManager(chart)
alertManager.addAlert({
type: 'indicator',
indicator: 'RSI',
condition: 'above',
value: 70,
message: 'RSI is overbought!',
sound: '/alert.mp3',
onTrigger: (alert) => {
console.log('Alert triggered:', alert)
}
})
RSI Alerts
class RSIAlertSystem {
constructor(chart, period = 14) {
this.chart = chart
this.period = period
this.overboughtLevel = 70
this.oversoldLevel = 30
this.lastRSI = null
}
checkAlerts() {
const rsi = this.calculateRSI()
if (rsi > this.overboughtLevel && this.lastRSI <= this.overboughtLevel) {
this.triggerAlert('overbought', rsi)
}
if (rsi < this.oversoldLevel && this.lastRSI >= this.oversoldLevel) {
this.triggerAlert('oversold', rsi)
}
this.lastRSI = rsi
}
calculateRSI() {
const data = this.chart.getData()
const closes = data.slice(-this.period - 1).map(c => c[4])
let gains = 0
let losses = 0
for (let i = 1; i < closes.length; i++) {
const change = closes[i] - closes[i - 1]
if (change > 0) {
gains += change
} else {
losses += Math.abs(change)
}
}
const avgGain = gains / this.period
const avgLoss = losses / this.period
if (avgLoss === 0) return 100
const rs = avgGain / avgLoss
return 100 - (100 / (1 + rs))
}
triggerAlert(type, value) {
const message = type === 'overbought'
? `RSI is overbought at ${value.toFixed(2)}`
: `RSI is oversold at ${value.toFixed(2)}`
this.showNotification(message)
this.playSound()
}
showNotification(message) {
if ('Notification' in window && Notification.permission === 'granted') {
new Notification('RSI Alert', { body: message })
} else {
alert(message)
}
}
playSound() {
const audio = new Audio('/alert.mp3')
audio.play().catch(e => console.log('Could not play sound'))
}
}
// Usage
const rsiAlerts = new RSIAlertSystem(chart, 14)
// Check on every update
chart.on('update', () => {
rsiAlerts.checkAlerts()
})
MACD Crossover Alerts
class MACDAlertSystem {
constructor(chart) {
this.chart = chart
this.lastMACD = null
this.lastSignal = null
}
checkCrossover() {
const { macd, signal } = this.calculateMACD()
if (this.lastMACD !== null && this.lastSignal !== null) {
// Bullish crossover
if (macd > signal && this.lastMACD <= this.lastSignal) {
this.triggerAlert('bullish', macd, signal)
}
// Bearish crossover
if (macd < signal && this.lastMACD >= this.lastSignal) {
this.triggerAlert('bearish', macd, signal)
}
}
this.lastMACD = macd
this.lastSignal = signal
}
calculateMACD() {
// Simplified MACD calculation
const data = this.chart.getData()
const closes = data.map(c => c[4])
const ema12 = this.calculateEMA(closes, 12)
const ema26 = this.calculateEMA(closes, 26)
const macd = ema12 - ema26
// Signal line (9-period EMA of MACD)
const signal = this.calculateEMA([macd], 9)
return { macd, signal }
}
calculateEMA(data, period) {
const k = 2 / (period + 1)
let ema = data[0]
for (let i = 1; i < data.length; i++) {
ema = data[i] * k + ema * (1 - k)
}
return ema
}
triggerAlert(type, macd, signal) {
const message = type === 'bullish'
? `Bullish MACD crossover detected`
: `Bearish MACD crossover detected`
this.showNotification(message, type)
}
showNotification(message, type) {
const color = type === 'bullish' ? '#4CAF50' : '#f44336'
// Browser notification
if ('Notification' in window && Notification.permission === 'granted') {
new Notification('MACD Alert', {
body: message,
icon: '/chart-icon.png'
})
}
// Visual alert on chart
this.chart.showAlert(message, color)
}
}
// Usage
const macdAlerts = new MACDAlertSystem(chart)
chart.on('update', () => {
macdAlerts.checkCrossover()
})
Price Level Alerts
class PriceLevelAlerts {
constructor(chart) {
this.chart = chart
this.levels = []
}
addLevel(price, type = 'both') {
const level = {
id: `level_${Date.now()}`,
price,
type, // 'above', 'below', or 'both'
triggered: false
}
this.levels.push(level)
// Draw level on chart
this.chart.addLine({
price: price,
color: '#FFA500',
style: 'dashed',
label: `Alert: ${price}`
})
return level.id
}
checkLevels() {
const currentPrice = this.chart.getCurrentPrice()
this.levels.forEach(level => {
if (level.triggered) return
const crossed = this.checkCross(level, currentPrice)
if (crossed) {
this.triggerAlert(level, currentPrice)
}
})
}
checkCross(level, currentPrice) {
if (level.type === 'above' && currentPrice >= level.price) {
return true
}
if (level.type === 'below' && currentPrice <= level.price) {
return true
}
if (level.type === 'both') {
return Math.abs(currentPrice - level.price) < 0.01
}
return false
}
triggerAlert(level, currentPrice) {
level.triggered = true
const message = `Price crossed ${level.price} (Current: ${currentPrice})`
this.showNotification(message)
this.highlightLevel(level)
}
showNotification(message) {
if ('Notification' in window && Notification.permission === 'granted') {
new Notification('Price Alert', { body: message })
}
}
highlightLevel(level) {
this.chart.updateLine(level.id, {
color: '#FF0000',
width: 2
})
}
removeLevel(id) {
this.levels = this.levels.filter(l => l.id !== id)
this.chart.removeLine(id)
}
}
// Usage
const priceAlerts = new PriceLevelAlerts(chart)
priceAlerts.addLevel(50000, 'above')
priceAlerts.addLevel(45000, 'below')
chart.on('update', () => {
priceAlerts.checkLevels()
})
Complete Alert UI
<!DOCTYPE html>
<html>
<head>
<style>
.alert-panel {
position: fixed;
top: 20px;
right: 20px;
width: 300px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 20px;
}
.alert-form {
margin-bottom: 20px;
}
.alert-form select,
.alert-form input {
width: 100%;
padding: 8px;
margin: 5px 0;
border: 1px solid #ddd;
border-radius: 4px;
}
.alert-form button {
width: 100%;
padding: 10px;
background: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.alert-list {
max-height: 300px;
overflow-y: auto;
}
.alert-item {
padding: 10px;
margin: 5px 0;
background: #f5f5f5;
border-radius: 4px;
display: flex;
justify-content: space-between;
align-items: center;
}
.alert-item.triggered {
background: #ffebee;
}
.alert-remove {
color: #f44336;
cursor: pointer;
}
</style>
</head>
<body>
<div class="alert-panel">
<h3>Alerts</h3>
<div class="alert-form">
<select id="alert-type">
<option value="rsi">RSI</option>
<option value="macd">MACD</option>
<option value="price">Price Level</option>
</select>
<input type="number" id="alert-value" placeholder="Value">
<select id="alert-condition">
<option value="above">Above</option>
<option value="below">Below</option>
<option value="crosses_above">Crosses Above</option>
<option value="crosses_below">Crosses Below</option>
</select>
<button onclick="addAlert()">Add Alert</button>
</div>
<div class="alert-list" id="alert-list"></div>
</div>
<tradex-chart id="chart"></tradex-chart>
<script>
const chart = document.getElementById('chart')
const alertManager = new AlertManager(chart)
// Request notification permission
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission()
}
function addAlert() {
const type = document.getElementById('alert-type').value
const value = parseFloat(document.getElementById('alert-value').value)
const condition = document.getElementById('alert-condition').value
const id = alertManager.addAlert({
type: 'indicator',
indicator: type.toUpperCase(),
condition,
value,
message: `${type.toUpperCase()} ${condition} ${value}`,
sound: '/alert.mp3',
onTrigger: (alert) => {
updateAlertList()
}
})
updateAlertList()
}
function updateAlertList() {
const list = document.getElementById('alert-list')
list.innerHTML = ''
alertManager.alerts.forEach(alert => {
const item = document.createElement('div')
item.className = `alert-item ${alert.triggered ? 'triggered' : ''}`
item.innerHTML = `
<span>${alert.message}</span>
<span class="alert-remove" onclick="removeAlert('${alert.id}')">×</span>
`
list.appendChild(item)
})
}
function removeAlert(id) {
alertManager.removeAlert(id)
updateAlertList()
}
// Check alerts on every update
chart.on('update', () => {
const data = chart.getData()
alertManager.checkAlerts(data)
})
</script>
</body>
</html>
React Alert Component
import { useState, useEffect } from 'react'
function AlertPanel({ chart }) {
const [alerts, setAlerts] = useState([])
const [newAlert, setNewAlert] = useState({
type: 'rsi',
condition: 'above',
value: 70
})
useEffect(() => {
// Request notification permission
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission()
}
}, [])
const addAlert = () => {
const alert = {
id: Date.now(),
...newAlert,
triggered: false
}
setAlerts([...alerts, alert])
}
const removeAlert = (id) => {
setAlerts(alerts.filter(a => a.id !== id))
}
return (
<div className="alert-panel">
<h3>Alerts</h3>
<div className="alert-form">
<select
value={newAlert.type}
onChange={(e) => setNewAlert({...newAlert, type: e.target.value})}
>
<option value="rsi">RSI</option>
<option value="macd">MACD</option>
<option value="price">Price</option>
</select>
<input
type="number"
value={newAlert.value}
onChange={(e) => setNewAlert({...newAlert, value: e.target.value})}
/>
<button onClick={addAlert}>Add Alert</button>
</div>
<div className="alert-list">
{alerts.map(alert => (
<div key={alert.id} className={`alert-item ${alert.triggered ? 'triggered' : ''}`}>
<span>{alert.type} {alert.condition} {alert.value}</span>
<button onClick={() => removeAlert(alert.id)}>×</button>
</div>
))}
</div>
</div>
)
}
export default AlertPanel
Related Documentation
- API Reference - Alert API
- Indicators - Indicator guide
- Events - Chart events