chore: add AGENTS.md with build, lint, test commands and style guidelines

This commit is contained in:
DiTus
2026-03-18 21:17:43 +01:00
parent e98c25efc4
commit 509f8033fa
32 changed files with 10087 additions and 133 deletions

231
js/ui/signal-markers.js Normal file
View File

@ -0,0 +1,231 @@
import { IndicatorRegistry } from '../indicators/index.js';
export function calculateSignalMarkers(candles) {
const activeIndicators = window.getActiveIndicators?.() || [];
const markers = [];
if (!candles || candles.length < 2) {
return markers;
}
for (const indicator of activeIndicators) {
if (indicator.params.showMarkers === false || indicator.params.showMarkers === 'false') {
continue;
}
console.log('[SignalMarkers] Processing indicator:', indicator.type, 'showMarkers:', indicator.params.showMarkers);
// Use cache if available
let results = indicator.cachedResults;
if (!results || !Array.isArray(results) || results.length !== candles.length) {
const IndicatorClass = IndicatorRegistry[indicator.type];
if (!IndicatorClass) {
continue;
}
const instance = new IndicatorClass(indicator);
results = instance.calculate(candles);
}
if (!results || !Array.isArray(results) || results.length === 0) {
continue;
}
const indicatorMarkers = findCrossoverMarkers(indicator, candles, results);
markers.push(...indicatorMarkers);
}
markers.sort((a, b) => a.time - b.time);
return markers;
}
function findCrossoverMarkers(indicator, candles, results) {
const markers = [];
const overbought = indicator.params?.overbought || 70;
const oversold = indicator.params?.oversold || 30;
const indicatorType = indicator.type;
const buyColor = indicator.params?.markerBuyColor || '#26a69a';
const sellColor = indicator.params?.markerSellColor || '#ef5350';
const buyShape = indicator.params?.markerBuyShape || 'arrowUp';
const sellShape = indicator.params?.markerSellShape || 'arrowDown';
const buyCustom = indicator.params?.markerBuyCustom || '◭';
const sellCustom = indicator.params?.markerSellCustom || '▼';
for (let i = 1; i < results.length; i++) {
const candle = candles[i];
const prevCandle = candles[i - 1];
const result = results[i];
const prevResult = results[i - 1];
if (!result || !prevResult) continue;
if (indicatorType === 'rsi' || indicatorType === 'stoch') {
const rsi = result.rsi ?? result;
const prevRsi = prevResult.rsi ?? prevResult;
if (rsi === undefined || prevRsi === undefined) continue;
if (prevRsi > overbought && rsi <= overbought) {
markers.push({
time: candle.time,
position: 'aboveBar',
color: sellColor,
shape: sellShape === 'custom' ? '' : sellShape,
text: sellShape === 'custom' ? sellCustom : ''
});
}
if (prevRsi < oversold && rsi >= oversold) {
markers.push({
time: candle.time,
position: 'belowBar',
color: buyColor,
shape: buyShape === 'custom' ? '' : buyShape,
text: buyShape === 'custom' ? buyCustom : ''
});
}
} else if (indicatorType === 'macd') {
const macd = result.macd ?? result.MACD;
const signal = result.signal ?? result.signalLine;
const prevMacd = prevResult.macd ?? prevResult.MACD;
const prevSignal = prevResult.signal ?? prevResult.signalLine;
if (macd === undefined || signal === undefined || prevMacd === undefined || prevSignal === undefined) continue;
const macdAbovePrev = prevMacd > prevSignal;
const macdAboveNow = macd > signal;
if (macdAbovePrev && !macdAboveNow) {
markers.push({
time: candle.time,
position: 'aboveBar',
color: sellColor,
shape: sellShape === 'custom' ? '' : sellShape,
text: sellShape === 'custom' ? sellCustom : ''
});
}
if (!macdAbovePrev && macdAboveNow) {
markers.push({
time: candle.time,
position: 'belowBar',
color: buyColor,
shape: buyShape === 'custom' ? '' : buyShape,
text: buyShape === 'custom' ? buyCustom : ''
});
}
} else if (indicatorType === 'bb') {
const upper = result.upper ?? result.upperBand;
const lower = result.lower ?? result.lowerBand;
if (upper === undefined || lower === undefined) continue;
const priceAboveUpperPrev = prevCandle.close > (prevResult.upper ?? prevResult.upperBand);
const priceAboveUpperNow = candle.close > upper;
if (priceAboveUpperPrev && !priceAboveUpperNow) {
markers.push({
time: candle.time,
position: 'aboveBar',
color: sellColor,
shape: sellShape === 'custom' ? '' : sellShape,
text: sellShape === 'custom' ? sellCustom : ''
});
}
if (!priceAboveUpperPrev && priceAboveUpperNow) {
markers.push({
time: candle.time,
position: 'belowBar',
color: buyColor,
shape: buyShape === 'custom' ? '' : buyShape,
text: buyShape === 'custom' ? buyCustom : ''
});
}
const priceBelowLowerPrev = prevCandle.close < (prevResult.lower ?? prevResult.lowerBand);
const priceBelowLowerNow = candle.close < lower;
if (priceBelowLowerPrev && !priceBelowLowerNow) {
markers.push({
time: candle.time,
position: 'belowBar',
color: buyColor,
shape: buyShape === 'custom' ? '' : buyShape,
text: buyShape === 'custom' ? buyCustom : ''
});
}
if (!priceBelowLowerPrev && priceBelowLowerNow) {
markers.push({
time: candle.time,
position: 'aboveBar',
color: sellColor,
shape: sellShape === 'custom' ? '' : sellShape,
text: sellShape === 'custom' ? sellCustom : ''
});
}
} else if (indicatorType === 'hurst') {
const upper = result.upper;
const lower = result.lower;
const prevUpper = prevResult?.upper;
const prevLower = prevResult?.lower;
if (upper === undefined || lower === undefined ||
prevUpper === undefined || prevLower === undefined) continue;
// BUY: price crosses down below lower band (was above, now below)
if (prevCandle.close > prevLower && candle.close < lower) {
markers.push({
time: candle.time,
position: 'belowBar',
color: buyColor,
shape: buyShape === 'custom' ? '' : buyShape,
text: buyShape === 'custom' ? buyCustom : ''
});
}
// SELL: price crosses down below upper band (was above, now below)
if (prevCandle.close > prevUpper && candle.close < upper) {
markers.push({
time: candle.time,
position: 'aboveBar',
color: sellColor,
shape: sellShape === 'custom' ? '' : sellShape,
text: sellShape === 'custom' ? sellCustom : ''
});
}
} else {
const ma = result.ma ?? result;
const prevMa = prevResult.ma ?? prevResult;
if (ma === undefined || prevMa === undefined) continue;
const priceAbovePrev = prevCandle.close > prevMa;
const priceAboveNow = candle.close > ma;
if (priceAbovePrev && !priceAboveNow) {
markers.push({
time: candle.time,
position: 'aboveBar',
color: sellColor,
shape: sellShape === 'custom' ? '' : sellShape,
text: sellShape === 'custom' ? sellCustom : ''
});
}
if (!priceAbovePrev && priceAboveNow) {
markers.push({
time: candle.time,
position: 'belowBar',
color: buyColor,
shape: buyShape === 'custom' ? '' : buyShape,
text: buyShape === 'custom' ? buyCustom : ''
});
}
}
}
return markers;
}