Add candlestick pattern indicators with TA-Lib integration
- Add 15 candlestick pattern indicators (Doji, Hammer, Engulfing, etc.) - Create dedicated Patterns chart pane for pattern visualization - Add hover tooltips with descriptions and SVG diagrams in indicator selector - Fix color picker compatibility by using hex format instead of RGBA - Fix chart positioning and timestamp synchronization for pattern chart Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7b796c51c8
commit
4ab7a5023d
|
|
@ -302,6 +302,160 @@ class MACD(Indicator):
|
|||
return df.iloc[self.properties['signal_p']:]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Candlestick Pattern Recognition Indicators
|
||||
# ============================================================================
|
||||
|
||||
class CandlestickPattern(Indicator):
|
||||
"""
|
||||
Base class for candlestick pattern recognition indicators.
|
||||
|
||||
Pattern indicators return discrete signals:
|
||||
- 100 = Bullish pattern detected
|
||||
- -100 = Bearish pattern detected
|
||||
- 0 = No pattern
|
||||
|
||||
All patterns require OHLC data (open, high, low, close).
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, indicator_type: str, properties: dict):
|
||||
super().__init__(name, indicator_type, properties)
|
||||
# Default display properties for candlestick patterns (hex format for color picker compatibility)
|
||||
self.properties.setdefault('bullish_color', '#00C853')
|
||||
self.properties.setdefault('bearish_color', '#FF5252')
|
||||
# Patterns don't use period in the same way - set to 0 to avoid warmup trimming
|
||||
self.properties.setdefault('period', 0)
|
||||
|
||||
def calculate(self, candles: pd.DataFrame, user_name: str, num_results: int = 1) -> pd.DataFrame:
|
||||
"""
|
||||
Calculate pattern detection across all candles.
|
||||
Returns a DataFrame with 'time' and 'value' columns.
|
||||
"""
|
||||
# Extract OHLC data as numpy arrays
|
||||
opens = candles.open.to_numpy(dtype='float')
|
||||
highs = candles.high.to_numpy(dtype='float')
|
||||
lows = candles.low.to_numpy(dtype='float')
|
||||
closes = candles.close.to_numpy(dtype='float')
|
||||
|
||||
# Call the pattern-specific detection method
|
||||
pattern_values = self.detect(opens, highs, lows, closes)
|
||||
|
||||
# Store the latest value
|
||||
self.properties['value'] = int(pattern_values[-1])
|
||||
|
||||
# Create DataFrame with signal values
|
||||
df = pd.DataFrame({
|
||||
'time': candles.time,
|
||||
'value': pattern_values.astype(int).tolist()
|
||||
})
|
||||
|
||||
return df
|
||||
|
||||
def detect(self, opens: np.ndarray, highs: np.ndarray,
|
||||
lows: np.ndarray, closes: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Override in subclass to call specific TA-Lib pattern function.
|
||||
Must return a numpy array of pattern signals (100, -100, or 0).
|
||||
"""
|
||||
raise NotImplementedError("Subclasses must implement the 'detect' method.")
|
||||
|
||||
|
||||
# Single Candle Patterns (6)
|
||||
|
||||
class CDL_Doji(CandlestickPattern):
|
||||
"""Doji - Indecision pattern where open and close are nearly equal."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDLDOJI(opens, highs, lows, closes)
|
||||
|
||||
|
||||
class CDL_Hammer(CandlestickPattern):
|
||||
"""Hammer - Bullish reversal pattern with small body and long lower shadow."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDLHAMMER(opens, highs, lows, closes)
|
||||
|
||||
|
||||
class CDL_InvertedHammer(CandlestickPattern):
|
||||
"""Inverted Hammer - Bullish reversal pattern with small body and long upper shadow."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDLINVERTEDHAMMER(opens, highs, lows, closes)
|
||||
|
||||
|
||||
class CDL_ShootingStar(CandlestickPattern):
|
||||
"""Shooting Star - Bearish reversal pattern with small body and long upper shadow."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDLSHOOTINGSTAR(opens, highs, lows, closes)
|
||||
|
||||
|
||||
class CDL_SpinningTop(CandlestickPattern):
|
||||
"""Spinning Top - Indecision pattern with small body and equal shadows."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDLSPINNINGTOP(opens, highs, lows, closes)
|
||||
|
||||
|
||||
class CDL_Marubozu(CandlestickPattern):
|
||||
"""Marubozu - Strong trend candle with no shadows (or very small)."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDLMARUBOZU(opens, highs, lows, closes)
|
||||
|
||||
|
||||
# Two Candle Patterns (4)
|
||||
|
||||
class CDL_Engulfing(CandlestickPattern):
|
||||
"""Engulfing - Reversal pattern where second candle completely engulfs the first."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDLENGULFING(opens, highs, lows, closes)
|
||||
|
||||
|
||||
class CDL_Harami(CandlestickPattern):
|
||||
"""Harami - Reversal pattern where second candle is contained within the first."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDLHARAMI(opens, highs, lows, closes)
|
||||
|
||||
|
||||
class CDL_Piercing(CandlestickPattern):
|
||||
"""Piercing Line - Bullish reversal where second candle opens below and closes above midpoint."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDLPIERCING(opens, highs, lows, closes)
|
||||
|
||||
|
||||
class CDL_DarkCloudCover(CandlestickPattern):
|
||||
"""Dark Cloud Cover - Bearish reversal where second candle opens above and closes below midpoint."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDLDARKCLOUDCOVER(opens, highs, lows, closes)
|
||||
|
||||
|
||||
# Three+ Candle Patterns (5)
|
||||
|
||||
class CDL_MorningStar(CandlestickPattern):
|
||||
"""Morning Star - Bullish reversal with three candles: bearish, small body, bullish."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDLMORNINGSTAR(opens, highs, lows, closes)
|
||||
|
||||
|
||||
class CDL_EveningStar(CandlestickPattern):
|
||||
"""Evening Star - Bearish reversal with three candles: bullish, small body, bearish."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDLEVENINGSTAR(opens, highs, lows, closes)
|
||||
|
||||
|
||||
class CDL_ThreeWhiteSoldiers(CandlestickPattern):
|
||||
"""Three White Soldiers - Strong bullish pattern with three consecutive bullish candles."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDL3WHITESOLDIERS(opens, highs, lows, closes)
|
||||
|
||||
|
||||
class CDL_ThreeBlackCrows(CandlestickPattern):
|
||||
"""Three Black Crows - Strong bearish pattern with three consecutive bearish candles."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDL3BLACKCROWS(opens, highs, lows, closes)
|
||||
|
||||
|
||||
class CDL_ThreeInside(CandlestickPattern):
|
||||
"""Three Inside Up/Down - Reversal pattern confirming a harami with a third candle."""
|
||||
def detect(self, opens, highs, lows, closes):
|
||||
return talib.CDL3INSIDE(opens, highs, lows, closes)
|
||||
|
||||
|
||||
# Register indicators in the registry
|
||||
indicators_registry['Volume'] = Volume
|
||||
indicators_registry['SMA'] = SMA
|
||||
|
|
@ -313,6 +467,26 @@ indicators_registry['BOLBands'] = BolBands
|
|||
indicators_registry['BOL%B'] = BollingerPercentB
|
||||
indicators_registry['MACD'] = MACD
|
||||
|
||||
# Register candlestick pattern indicators
|
||||
# Single Candle Patterns
|
||||
indicators_registry['CDL_DOJI'] = CDL_Doji
|
||||
indicators_registry['CDL_HAMMER'] = CDL_Hammer
|
||||
indicators_registry['CDL_INVERTEDHAMMER'] = CDL_InvertedHammer
|
||||
indicators_registry['CDL_SHOOTINGSTAR'] = CDL_ShootingStar
|
||||
indicators_registry['CDL_SPINNINGTOP'] = CDL_SpinningTop
|
||||
indicators_registry['CDL_MARUBOZU'] = CDL_Marubozu
|
||||
# Two Candle Patterns
|
||||
indicators_registry['CDL_ENGULFING'] = CDL_Engulfing
|
||||
indicators_registry['CDL_HARAMI'] = CDL_Harami
|
||||
indicators_registry['CDL_PIERCING'] = CDL_Piercing
|
||||
indicators_registry['CDL_DARKCLOUDCOVER'] = CDL_DarkCloudCover
|
||||
# Three+ Candle Patterns
|
||||
indicators_registry['CDL_MORNINGSTAR'] = CDL_MorningStar
|
||||
indicators_registry['CDL_EVENINGSTAR'] = CDL_EveningStar
|
||||
indicators_registry['CDL_3WHITESOLDIERS'] = CDL_ThreeWhiteSoldiers
|
||||
indicators_registry['CDL_3BLACKCROWS'] = CDL_ThreeBlackCrows
|
||||
indicators_registry['CDL_3INSIDE'] = CDL_ThreeInside
|
||||
|
||||
|
||||
class Indicators:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -367,6 +367,14 @@ height: 500px;
|
|||
grid-column: 1;
|
||||
grid-row:4;
|
||||
}
|
||||
#chart4{
|
||||
grid-column: 1;
|
||||
grid-row:5;
|
||||
}
|
||||
#chart5{
|
||||
grid-column: 1;
|
||||
grid-row:6;
|
||||
}
|
||||
/***************************************************/
|
||||
|
||||
/****************Right Panel***********************/
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ class Charts {
|
|||
this.chart2_id = idata.chart2_id;
|
||||
this.chart3_id = idata.chart3_id;
|
||||
this.chart4_id = idata.chart4_id;
|
||||
this.chart5_id = idata.chart5_id; // Patterns chart
|
||||
this.trading_pair = idata.trading_pair;
|
||||
this.price_history = idata.price_history;
|
||||
/* A list of bound charts this is necessary for maintaining a dynamic
|
||||
|
|
@ -68,6 +69,23 @@ class Charts {
|
|||
this.bind_charts(this.chart4);
|
||||
}
|
||||
|
||||
create_patterns_chart(){
|
||||
// Create a chart for candlestick pattern indicators
|
||||
// Scale is fixed -100 to +100 (bearish to bullish signals)
|
||||
this.chart5 = this.create_chart(this.chart5_id, 100);
|
||||
this.set_priceScale(this.chart5, 0.1, 0.1);
|
||||
// Put the name of the chart in a watermark
|
||||
this.addWatermark(this.chart5, 'Patterns');
|
||||
this.bind_charts(this.chart5);
|
||||
|
||||
// Sync time scale with main chart so timestamps align
|
||||
if (this.chart_1) {
|
||||
let barSpacing = this.chart_1.timeScale().getBarSpacing();
|
||||
let scrollPosition = this.chart_1.timeScale().scrollPosition();
|
||||
this.chart5.timeScale().applyOptions({ rightOffset: scrollPosition, barSpacing: barSpacing });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
create_chart(target_id, height=500){
|
||||
// Accepts a target element to place the chart in.
|
||||
|
|
@ -148,6 +166,9 @@ class Charts {
|
|||
// if bound_charts has four elements in it bind them
|
||||
if (bcl == 4) { this.bind4charts(); }
|
||||
|
||||
// if bound_charts has five elements in it bind them
|
||||
if (bcl == 5) { this.bind5charts(); }
|
||||
|
||||
return;
|
||||
}
|
||||
add_to_list(chart){
|
||||
|
|
@ -233,6 +254,26 @@ class Charts {
|
|||
this.bound_charts[3].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(3));
|
||||
}
|
||||
|
||||
bind5charts(){
|
||||
// Sync all 5 charts together (main + RSI + MACD + %B + Patterns)
|
||||
let syncFromChart = (sourceIndex) => {
|
||||
return (e) => {
|
||||
let barSpacing = this.bound_charts[sourceIndex].timeScale().getBarSpacing();
|
||||
let scrollPosition = this.bound_charts[sourceIndex].timeScale().scrollPosition();
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (i !== sourceIndex) {
|
||||
this.bound_charts[i].timeScale().applyOptions({ rightOffset: scrollPosition, barSpacing: barSpacing });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.bound_charts[0].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(0));
|
||||
this.bound_charts[1].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(1));
|
||||
this.bound_charts[2].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(2));
|
||||
this.bound_charts[3].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(3));
|
||||
this.bound_charts[4].timeScale().subscribeVisibleTimeRangeChange(syncFromChart(4));
|
||||
}
|
||||
|
||||
// Set trade markers on chart for all trades in backtest results
|
||||
setTradeMarkers(trades) {
|
||||
if (!this.candleSeries) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ class Data {
|
|||
this.chart2_id = 'chart2';
|
||||
this.chart3_id = 'chart3';
|
||||
this.chart4_id = 'chart4';
|
||||
this.chart5_id = 'chart5'; // Candlestick patterns chart
|
||||
|
||||
/* Set into memory configuration data from the server. */
|
||||
// The assets being traded.
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ class User_Interface {
|
|||
chart2_id: this.data.chart2_id,
|
||||
chart3_id: this.data.chart3_id,
|
||||
chart4_id: this.data.chart4_id,
|
||||
chart5_id: this.data.chart5_id,
|
||||
trading_pair: this.data.trading_pair,
|
||||
price_history: this.data.price_history
|
||||
};
|
||||
|
|
|
|||
|
|
@ -545,6 +545,111 @@ class Bolenger extends Indicator {
|
|||
}
|
||||
indicatorMap.set("BOLBands", Bolenger);
|
||||
|
||||
|
||||
// Candlestick Pattern Indicator class
|
||||
// Displays pattern signals as histogram bars in a dedicated patterns chart
|
||||
class CandlestickPattern extends Indicator {
|
||||
constructor(name, charts, bullish_color, bearish_color) {
|
||||
super(name);
|
||||
// Create patterns chart if it doesn't exist
|
||||
if (!charts.hasOwnProperty('chart5') || !charts.chart5) {
|
||||
charts.create_patterns_chart();
|
||||
}
|
||||
let chart = charts.chart5;
|
||||
|
||||
// Store colors for pattern signals (hex format for color picker compatibility)
|
||||
this.bullish_color = bullish_color || '#00C853';
|
||||
this.bearish_color = bearish_color || '#FF5252';
|
||||
|
||||
// Add histogram series for this pattern
|
||||
this.addPatternHist(name, chart);
|
||||
this.outputs = ['value'];
|
||||
}
|
||||
|
||||
static getIndicatorConfig() {
|
||||
return {
|
||||
args: ['name', 'charts', 'bullish_color', 'bearish_color'],
|
||||
class: this
|
||||
};
|
||||
}
|
||||
|
||||
addPatternHist(name, chart) {
|
||||
// Create histogram series for pattern signals
|
||||
this.hist[name] = chart.addHistogramSeries({
|
||||
priceFormat: {
|
||||
type: 'price',
|
||||
},
|
||||
priceScaleId: 'right',
|
||||
});
|
||||
// Create legend for the pattern
|
||||
iOutput.create_legend(`${this.name}_pattern`, chart, this.hist[name]);
|
||||
}
|
||||
|
||||
init(data) {
|
||||
// Transform data to add colors based on signal value
|
||||
// Keep ALL data points (including zeros) to maintain proper time scale alignment
|
||||
// Zero values will have no visible bar (height = 0) but the timestamp is needed
|
||||
const histData = data.map(d => ({
|
||||
time: d.time,
|
||||
value: d.value,
|
||||
// Use bullish/bearish colors for signals, gray for no signal (won't be visible anyway)
|
||||
color: d.value > 0 ? this.bullish_color
|
||||
: d.value < 0 ? this.bearish_color
|
||||
: '#808080'
|
||||
}));
|
||||
this.setHist(this.name, histData);
|
||||
|
||||
// Update display with latest signal
|
||||
const latestValue = data.at(-1).value;
|
||||
let displayText = latestValue > 0 ? 'Bullish' : latestValue < 0 ? 'Bearish' : 'None';
|
||||
this.updateDisplay(this.name, displayText, 'value');
|
||||
}
|
||||
|
||||
update(data) {
|
||||
// Transform data to add colors based on signal value
|
||||
// Keep ALL data points to maintain time scale alignment with other charts
|
||||
const histData = data.map(d => ({
|
||||
time: d.time,
|
||||
value: d.value,
|
||||
color: d.value > 0 ? this.bullish_color
|
||||
: d.value < 0 ? this.bearish_color
|
||||
: '#808080'
|
||||
}));
|
||||
this.setHist(this.name, histData);
|
||||
|
||||
// Update display with latest signal
|
||||
const latestValue = data.at(-1).value;
|
||||
let displayText = latestValue > 0 ? 'Bullish' : latestValue < 0 ? 'Bearish' : 'None';
|
||||
this.updateDisplay(this.name, displayText, 'value');
|
||||
}
|
||||
|
||||
updateDisplay(name, value, value_name) {
|
||||
// Override to handle text display for patterns
|
||||
let element = document.getElementById(this.name + '_' + value_name);
|
||||
if (element) {
|
||||
element.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register all candlestick pattern types to use the same CandlestickPattern class
|
||||
indicatorMap.set("CDL_DOJI", CandlestickPattern);
|
||||
indicatorMap.set("CDL_HAMMER", CandlestickPattern);
|
||||
indicatorMap.set("CDL_INVERTEDHAMMER", CandlestickPattern);
|
||||
indicatorMap.set("CDL_SHOOTINGSTAR", CandlestickPattern);
|
||||
indicatorMap.set("CDL_SPINNINGTOP", CandlestickPattern);
|
||||
indicatorMap.set("CDL_MARUBOZU", CandlestickPattern);
|
||||
indicatorMap.set("CDL_ENGULFING", CandlestickPattern);
|
||||
indicatorMap.set("CDL_HARAMI", CandlestickPattern);
|
||||
indicatorMap.set("CDL_PIERCING", CandlestickPattern);
|
||||
indicatorMap.set("CDL_DARKCLOUDCOVER", CandlestickPattern);
|
||||
indicatorMap.set("CDL_MORNINGSTAR", CandlestickPattern);
|
||||
indicatorMap.set("CDL_EVENINGSTAR", CandlestickPattern);
|
||||
indicatorMap.set("CDL_3WHITESOLDIERS", CandlestickPattern);
|
||||
indicatorMap.set("CDL_3BLACKCROWS", CandlestickPattern);
|
||||
indicatorMap.set("CDL_3INSIDE", CandlestickPattern);
|
||||
|
||||
|
||||
class Indicators {
|
||||
constructor(comms) {
|
||||
// Contains instantiated indicators.
|
||||
|
|
@ -570,6 +675,8 @@ class Indicators {
|
|||
if (arg === 'color_1') return indicators[name].color_1 || 'red';
|
||||
if (arg === 'color_2') return indicators[name].color_2 || 'white';
|
||||
if (arg === 'color_3') return indicators[name].color_3 || 'blue';
|
||||
if (arg === 'bullish_color') return indicators[name].bullish_color || '#00C853';
|
||||
if (arg === 'bearish_color') return indicators[name].bearish_color || '#FF5252';
|
||||
});
|
||||
|
||||
this.i_objs[name] = new IndicatorConstructor(...preparedArgs);
|
||||
|
|
@ -658,12 +765,15 @@ class Indicators {
|
|||
let chart;
|
||||
|
||||
// Determine which chart the indicator is on, based on its type
|
||||
const indicatorType = this.i_objs[indicator].constructor.name;
|
||||
if (indicator.includes('RSI')) {
|
||||
chart = window.UI.charts.chart2; // Assume RSI is on chart2
|
||||
} else if (indicator.includes('MACD')) {
|
||||
chart = window.UI.charts.chart3; // Assume MACD is on chart3
|
||||
} else if (indicator.includes('%B') || indicator.includes('PercentB')) {
|
||||
chart = window.UI.charts.chart4; // %B is on chart4
|
||||
} else if (indicatorType === 'CandlestickPattern') {
|
||||
chart = window.UI.charts.chart5; // Candlestick patterns on chart5
|
||||
} else {
|
||||
chart = window.UI.charts.chart_1; // Default to the main chart
|
||||
}
|
||||
|
|
@ -906,18 +1016,19 @@ class Indicators {
|
|||
// Append all selected indicators as a single array-like structure
|
||||
formData.append('indicator', JSON.stringify(selectedIndicators));
|
||||
|
||||
// Show loading feedback immediately
|
||||
const submitBtn = form.querySelector('input[type="submit"]');
|
||||
const originalValue = submitBtn.value;
|
||||
submitBtn.value = 'Updating...';
|
||||
submitBtn.disabled = true;
|
||||
|
||||
// Hide the popup and show a brief loading indicator
|
||||
// Hide the popup immediately
|
||||
const popup = document.getElementById('indicators');
|
||||
if (popup) {
|
||||
popup.style.display = 'none';
|
||||
}
|
||||
|
||||
// Show a loading overlay on the page
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'loading-overlay';
|
||||
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;justify-content:center;align-items:center;z-index:9999;';
|
||||
overlay.innerHTML = '<div style="background:white;padding:20px 40px;border-radius:10px;font-size:18px;font-weight:bold;">Updating indicators...</div>';
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Send form data via AJAX (fetch)
|
||||
fetch(form.action, {
|
||||
method: form.method,
|
||||
|
|
@ -927,15 +1038,15 @@ class Indicators {
|
|||
// Handle success (you can reload the page or update the UI)
|
||||
window.location.reload();
|
||||
} else {
|
||||
// Restore button on error
|
||||
submitBtn.value = originalValue;
|
||||
submitBtn.disabled = false;
|
||||
// Remove overlay on error
|
||||
const overlay = document.getElementById('loading-overlay');
|
||||
if (overlay) overlay.remove();
|
||||
alert('Failed to update indicators.');
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Error:', error);
|
||||
submitBtn.value = originalValue;
|
||||
submitBtn.disabled = false;
|
||||
const overlay = document.getElementById('loading-overlay');
|
||||
if (overlay) overlay.remove();
|
||||
alert('An error occurred while updating the indicators.');
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
<!-- Container for the whole user app -->
|
||||
<div id="master_panel" class="master_panel"
|
||||
style="width 1550px; height 800px;display: grid;
|
||||
grid-template-columns: 1000px 550px; grid-template-rows: 50px 500px 100px 100px;">
|
||||
grid-template-columns: 1000px 550px; grid-template-rows: 50px 500px 100px 100px 100px 100px;">
|
||||
<!-- Application Header -->
|
||||
<div id="app_header">
|
||||
<H1 id="app_title">{{ title }}</H1>
|
||||
|
|
@ -61,6 +61,7 @@
|
|||
<div id="chart2"></div>
|
||||
<div id="chart3"></div>
|
||||
<div id="chart4"></div>
|
||||
<div id="chart5"></div> <!-- Candlestick Patterns chart -->
|
||||
<!-- This is the control panel on the right of the screen -->
|
||||
{% include "control_panel.html" %}
|
||||
</div><!-- End Master Panel --->
|
||||
|
|
|
|||
|
|
@ -14,14 +14,28 @@
|
|||
<input type="text" id="newi_name" value="New Indicator" style="width: 100%; margin-top: 5px;">
|
||||
</div>
|
||||
|
||||
<!-- Type selector -->
|
||||
<div style="margin-bottom: 10px;">
|
||||
<!-- Type selector with tooltip -->
|
||||
<div style="margin-bottom: 10px; position: relative;">
|
||||
<label for="newi_type"><b>Type:</b></label>
|
||||
<select id="newi_type" style="width: 100%; margin-top: 5px;">
|
||||
<div class="indicator-type-wrapper" style="position: relative;">
|
||||
<input type="text" id="newi_type_search" placeholder="Search indicators..."
|
||||
style="width: 100%; margin-top: 5px; padding: 8px; box-sizing: border-box;"
|
||||
value="{% if indicator_types %}{{ indicator_types[0] }}{% endif %}"
|
||||
onfocus="showIndicatorDropdown()" oninput="filterIndicatorTypes()">
|
||||
<input type="hidden" id="newi_type" value="{% if indicator_types %}{{ indicator_types[0] }}{% endif %}">
|
||||
|
||||
<!-- Custom dropdown -->
|
||||
<div id="indicator_dropdown" class="indicator-dropdown" style="display: none;">
|
||||
{% for i_type in indicator_types %}
|
||||
<option value="{{i_type}}">{{i_type}}</option>
|
||||
<div class="indicator-option" data-value="{{i_type}}"
|
||||
onmouseover="showIndicatorTooltip('{{i_type}}')"
|
||||
onclick="selectIndicatorType('{{i_type}}')">
|
||||
{{i_type}}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Properties display -->
|
||||
|
|
@ -47,6 +61,8 @@
|
|||
<option value="color_1">
|
||||
<option value="color_2">
|
||||
<option value="color_3">
|
||||
<option value="bullish_color">
|
||||
<option value="bearish_color">
|
||||
</datalist>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -98,3 +114,294 @@
|
|||
<!-- Resizer -->
|
||||
<div id="resize-indicator" class="resize-handle"></div>
|
||||
</div>
|
||||
|
||||
<!-- Tooltip panel - placed outside dialog to avoid overflow clipping -->
|
||||
<div id="indicator_tooltip" class="indicator-tooltip">
|
||||
<div id="tooltip_title" style="font-weight: bold; margin-bottom: 8px;"></div>
|
||||
<div id="tooltip_description" style="font-size: 12px; margin-bottom: 10px;"></div>
|
||||
<div id="tooltip_svg" style="text-align: center;"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.indicator-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.indicator-option {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.indicator-option:hover {
|
||||
background-color: #3E3AF2;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.indicator-option:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.indicator-tooltip {
|
||||
display: none;
|
||||
position: fixed;
|
||||
width: 280px;
|
||||
padding: 15px;
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.25);
|
||||
z-index: 10001;
|
||||
}
|
||||
|
||||
.indicator-tooltip::before {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Indicator descriptions and SVGs
|
||||
const indicatorInfo = {
|
||||
// Regular indicators
|
||||
'SMA': {
|
||||
description: 'Simple Moving Average - Calculates the average price over a specified period. Useful for identifying trends and support/resistance levels.',
|
||||
svg: null
|
||||
},
|
||||
'EMA': {
|
||||
description: 'Exponential Moving Average - Similar to SMA but gives more weight to recent prices, making it more responsive to new information.',
|
||||
svg: null
|
||||
},
|
||||
'RSI': {
|
||||
description: 'Relative Strength Index - Momentum oscillator measuring speed and magnitude of price changes. Values above 70 indicate overbought, below 30 oversold.',
|
||||
svg: null
|
||||
},
|
||||
'MACD': {
|
||||
description: 'Moving Average Convergence Divergence - Shows relationship between two EMAs. Used to identify trend direction, momentum, and potential reversals.',
|
||||
svg: null
|
||||
},
|
||||
'BOLBands': {
|
||||
description: 'Bollinger Bands - Volatility indicator with upper and lower bands around a moving average. Price touching bands may indicate overbought/oversold conditions.',
|
||||
svg: null
|
||||
},
|
||||
'BOL%B': {
|
||||
description: 'Bollinger %B - Shows where price is relative to the Bollinger Bands. Values above 1 = above upper band, below 0 = below lower band.',
|
||||
svg: null
|
||||
},
|
||||
'ATR': {
|
||||
description: 'Average True Range - Measures market volatility by calculating the average range between high and low prices over a period.',
|
||||
svg: null
|
||||
},
|
||||
'LREG': {
|
||||
description: 'Linear Regression - Projects future prices based on a linear trend line fitted to recent price data.',
|
||||
svg: null
|
||||
},
|
||||
'Volume': {
|
||||
description: 'Trading Volume - Shows the number of units traded. High volume confirms price moves, low volume suggests weak conviction.',
|
||||
svg: null
|
||||
},
|
||||
|
||||
// Candlestick patterns - Single candle
|
||||
'CDL_DOJI': {
|
||||
description: 'Doji - Open and close are nearly equal, forming a cross shape. Indicates market indecision and potential reversal.',
|
||||
svg: `<svg width="60" height="80" viewBox="0 0 60 80">
|
||||
<line x1="30" y1="10" x2="30" y2="70" stroke="#333" stroke-width="2"/>
|
||||
<line x1="20" y1="40" x2="40" y2="40" stroke="#333" stroke-width="3"/>
|
||||
</svg>`
|
||||
},
|
||||
'CDL_HAMMER': {
|
||||
description: 'Hammer - Small body at top with long lower shadow. Bullish reversal signal appearing after a downtrend.',
|
||||
svg: `<svg width="60" height="80" viewBox="0 0 60 80">
|
||||
<line x1="30" y1="15" x2="30" y2="70" stroke="#333" stroke-width="2"/>
|
||||
<rect x="22" y="15" width="16" height="12" fill="#00C853" stroke="#333"/>
|
||||
</svg>`
|
||||
},
|
||||
'CDL_INVERTEDHAMMER': {
|
||||
description: 'Inverted Hammer - Small body at bottom with long upper shadow. Potential bullish reversal after downtrend.',
|
||||
svg: `<svg width="60" height="80" viewBox="0 0 60 80">
|
||||
<line x1="30" y1="10" x2="30" y2="65" stroke="#333" stroke-width="2"/>
|
||||
<rect x="22" y="53" width="16" height="12" fill="#00C853" stroke="#333"/>
|
||||
</svg>`
|
||||
},
|
||||
'CDL_SHOOTINGSTAR': {
|
||||
description: 'Shooting Star - Small body at bottom with long upper shadow. Bearish reversal signal after an uptrend.',
|
||||
svg: `<svg width="60" height="80" viewBox="0 0 60 80">
|
||||
<line x1="30" y1="10" x2="30" y2="65" stroke="#333" stroke-width="2"/>
|
||||
<rect x="22" y="53" width="16" height="12" fill="#FF5252" stroke="#333"/>
|
||||
</svg>`
|
||||
},
|
||||
'CDL_SPINNINGTOP': {
|
||||
description: 'Spinning Top - Small body with upper and lower shadows. Indicates indecision between buyers and sellers.',
|
||||
svg: `<svg width="60" height="80" viewBox="0 0 60 80">
|
||||
<line x1="30" y1="10" x2="30" y2="70" stroke="#333" stroke-width="2"/>
|
||||
<rect x="22" y="32" width="16" height="16" fill="#888" stroke="#333"/>
|
||||
</svg>`
|
||||
},
|
||||
'CDL_MARUBOZU': {
|
||||
description: 'Marubozu - Long body with no shadows. Strong bullish (white) or bearish (black) conviction.',
|
||||
svg: `<svg width="60" height="80" viewBox="0 0 60 80">
|
||||
<rect x="20" y="10" width="20" height="60" fill="#00C853" stroke="#333" stroke-width="2"/>
|
||||
</svg>`
|
||||
},
|
||||
|
||||
// Candlestick patterns - Two candle
|
||||
'CDL_ENGULFING': {
|
||||
description: 'Engulfing Pattern - Second candle completely engulfs the first. Bullish engulfing after downtrend, bearish after uptrend.',
|
||||
svg: `<svg width="80" height="80" viewBox="0 0 80 80">
|
||||
<rect x="12" y="30" width="14" height="25" fill="#FF5252" stroke="#333"/>
|
||||
<rect x="38" y="20" width="20" height="45" fill="#00C853" stroke="#333" stroke-width="2"/>
|
||||
</svg>`
|
||||
},
|
||||
'CDL_HARAMI': {
|
||||
description: 'Harami - Second candle is contained within the first. Suggests potential reversal, especially with confirmation.',
|
||||
svg: `<svg width="80" height="80" viewBox="0 0 80 80">
|
||||
<rect x="10" y="15" width="20" height="50" fill="#FF5252" stroke="#333" stroke-width="2"/>
|
||||
<rect x="42" y="30" width="12" height="20" fill="#00C853" stroke="#333"/>
|
||||
</svg>`
|
||||
},
|
||||
'CDL_PIERCING': {
|
||||
description: 'Piercing Line - Bullish pattern where second candle opens below prior low and closes above midpoint of first candle.',
|
||||
svg: `<svg width="80" height="80" viewBox="0 0 80 80">
|
||||
<rect x="12" y="15" width="16" height="40" fill="#FF5252" stroke="#333"/>
|
||||
<line x1="20" y1="55" x2="20" y2="65" stroke="#333" stroke-width="2"/>
|
||||
<rect x="42" y="25" width="16" height="40" fill="#00C853" stroke="#333"/>
|
||||
<line x1="50" y1="65" x2="50" y2="72" stroke="#333" stroke-width="2"/>
|
||||
</svg>`
|
||||
},
|
||||
'CDL_DARKCLOUDCOVER': {
|
||||
description: 'Dark Cloud Cover - Bearish pattern where second candle opens above prior high and closes below midpoint of first candle.',
|
||||
svg: `<svg width="80" height="80" viewBox="0 0 80 80">
|
||||
<rect x="12" y="25" width="16" height="40" fill="#00C853" stroke="#333"/>
|
||||
<line x1="20" y1="15" x2="20" y2="25" stroke="#333" stroke-width="2"/>
|
||||
<rect x="42" y="15" width="16" height="40" fill="#FF5252" stroke="#333"/>
|
||||
<line x1="50" y1="55" x2="50" y2="65" stroke="#333" stroke-width="2"/>
|
||||
</svg>`
|
||||
},
|
||||
|
||||
// Candlestick patterns - Three+ candle
|
||||
'CDL_MORNINGSTAR': {
|
||||
description: 'Morning Star - Three-candle bullish reversal: large bearish, small body (star), large bullish. Strong reversal signal.',
|
||||
svg: `<svg width="100" height="80" viewBox="0 0 100 80">
|
||||
<rect x="8" y="15" width="14" height="40" fill="#FF5252" stroke="#333"/>
|
||||
<rect x="36" y="50" width="10" height="8" fill="#888" stroke="#333"/>
|
||||
<line x1="41" y1="45" x2="41" y2="50" stroke="#333" stroke-width="2"/>
|
||||
<line x1="41" y1="58" x2="41" y2="65" stroke="#333" stroke-width="2"/>
|
||||
<rect x="60" y="20" width="14" height="40" fill="#00C853" stroke="#333"/>
|
||||
</svg>`
|
||||
},
|
||||
'CDL_EVENINGSTAR': {
|
||||
description: 'Evening Star - Three-candle bearish reversal: large bullish, small body (star), large bearish. Strong reversal signal.',
|
||||
svg: `<svg width="100" height="80" viewBox="0 0 100 80">
|
||||
<rect x="8" y="25" width="14" height="40" fill="#00C853" stroke="#333"/>
|
||||
<rect x="36" y="12" width="10" height="8" fill="#888" stroke="#333"/>
|
||||
<line x1="41" y1="8" x2="41" y2="12" stroke="#333" stroke-width="2"/>
|
||||
<line x1="41" y1="20" x2="41" y2="28" stroke="#333" stroke-width="2"/>
|
||||
<rect x="60" y="15" width="14" height="40" fill="#FF5252" stroke="#333"/>
|
||||
</svg>`
|
||||
},
|
||||
'CDL_3WHITESOLDIERS': {
|
||||
description: 'Three White Soldiers - Three consecutive long bullish candles. Very strong bullish signal, but rare pattern.',
|
||||
svg: `<svg width="100" height="80" viewBox="0 0 100 80">
|
||||
<rect x="8" y="45" width="14" height="25" fill="#00C853" stroke="#333"/>
|
||||
<rect x="32" y="30" width="14" height="25" fill="#00C853" stroke="#333"/>
|
||||
<rect x="56" y="15" width="14" height="25" fill="#00C853" stroke="#333"/>
|
||||
</svg>`
|
||||
},
|
||||
'CDL_3BLACKCROWS': {
|
||||
description: 'Three Black Crows - Three consecutive long bearish candles. Very strong bearish signal, but rare pattern.',
|
||||
svg: `<svg width="100" height="80" viewBox="0 0 100 80">
|
||||
<rect x="8" y="10" width="14" height="25" fill="#FF5252" stroke="#333"/>
|
||||
<rect x="32" y="25" width="14" height="25" fill="#FF5252" stroke="#333"/>
|
||||
<rect x="56" y="40" width="14" height="25" fill="#FF5252" stroke="#333"/>
|
||||
</svg>`
|
||||
},
|
||||
'CDL_3INSIDE': {
|
||||
description: 'Three Inside Up/Down - Three-candle reversal pattern. Harami followed by confirmation candle in reversal direction.',
|
||||
svg: `<svg width="100" height="80" viewBox="0 0 100 80">
|
||||
<rect x="8" y="20" width="16" height="40" fill="#FF5252" stroke="#333"/>
|
||||
<rect x="34" y="30" width="10" height="15" fill="#00C853" stroke="#333"/>
|
||||
<rect x="56" y="15" width="14" height="35" fill="#00C853" stroke="#333"/>
|
||||
</svg>`
|
||||
}
|
||||
};
|
||||
|
||||
function showIndicatorDropdown() {
|
||||
document.getElementById('indicator_dropdown').style.display = 'block';
|
||||
// Don't show tooltip until hovering over an option
|
||||
}
|
||||
|
||||
function hideIndicatorDropdown() {
|
||||
setTimeout(() => {
|
||||
document.getElementById('indicator_dropdown').style.display = 'none';
|
||||
document.getElementById('indicator_tooltip').style.display = 'none';
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function filterIndicatorTypes() {
|
||||
const search = document.getElementById('newi_type_search').value.toLowerCase();
|
||||
const options = document.querySelectorAll('.indicator-option');
|
||||
options.forEach(opt => {
|
||||
const text = opt.textContent.toLowerCase();
|
||||
opt.style.display = text.includes(search) ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function selectIndicatorType(type) {
|
||||
document.getElementById('newi_type').value = type;
|
||||
document.getElementById('newi_type_search').value = type;
|
||||
document.getElementById('indicator_dropdown').style.display = 'none';
|
||||
document.getElementById('indicator_tooltip').style.display = 'none';
|
||||
}
|
||||
|
||||
function showIndicatorTooltip(type) {
|
||||
const info = indicatorInfo[type];
|
||||
const tooltip = document.getElementById('indicator_tooltip');
|
||||
const title = document.getElementById('tooltip_title');
|
||||
const desc = document.getElementById('tooltip_description');
|
||||
const svg = document.getElementById('tooltip_svg');
|
||||
const dropdown = document.getElementById('indicator_dropdown');
|
||||
|
||||
if (info) {
|
||||
title.textContent = type;
|
||||
desc.textContent = info.description;
|
||||
svg.innerHTML = info.svg || '';
|
||||
} else {
|
||||
title.textContent = type;
|
||||
desc.textContent = 'No description available.';
|
||||
svg.innerHTML = '';
|
||||
}
|
||||
|
||||
// Get dropdown position and place tooltip to the right of it
|
||||
const dropdownRect = dropdown.getBoundingClientRect();
|
||||
|
||||
// Position tooltip to the right of the dropdown
|
||||
tooltip.style.display = 'block';
|
||||
tooltip.style.position = 'fixed';
|
||||
tooltip.style.left = (dropdownRect.right + 10) + 'px';
|
||||
tooltip.style.top = dropdownRect.top + 'px';
|
||||
|
||||
// If tooltip would go off right edge of screen, put it to the left instead
|
||||
const tooltipWidth = 280; // matches CSS width
|
||||
if (dropdownRect.right + 10 + tooltipWidth > window.innerWidth) {
|
||||
tooltip.style.left = (dropdownRect.left - tooltipWidth - 10) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
const wrapper = document.querySelector('.indicator-type-wrapper');
|
||||
if (wrapper && !wrapper.contains(e.target)) {
|
||||
document.getElementById('indicator_dropdown').style.display = 'none';
|
||||
document.getElementById('indicator_tooltip').style.display = 'none';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue