feat: Implement HTS (Higher Timeframe Trend System) strategy
- Add HTS strategy engine with crossover and alignment-based entries - Implement Auto HTS feature (computes on TF/4 from 1m data) - Add 1H Red Zone filter to validate long signals - Add channel-based stop loss with RTL functionality - Enhanced visualization with 30% opacity channel lines - Fixed data alignment in simulation (uses htsData instead of mismatched indices) - Fixed syntax errors in hts-engine.js (malformed template literals) - Fixed duplicate code in simulation.js - Added showSimulationMarkers to window object for global access - Enhanced logging for trade signals and simulation results - Fix missing prevFastHigh/currFastHigh in hts-visualizer.js - Disable trend zone overlays to prevent chart clutter - Implement client-side visualization for trade markers using line series - MA strategy indicator configuration fixed (was empty during engine run) - Made entry conditions more permissive for shorter timeframes - Added comprehensive error handling and console logging
This commit is contained in:
233
src/api/dashboard/static/js/ui/hts-visualizer.js
Normal file
233
src/api/dashboard/static/js/ui/hts-visualizer.js
Normal file
@ -0,0 +1,233 @@
|
||||
import { HTSStrategyEngine } from '../strategies/hts-engine.js';
|
||||
|
||||
const HTS_COLORS = {
|
||||
fastHigh: '#00bcd4',
|
||||
fastLow: '#00bcd4',
|
||||
slowHigh: '#f44336',
|
||||
slowLow: '#f44336',
|
||||
bullishZone: 'rgba(38, 166, 154, 0.1)',
|
||||
bearishZone: 'rgba(239, 83, 80, 0.1)',
|
||||
channelRegion: 'rgba(41, 98, 255, 0.05)'
|
||||
};
|
||||
|
||||
let HTSOverlays = [];
|
||||
|
||||
export class HTSVisualizer {
|
||||
constructor(chart, candles) {
|
||||
this.chart = chart;
|
||||
this.candles = candles;
|
||||
this.overlays = [];
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.overlays.forEach(overlay => {
|
||||
try {
|
||||
this.chart.removeSeries(overlay.series);
|
||||
} catch (e) { }
|
||||
});
|
||||
this.overlays = [];
|
||||
}
|
||||
|
||||
addHTSChannels(htsData, isAutoHTS = false) {
|
||||
this.clear();
|
||||
|
||||
if (!htsData || htsData.length === 0) return;
|
||||
|
||||
const alpha = isAutoHTS ? 0.3 : 0.3;
|
||||
const lineWidth = isAutoHTS ? 1 : 2;
|
||||
|
||||
const fastHighSeries = this.chart.addSeries(LightweightCharts.LineSeries, {
|
||||
color: `rgba(0, 188, 212, ${alpha})`,
|
||||
lineWidth: lineWidth,
|
||||
lastValueVisible: false,
|
||||
title: 'HTS Fast High' + (isAutoHTS ? ' (Auto)' : ''),
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: false
|
||||
});
|
||||
|
||||
const fastLowSeries = this.chart.addSeries(LightweightCharts.LineSeries, {
|
||||
color: `rgba(0, 188, 212, ${alpha})`,
|
||||
lineWidth: lineWidth,
|
||||
lastValueVisible: false,
|
||||
title: 'HTS Fast Low' + (isAutoHTS ? ' (Auto)' : ''),
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: false
|
||||
});
|
||||
|
||||
const slowHighSeries = this.chart.addSeries(LightweightCharts.LineSeries, {
|
||||
color: `rgba(244, 67, 54, ${alpha})`,
|
||||
lineWidth: lineWidth + 1,
|
||||
lastValueVisible: false,
|
||||
title: 'HTS Slow High' + (isAutoHTS ? ' (Auto)' : ''),
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: false
|
||||
});
|
||||
|
||||
const slowLowSeries = this.chart.addSeries(LightweightCharts.LineSeries, {
|
||||
color: `rgba(244, 67, 54, ${alpha})`,
|
||||
lineWidth: lineWidth + 1,
|
||||
lastValueVisible: false,
|
||||
title: 'HTS Slow Low' + (isAutoHTS ? ' (Auto)' : ''),
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: false
|
||||
});
|
||||
|
||||
const fastHighData = htsData.map(h => ({ time: h.time, value: h.fastHigh }));
|
||||
const fastLowData = htsData.map(h => ({ time: h.time, value: h.fastLow }));
|
||||
const slowHighData = htsData.map(h => ({ time: h.time, value: h.slowHigh }));
|
||||
const slowLowData = htsData.map(h => ({ time: h.time, value: h.slowLow }));
|
||||
|
||||
fastHighSeries.setData(fastHighData);
|
||||
fastLowSeries.setData(fastLowData);
|
||||
slowHighSeries.setData(slowHighData);
|
||||
slowLowSeries.setData(slowLowData);
|
||||
|
||||
this.overlays.push(
|
||||
{ series: fastHighSeries, name: 'fastHigh' },
|
||||
{ series: fastLowSeries, name: 'fastLow' },
|
||||
{ series: slowHighSeries, name: 'slowHigh' },
|
||||
{ series: slowLowSeries, name: 'slowLow' }
|
||||
);
|
||||
|
||||
return {
|
||||
fastHigh: fastHighSeries,
|
||||
fastLow: fastLowSeries,
|
||||
slowHigh: slowHighSeries,
|
||||
slowLow: slowLowSeries
|
||||
};
|
||||
}
|
||||
|
||||
addTrendZones(htsData) {
|
||||
if (!htsData || htsData.length < 2) return;
|
||||
|
||||
const trendZones = [];
|
||||
let currentZone = null;
|
||||
|
||||
for (let i = 1; i < htsData.length; i++) {
|
||||
const prev = htsData[i - 1];
|
||||
const curr = htsData[i];
|
||||
|
||||
const prevBullish = prev.fastLow > prev.slowLow && prev.fastHigh > prev.slowHigh;
|
||||
const currBullish = curr.fastLow > curr.slowLow && curr.fastHigh > curr.slowHigh;
|
||||
|
||||
const prevBearish = prev.fastLow < prev.slowLow && prev.fastHigh < prev.slowHigh;
|
||||
const currBearish = curr.fastLow < curr.slowLow && curr.fastHigh < curr.slowHigh;
|
||||
|
||||
if (currBullish && !prevBullish) {
|
||||
currentZone = { type: 'bullish', start: curr.time };
|
||||
} else if (currBearish && !prevBearish) {
|
||||
currentZone = { type: 'bearish', start: curr.time };
|
||||
} else if (!currBullish && !currBearish && currentZone) {
|
||||
currentZone.end = prev.time;
|
||||
trendZones.push({ ...currentZone });
|
||||
currentZone = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentZone) {
|
||||
currentZone.end = htsData[htsData.length - 1].time;
|
||||
trendZones.push(currentZone);
|
||||
}
|
||||
|
||||
trendZones.forEach(zone => {
|
||||
const zoneSeries = this.chart.addSeries(LightweightCharts.AreaSeries, {
|
||||
topColor: zone.type === 'bullish' ? 'rgba(38, 166, 154, 0.02)' : 'rgba(239, 83, 80, 0.02)',
|
||||
bottomColor: zone.type === 'bullish' ? 'rgba(38, 166, 154, 0.02)' : 'rgba(239, 83, 80, 0.02)',
|
||||
lineColor: 'transparent',
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
});
|
||||
|
||||
if (this.candles && this.candles.length > 0) {
|
||||
const maxPrice = Math.max(...this.candles.map(c => c.high)) * 2;
|
||||
const minPrice = Math.min(...this.candles.map(c => c.low)) * 0.5;
|
||||
|
||||
const startTime = zone.start || (this.candles[0]?.time);
|
||||
const endTime = zone.end || (this.candles[this.candles.length - 1]?.time);
|
||||
|
||||
zoneSeries.setData([
|
||||
{ time: startTime, value: minPrice },
|
||||
{ time: startTime, value: maxPrice },
|
||||
{ time: endTime, value: maxPrice },
|
||||
{ time: endTime, value: minPrice }
|
||||
]);
|
||||
}
|
||||
|
||||
this.overlays.push({ series: zoneSeries, name: `trendZone_${zone.type}_${zone.start}` });
|
||||
});
|
||||
}
|
||||
|
||||
addCrossoverMarkers(htsData) {
|
||||
if (!htsData || htsData.length < 2) return;
|
||||
|
||||
const markers = [];
|
||||
|
||||
for (let i = 1; i < htsData.length; i++) {
|
||||
const prev = htsData[i - 1];
|
||||
const curr = htsData[i];
|
||||
|
||||
if (!prev || !curr) continue;
|
||||
|
||||
const price = curr.price;
|
||||
|
||||
const prevFastLow = prev.fastLow;
|
||||
const currFastLow = curr.fastLow;
|
||||
const prevFastHigh = prev.fastHigh;
|
||||
const currFastHigh = curr.fastHigh;
|
||||
const prevSlowLow = prev.slowLow;
|
||||
const currSlowLow = curr.slowLow;
|
||||
const prevSlowHigh = prev.slowHigh;
|
||||
const currSlowHigh = curr.slowHigh;
|
||||
|
||||
if (prevFastLow <= prevSlowLow && currFastLow > currSlowLow && price > currSlowLow) {
|
||||
markers.push({
|
||||
time: curr.time,
|
||||
position: 'belowBar',
|
||||
color: '#26a69a',
|
||||
shape: 'arrowUp',
|
||||
text: 'BUY',
|
||||
size: 1.2
|
||||
});
|
||||
}
|
||||
|
||||
if (prevFastHigh >= prevSlowHigh && currFastHigh < currSlowHigh && price < currSlowHigh) {
|
||||
markers.push({
|
||||
time: curr.time,
|
||||
position: 'aboveBar',
|
||||
color: '#ef5350',
|
||||
shape: 'arrowDown',
|
||||
text: 'SELL',
|
||||
size: 1.2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const candleSeries = this.candleData?.series;
|
||||
if (candleSeries && typeof candleSeries.setMarkers === 'function') {
|
||||
candleSeries.setMarkers(markers);
|
||||
}
|
||||
|
||||
return markers;
|
||||
}
|
||||
}
|
||||
|
||||
export function addHTSVisualization(chart, candleSeries, htsData, candles, isAutoHTS = false) {
|
||||
const visualizer = new HTSVisualizer(chart, candles);
|
||||
visualizer.candleData = { series: candleSeries };
|
||||
visualizer.addHTSChannels(htsData, isAutoHTS);
|
||||
|
||||
// Disable trend zones to avoid visual clutter
|
||||
// visualizer.addTrendZones(htsData);
|
||||
|
||||
if (window.showCrossoverMarkers !== false) {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
visualizer.addCrossoverMarkers(htsData);
|
||||
} catch (e) {
|
||||
console.warn('Crossover markers skipped (API limitation):', e.message);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
return visualizer;
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
import { ClientStrategyEngine } from '../strategies/index.js';
|
||||
import { HTSStrategyEngine } from '../strategies/hts-engine.js';
|
||||
import { addHTSVisualization } from './hts-visualizer.js';
|
||||
import { SimulationStorage } from './storage.js';
|
||||
import { downloadFile } from '../utils/index.js';
|
||||
import { showExportDialog, closeExportDialog, performExport } from './export.js';
|
||||
@ -94,40 +96,92 @@ export async function runSimulation() {
|
||||
timeframes: { primary: interval, secondary: secondaryTF ? [secondaryTF] : [] },
|
||||
indicators: []
|
||||
};
|
||||
|
||||
|
||||
console.log('Building strategy config:');
|
||||
console.log(' Primary TF:', interval);
|
||||
console.log(' Secondary TF:', secondaryTF);
|
||||
console.log(' Available candles:', Object.keys(candlesMap));
|
||||
|
||||
if (strategyConfig.id === 'ma_trend') {
|
||||
const period = strategyConfig.params?.period || 44;
|
||||
engineConfig.indicators.push({
|
||||
name: `ma${period}`,
|
||||
type: 'sma',
|
||||
params: { period: period },
|
||||
timeframe: interval
|
||||
});
|
||||
if (secondaryTF) {
|
||||
|
||||
let results;
|
||||
|
||||
if (strategyConfig.id === 'hts_trend') {
|
||||
console.log('Running HTS strategy simulation...');
|
||||
const htsEngine = new HTSStrategyEngine(strategyConfig.params);
|
||||
|
||||
let oneMinCandles = null;
|
||||
let h1hCandles = null;
|
||||
|
||||
if (strategyConfig.params?.useAutoHTS) {
|
||||
console.log('Fetching 1m candles for Auto HTS...');
|
||||
const oneMinQuery = `symbol=BTC&interval=1m&start=${fetchStart.toISOString()}&limit=10000`;
|
||||
const oneMinResponse = await fetch(`/api/v1/candles?${oneMinQuery}`);
|
||||
if (oneMinResponse.ok) {
|
||||
const oneMinData = await oneMinResponse.json();
|
||||
if (oneMinData.candles && oneMinData.candles.length > 0) {
|
||||
oneMinCandles = oneMinData.candles.map(c => ({
|
||||
time: Math.floor(new Date(c.time).getTime() / 1000),
|
||||
open: parseFloat(c.open),
|
||||
high: parseFloat(c.high),
|
||||
low: parseFloat(c.low),
|
||||
close: parseFloat(c.close),
|
||||
volume: parseFloat(c.volume)
|
||||
}));
|
||||
console.log(`Got ${oneMinCandles.length} 1m candles`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strategyConfig.params?.use1HFilter) {
|
||||
console.log('Fetching 1H candles for HTS 1H filter...');
|
||||
const h1hQuery = `symbol=BTC&interval=1h&start=${fetchStart.toISOString()}&limit=500`;
|
||||
const h1hResponse = await fetch(`/api/v1/candles?${h1hQuery}`);
|
||||
if (h1hResponse.ok) {
|
||||
const h1hData = await h1hResponse.json();
|
||||
if (h1hData.candles && h1hData.candles.length > 0) {
|
||||
h1hCandles = h1hData.candles.map(c => ({
|
||||
time: Math.floor(new Date(c.time).getTime() / 1000),
|
||||
open: parseFloat(c.open),
|
||||
high: parseFloat(c.high),
|
||||
low: parseFloat(c.low),
|
||||
close: parseFloat(c.close),
|
||||
volume: parseFloat(c.volume)
|
||||
}));
|
||||
console.log(`Got ${h1hCandles.length} 1H candles`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results = htsEngine.runSimulation(candlesMap[interval], oneMinCandles, h1hCandles);
|
||||
} else {
|
||||
if (strategyConfig.id === 'ma_trend') {
|
||||
const period = strategyConfig.params?.period || 44;
|
||||
engineConfig.indicators.push({
|
||||
name: `ma${period}_${secondaryTF}`,
|
||||
name: `ma${period}`,
|
||||
type: 'sma',
|
||||
params: { period: period },
|
||||
timeframe: secondaryTF
|
||||
timeframe: interval
|
||||
});
|
||||
if (secondaryTF) {
|
||||
engineConfig.indicators.push({
|
||||
name: `ma${period}_${secondaryTF}`,
|
||||
type: 'sma',
|
||||
params: { period: period },
|
||||
timeframe: secondaryTF
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(' Indicators configured:', engineConfig.indicators.map(i => `${i.name} on ${i.timeframe}`));
|
||||
|
||||
const riskConfig = {
|
||||
positionSizing: { method: 'percent', value: riskPercent },
|
||||
stopLoss: { enabled: true, method: 'percent', value: stopLossPercent }
|
||||
};
|
||||
|
||||
const engine = new ClientStrategyEngine();
|
||||
results = engine.run(candlesMap, engineConfig, riskConfig, start);
|
||||
}
|
||||
|
||||
console.log(' Indicators configured:', engineConfig.indicators.map(i => `${i.name} on ${i.timeframe}`));
|
||||
|
||||
const riskConfig = {
|
||||
positionSizing: { method: 'percent', value: riskPercent },
|
||||
stopLoss: { enabled: true, method: 'percent', value: stopLossPercent }
|
||||
};
|
||||
|
||||
const engine = new ClientStrategyEngine();
|
||||
const results = engine.run(candlesMap, engineConfig, riskConfig, start);
|
||||
|
||||
|
||||
if (results.error) throw new Error(results.error);
|
||||
|
||||
setLastResults({
|
||||
@ -163,7 +217,24 @@ export async function runSimulation() {
|
||||
}
|
||||
|
||||
showSimulationMarkers();
|
||||
|
||||
|
||||
if (strategyConfig.id === 'hts_trend' && results.htsData && window.dashboard) {
|
||||
try {
|
||||
console.log('Visualizing HTS channels...');
|
||||
const candles = window.dashboard.allData.get(interval) || candlesMap[interval];
|
||||
htsVisualizer = addHTSVisualization(
|
||||
window.dashboard.chart,
|
||||
window.dashboard.candleSeries,
|
||||
results.htsData,
|
||||
candles,
|
||||
strategyConfig.params?.useAutoHTS
|
||||
);
|
||||
console.log('HTS channels visualized');
|
||||
} catch (e) {
|
||||
console.error('Error visualizing HTS channels:', e);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Simulation error:', error);
|
||||
alert('Simulation error: ' + error.message);
|
||||
@ -244,75 +315,158 @@ function drawEquitySparkline(results) {
|
||||
}
|
||||
|
||||
let tradeLineSeries = [];
|
||||
let htsVisualizer = null;
|
||||
let tradeMarkerSeries = [];
|
||||
|
||||
export function showSimulationMarkers() {
|
||||
const results = getLastResults();
|
||||
if (!results || !window.dashboard) return;
|
||||
|
||||
|
||||
if (!results || !window.dashboard) {
|
||||
console.warn('Cannot show markers: no results or dashboard');
|
||||
return;
|
||||
}
|
||||
|
||||
const trades = results.trades || results.results?.trades || [];
|
||||
const markers = [];
|
||||
|
||||
|
||||
clearSimulationMarkers();
|
||||
|
||||
|
||||
console.log('Plotting trades:', trades.length);
|
||||
|
||||
trades.forEach((trade, i) => {
|
||||
console.log('Dashboard check:', {
|
||||
dashboard: !!window.dashboard,
|
||||
chart: !!window.dashboard?.chart,
|
||||
candleSeries: !!window.dashboard?.candleSeries,
|
||||
setMarkers: typeof window.dashboard?.candleSeries?.setMarkers
|
||||
});
|
||||
|
||||
trades.forEach((trade, i) => {
|
||||
let entryTime, exitTime;
|
||||
|
||||
|
||||
if (typeof trade.entryTime === 'number') {
|
||||
entryTime = trade.entryTime;
|
||||
} else {
|
||||
entryTime = Math.floor(new Date(trade.entryTime).getTime() / 1000);
|
||||
}
|
||||
|
||||
|
||||
if (typeof trade.exitTime === 'number') {
|
||||
exitTime = trade.exitTime;
|
||||
} else {
|
||||
exitTime = Math.floor(new Date(trade.exitTime).getTime() / 1000);
|
||||
}
|
||||
|
||||
const pnlSymbol = trade.pnl > 0 ? '+' : '';
|
||||
|
||||
markers.push({
|
||||
time: entryTime,
|
||||
position: 'belowBar',
|
||||
color: '#2196f3',
|
||||
shape: 'arrowUp',
|
||||
text: 'BUY',
|
||||
size: 1
|
||||
});
|
||||
|
||||
markers.push({
|
||||
time: exitTime,
|
||||
position: 'aboveBar',
|
||||
color: trade.pnl > 0 ? '#4caf50' : '#f44336',
|
||||
shape: 'arrowDown',
|
||||
text: `SELL ${pnlSymbol}${trade.pnlPct.toFixed(1)}%`,
|
||||
size: 1
|
||||
});
|
||||
|
||||
const lineSeries = window.dashboard.chart.addSeries(LightweightCharts.LineSeries, {
|
||||
color: '#2196f3',
|
||||
lineWidth: 1,
|
||||
lastValueVisible: false,
|
||||
title: '',
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: false
|
||||
}, 0);
|
||||
|
||||
lineSeries.setData([
|
||||
{ time: entryTime, value: trade.entryPrice },
|
||||
{ time: exitTime, value: trade.exitPrice }
|
||||
]);
|
||||
|
||||
tradeLineSeries.push(lineSeries);
|
||||
|
||||
console.log(`[Trade ${i}] Entry: ${new Date(entryTime * 1000).toISOString()}, Exit: ${new Date(exitTime * 1000).toISOString()}`);
|
||||
console.log(` Entry Price: $${trade.entryPrice.toFixed(2)} | Exit Price: $${trade.exitPrice.toFixed(2)} | PnL: ${trade.pnl > 0 ? '+' : ''}$${trade.pnl.toFixed(2)} (${trade.pnlPct.toFixed(1)}%)`);
|
||||
|
||||
if (window.dashboard && window.dashboard.chart && window.dashboard.candleSeries) {
|
||||
try {
|
||||
const data = window.dashboard.candleSeries.data();
|
||||
if (!data || data.length === 0) {
|
||||
console.warn('No chart data available for markers');
|
||||
return;
|
||||
}
|
||||
|
||||
const chartTimeRange = {
|
||||
min: Math.min(...data.map(c => c.time)),
|
||||
max: Math.max(...data.map(c => c.time))
|
||||
};
|
||||
|
||||
if (entryTime < chartTimeRange.min || entryTime > chartTimeRange.max) {
|
||||
console.log(`Skipping trade ${i} - entry time outside chart range`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (exitTime < chartTimeRange.min || exitTime > chartTimeRange.max) {
|
||||
console.log(`Skipping trade ${i} - exit time outside chart range`);
|
||||
return;
|
||||
}
|
||||
|
||||
const buyMarkerSeries = window.dashboard.chart.addSeries(LightweightCharts.LineSeries, {
|
||||
color: '#2196f3',
|
||||
lineWidth: 2,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: false
|
||||
});
|
||||
|
||||
const entryPrice = trade.entryPrice;
|
||||
buyMarkerSeries.setData([
|
||||
{ time: entryTime, value: entryPrice },
|
||||
{ time: entryTime + 1, value: entryPrice }
|
||||
]);
|
||||
|
||||
tradeMarkerSeries.push({ series: buyMarkerSeries });
|
||||
|
||||
const pnlColor = trade.pnl > 0 ? '#4caf50' : '#f44336';
|
||||
|
||||
const sellMarkerSeries = window.dashboard.chart.addSeries(LightweightCharts.LineSeries, {
|
||||
color: pnlColor,
|
||||
lineWidth: 2,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: false
|
||||
});
|
||||
|
||||
const exitPrice = trade.exitPrice;
|
||||
sellMarkerSeries.setData([
|
||||
{ time: exitTime, value: exitPrice },
|
||||
{ time: exitTime + 1, value: exitPrice }
|
||||
]);
|
||||
|
||||
tradeMarkerSeries.push({ series: sellMarkerSeries });
|
||||
|
||||
const lineSeries = window.dashboard.chart.addSeries(LightweightCharts.LineSeries, {
|
||||
color: 'rgba(33, 150, 243, 0.3)',
|
||||
lineWidth: 1,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: false
|
||||
});
|
||||
|
||||
lineSeries.setData([
|
||||
{ time: entryTime, value: trade.entryPrice },
|
||||
{ time: exitTime, value: trade.exitPrice }
|
||||
]);
|
||||
|
||||
tradeLineSeries.push(lineSeries);
|
||||
} catch (e) {
|
||||
console.error(`Error adding marker for trade ${i}:`, e.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
markers.sort((a, b) => a.time - b.time);
|
||||
|
||||
window.dashboard.candleSeries.setMarkers(markers);
|
||||
|
||||
|
||||
const candleSeries = window.dashboard.candleSeries;
|
||||
if (candleSeries && typeof candleSeries.setMarkers === 'function') {
|
||||
try {
|
||||
console.log('Setting', markers.length, 'markers on candle series');
|
||||
|
||||
const currentData = candleSeries.data();
|
||||
const markerTimes = new Set(markers.map(m => m.time));
|
||||
const candleTimes = new Set(currentData.map(c => c.time));
|
||||
|
||||
const orphanMarkers = markers.filter(m => !candleTimes.has(m.time));
|
||||
if (orphanMarkers.length > 0) {
|
||||
console.warn(`${orphanMarkers.length} markers have timestamps not found in chart data:`);
|
||||
orphanMarkers.forEach(m => {
|
||||
console.warn(' - Time:', m.time, '(', new Date(m.time * 1000).toISOString(), ')');
|
||||
});
|
||||
console.log('First few candle times:', Array.from(candleTimes).slice(0, 5));
|
||||
console.log('Last few candle times:', Array.from(candleTimes).slice(-5));
|
||||
}
|
||||
|
||||
candleSeries.setMarkers(markers);
|
||||
console.log('Markers set successfully');
|
||||
} catch (e) {
|
||||
console.error('Error setting markers:', e);
|
||||
console.error('Marker methods available:', Object.getOwnPropertyNames(Object.getPrototypeOf(candleSeries)).filter(m => m.includes('marker')));
|
||||
}
|
||||
} else {
|
||||
console.warn('Markers not supported on this chart version - trades will be logged but not visualized on chart');
|
||||
}
|
||||
|
||||
console.log(`Plotted ${trades.length} trades with connection lines`);
|
||||
console.log(`Trade markers created for ${trades.length} positions`);
|
||||
}
|
||||
|
||||
export function clearSimulationMarkers() {
|
||||
@ -323,7 +477,7 @@ export function clearSimulationMarkers() {
|
||||
} catch (e) {
|
||||
// Ignore errors clearing markers
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
tradeLineSeries.forEach(series => {
|
||||
try {
|
||||
@ -337,8 +491,33 @@ export function clearSimulationMarkers() {
|
||||
} catch (e) {
|
||||
// Ignore errors removing series
|
||||
}
|
||||
|
||||
|
||||
tradeLineSeries = [];
|
||||
|
||||
try {
|
||||
if (htsVisualizer) {
|
||||
htsVisualizer.clear();
|
||||
htsVisualizer = null;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors clearing HTS visualizer
|
||||
}
|
||||
|
||||
// Clear trade marker series
|
||||
try {
|
||||
tradeMarkerSeries.forEach(marker => {
|
||||
try {
|
||||
if (window.dashboard && window.dashboard.chart && marker.series) {
|
||||
window.dashboard.chart.removeSeries(marker.series);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
});
|
||||
tradeMarkerSeries = [];
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
export function clearSimulationResults() {
|
||||
|
||||
@ -72,26 +72,35 @@ export function renderStrategyParams(strategyId) {
|
||||
export async function loadStrategies() {
|
||||
try {
|
||||
console.log('Fetching strategies from API...');
|
||||
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||
|
||||
|
||||
const response = await fetch('/api/v1/strategies?_=' + Date.now(), {
|
||||
signal: controller.signal
|
||||
});
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
let data = await response.json();
|
||||
console.log('Strategies loaded:', data);
|
||||
|
||||
|
||||
if (!data.strategies) {
|
||||
throw new Error('Invalid response format: missing strategies array');
|
||||
}
|
||||
|
||||
|
||||
data.strategies = data.strategies.filter(s => s.id !== 'hts_trend');
|
||||
|
||||
data.strategies.push({
|
||||
id: 'hts_trend',
|
||||
name: 'HTS Trend Strategy',
|
||||
description: 'Higher Timeframe Trend System with Auto HTS, 1H filters, and channel-based entries/exits',
|
||||
required_indicators: []
|
||||
});
|
||||
|
||||
window.availableStrategies = data.strategies;
|
||||
renderStrategies(data.strategies);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user