diff --git a/src/api/dashboard/static/index.html b/src/api/dashboard/static/index.html
index ba65160..445cde9 100644
--- a/src/api/dashboard/static/index.html
+++ b/src/api/dashboard/static/index.html
@@ -1392,7 +1392,6 @@
@@ -1406,113 +1405,6 @@
-
-
-
diff --git a/src/api/dashboard/static/js/app.js b/src/api/dashboard/static/js/app.js
index 40c6ce4..67b64af 100644
--- a/src/api/dashboard/static/js/app.js
+++ b/src/api/dashboard/static/js/app.js
@@ -1,25 +1,5 @@
import { TradingDashboard, refreshTA, openAIAnalysis } from './ui/chart.js';
import { restoreSidebarState, toggleSidebar, initSidebarTabs, restoreSidebarTabState } from './ui/sidebar.js';
-import { SimulationStorage } from './ui/storage.js';
-import { showExportDialog, closeExportDialog, performExport, exportSavedSimulation } from './ui/export.js';
-import {
- runSimulation,
- displayEnhancedResults,
- showSimulationMarkers,
- clearSimulationResults,
- getLastResults,
- setLastResults
-} from './ui/simulation.js';
-import {
- renderStrategies,
- selectStrategy,
- loadStrategies,
- saveSimulation,
- renderSavedSimulations,
- loadSavedSimulation,
- deleteSavedSimulation,
- setCurrentStrategy
-} from './ui/strategies-panel.js';
import {
initIndicatorPanel,
getActiveIndicators,
@@ -28,42 +8,13 @@ import {
addIndicator,
removeIndicatorById
} from './ui/indicators-panel-new.js';
-import { StrategyParams } from './strategies/config.js';
import { IndicatorRegistry } from './indicators/index.js';
window.dashboard = null;
-function setDefaultStartDate() {
- const startDateInput = document.getElementById('simStartDate');
- if (startDateInput) {
- const sevenDaysAgo = new Date();
- sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
- startDateInput.value = sevenDaysAgo.toISOString().slice(0, 16);
- }
-}
-
-function updateTimeframeDisplay() {
- const display = document.getElementById('simTimeframeDisplay');
- if (display && window.dashboard) {
- display.value = window.dashboard.currentInterval.toUpperCase();
- }
-}
-
window.toggleSidebar = toggleSidebar;
window.refreshTA = refreshTA;
window.openAIAnalysis = openAIAnalysis;
-window.showExportDialog = showExportDialog;
-window.closeExportDialog = closeExportDialog;
-window.performExport = performExport;
-window.exportSavedSimulation = exportSavedSimulation;
-window.runSimulation = runSimulation;
-window.saveSimulation = saveSimulation;
-window.showSimulationMarkers = showSimulationMarkers;
-window.renderSavedSimulations = renderSavedSimulations;
-window.loadSavedSimulation = loadSavedSimulation;
-window.deleteSavedSimulation = deleteSavedSimulation;
-window.clearSimulationResults = clearSimulationResults;
-window.updateTimeframeDisplay = updateTimeframeDisplay;
window.renderIndicatorList = function() {
// This function is no longer needed for sidebar indicators
};
@@ -73,8 +24,6 @@ window.initIndicatorPanel = initIndicatorPanel;
window.addIndicator = addIndicator;
window.toggleIndicator = addIndicator;
-window.StrategyParams = StrategyParams;
-window.SimulationStorage = SimulationStorage;
window.IndicatorRegistry = IndicatorRegistry;
document.addEventListener('DOMContentLoaded', async () => {
@@ -88,11 +37,6 @@ document.addEventListener('DOMContentLoaded', async () => {
restoreSidebarState();
restoreSidebarTabState();
initSidebarTabs();
- setDefaultStartDate();
- updateTimeframeDisplay();
- renderSavedSimulations();
-
- await loadStrategies();
// Initialize indicator panel
window.initIndicatorPanel();
diff --git a/src/api/dashboard/static/js/strategies/config.js b/src/api/dashboard/static/js/strategies/config.js
deleted file mode 100644
index 0beea5b..0000000
--- a/src/api/dashboard/static/js/strategies/config.js
+++ /dev/null
@@ -1,12 +0,0 @@
-export const StrategyParams = {
- hts_trend: [
- { name: 'shortPeriod', label: 'Fast Period', type: 'number', default: 33, min: 5, max: 200 },
- { name: 'longPeriod', label: 'Slow Period', type: 'number', default: 144, min: 10, max: 500 },
- { name: 'maType', label: 'MA Type', type: 'select', options: ['RMA', 'SMA', 'EMA', 'WMA', 'VWMA'], default: 'RMA' },
- { name: 'useAutoHTS', label: 'Auto HTS (TF/4)', type: 'boolean', default: false },
- { name: 'use1HFilter', label: '1H Red Zone Filter', type: 'boolean', default: true }
- ],
- ma_trend: [
- { name: 'period', label: 'MA Period', type: 'number', default: 44, min: 5, max: 500 }
- ]
-};
diff --git a/src/api/dashboard/static/js/strategies/engine.js b/src/api/dashboard/static/js/strategies/engine.js
deleted file mode 100644
index cfa47cf..0000000
--- a/src/api/dashboard/static/js/strategies/engine.js
+++ /dev/null
@@ -1,167 +0,0 @@
-import { IndicatorRegistry } from '../indicators/index.js';
-import { RiskManager } from './risk-manager.js';
-
-export class ClientStrategyEngine {
- constructor() {
- this.indicatorTypes = IndicatorRegistry;
- }
-
- run(candlesMap, strategyConfig, riskConfig, simulationStart) {
- const primaryTF = strategyConfig.timeframes?.primary || '1d';
- const candles = candlesMap[primaryTF];
- if (!candles) return { error: `No candles for primary timeframe ${primaryTF}` };
-
- const indicatorResults = {};
- console.log('Calculating indicators for timeframes:', Object.keys(candlesMap));
- for (const tf in candlesMap) {
- indicatorResults[tf] = {};
- const tfCandles = candlesMap[tf];
- const tfIndicators = (strategyConfig.indicators || []).filter(ind => (ind.timeframe || primaryTF) === tf);
-
- console.log(` TF ${tf}: ${tfIndicators.length} indicators to calculate`);
-
- for (const ind of tfIndicators) {
- const IndicatorClass = this.indicatorTypes[ind.type];
- if (IndicatorClass) {
- const instance = new IndicatorClass(ind);
- indicatorResults[tf][ind.name] = instance.calculate(tfCandles);
- const validValues = indicatorResults[tf][ind.name].filter(v => v !== null).length;
- console.log(` Calculated ${ind.name} on ${tf}: ${validValues} valid values`);
- }
- }
- }
-
- const risk = new RiskManager(riskConfig);
- const trades = [];
- let position = null;
- const startTimeSec = Math.floor(new Date(simulationStart).getTime() / 1000);
- console.log('Simulation start (seconds):', startTimeSec, 'Date:', simulationStart);
- console.log('Total candles available:', candles.length);
- console.log('First candle time:', candles[0].time, 'Last candle time:', candles[candles.length - 1].time);
-
- const pointers = {};
- for (const tf in candlesMap) pointers[tf] = 0;
-
- let processedCandles = 0;
-
- for (let i = 1; i < candles.length; i++) {
- const time = candles[i].time;
- const price = candles[i].close;
-
- if (time < startTimeSec) {
- for (const tf in candlesMap) {
- while (pointers[tf] < candlesMap[tf].length - 1 &&
- candlesMap[tf][pointers[tf] + 1].time <= time) {
- pointers[tf]++;
- }
- }
- continue;
- }
-
- processedCandles++;
-
- for (const tf in candlesMap) {
- while (pointers[tf] < candlesMap[tf].length - 1 &&
- candlesMap[tf][pointers[tf] + 1].time <= time) {
- pointers[tf]++;
- }
- }
-
- const signal = this.evaluate(i, pointers, candles, candlesMap, indicatorResults, strategyConfig, position);
-
- if (signal === 'BUY' && !position) {
- const size = risk.calculateSize(price);
- position = { type: 'long', entryPrice: price, entryTime: candles[i].time, size };
- } else if (signal === 'SELL' && position) {
- const pnl = (price - position.entryPrice) * position.size;
- trades.push({ ...position, exitPrice: price, exitTime: candles[i].time, pnl, pnlPct: (pnl / (position.entryPrice * position.size)) * 100 });
- risk.balance += pnl;
- position = null;
- }
- }
-
- console.log(`Simulation complete: ${processedCandles} candles processed after start date, ${trades.length} trades`);
-
- return {
- total_trades: trades.length,
- win_rate: (trades.filter(t => t.pnl > 0).length / (trades.length || 1)) * 100,
- total_pnl: risk.balance - 1000,
- trades
- };
- }
-
- evaluate(index, pointers, candles, candlesMap, indicatorResults, config, position) {
- const primaryTF = config.timeframes?.primary || '1d';
-
- const getVal = (indName, tf) => {
- const tfValues = indicatorResults[tf]?.[indName];
- if (!tfValues) return null;
- return tfValues[pointers[tf]];
- };
-
- const getPrice = (tf) => {
- const tfCandles = candlesMap[tf];
- if (!tfCandles) return null;
- return tfCandles[pointers[tf]].close;
- };
-
- if (config.id === 'ma_trend') {
- const period = config.params?.period || 44;
-
- if (index === 1) {
- console.log('First candle time:', candles[index].time, 'Date:', new Date(candles[index].time * 1000));
- console.log(`MA${period} value:`, getVal(`ma${period}`, primaryTF));
- }
- const maValue = getVal(`ma${period}`, primaryTF);
- const price = candles[index].close;
-
- const secondaryTF = config.timeframes?.secondary?.[0];
- let secondaryBullish = true;
- let secondaryBearish = true;
- if (secondaryTF) {
- const secondaryPrice = getPrice(secondaryTF);
- const secondaryMA = getVal(`ma${period}_${secondaryTF}`, secondaryTF);
- if (secondaryPrice !== null && secondaryMA !== null) {
- secondaryBullish = secondaryPrice > secondaryMA;
- secondaryBearish = secondaryPrice < secondaryMA;
- }
- if (index === 1) {
- console.log(`Trend check: ${secondaryTF} price=${secondaryPrice}, MA=${secondaryMA}, bullish=${secondaryBullish}, bearish=${secondaryBearish}`);
- }
- }
-
- if (maValue) {
- if (price > maValue && secondaryBullish) return 'BUY';
- if (price < maValue && secondaryBearish) return 'SELL';
- }
- }
-
- const evaluateConditions = (conds) => {
- if (!conds || !conds.conditions) return false;
- const results = conds.conditions.map(c => {
- const targetTF = c.timeframe || primaryTF;
- const leftVal = c.indicator === 'price' ? getPrice(targetTF) : getVal(c.indicator, targetTF);
- const rightVal = typeof c.value === 'number' ? c.value : (c.value === 'price' ? getPrice(targetTF) : getVal(c.value, targetTF));
-
- if (leftVal === null || rightVal === null) return false;
-
- switch(c.operator) {
- case '>': return leftVal > rightVal;
- case '<': return leftVal < rightVal;
- case '>=': return leftVal >= rightVal;
- case '<=': return leftVal <= rightVal;
- case '==': return leftVal == rightVal;
- default: return false;
- }
- });
-
- if (conds.logic === 'OR') return results.some(r => r);
- return results.every(r => r);
- };
-
- if (evaluateConditions(config.entryLong)) return 'BUY';
- if (evaluateConditions(config.exitLong)) return 'SELL';
-
- return 'HOLD';
- }
-}
diff --git a/src/api/dashboard/static/js/strategies/hts-engine.js b/src/api/dashboard/static/js/strategies/hts-engine.js
deleted file mode 100644
index ec66191..0000000
--- a/src/api/dashboard/static/js/strategies/hts-engine.js
+++ /dev/null
@@ -1,423 +0,0 @@
-import { MA } from '../indicators/ma.js';
-
-/**
- * HTS (Higher Timeframe Trend System) Strategy Engine
- * Computes trading signals based on HTS indicator rules:
- * 1. Trend detection using fast/slow channels
- * 2. Entry rules on channel breakouts
- * 3. 1H timeframe filter (optional)
- * 4. Stop loss based on opposite channel
- */
-
-export class HTSStrategyEngine {
- constructor(config = {}) {
- this.config = {
- shortPeriod: config.shortPeriod || 33,
- longPeriod: config.longPeriod || 144,
- maType: config.maType || 'RMA',
- useAutoHTS: config.useAutoHTS || false,
- use1HFilter: config.use1HFilter !== false
- };
- }
-
- calculateHTSCandles(candles, periodMult = 1) {
- if (!candles || candles.length < this.config.longPeriod) return [];
-
- const shortPeriod = this.config.shortPeriod;
- const longPeriod = this.config.longPeriod;
- const maType = this.config.maType;
-
- const shortHigh = MA.get(maType, candles, shortPeriod, 'high');
- const shortLow = MA.get(maType, candles, shortPeriod, 'low');
- const longHigh = MA.get(maType, candles, longPeriod, 'high');
- const longLow = MA.get(maType, candles, longPeriod, 'low');
-
- const results = [];
- for (let i = 0; i < candles.length; i++) {
- if (!shortHigh[i] || !longLow[i]) continue;
-
- results.push({
- time: candles[i].time,
- fastHigh: shortHigh[i],
- fastLow: shortLow[i],
- slowHigh: longHigh[i],
- slowLow: longLow[i],
- price: candles[i].close,
- fastMidpoint: (shortHigh[i] + shortLow[i]) / 2,
- slowMidpoint: (longHigh[i] + longLow[i]) / 2
- });
- }
-
- return results;
- }
-
- computeAutoHTS(oneMinCandles, targetTF) {
- if (!oneMinCandles || oneMinCandles.length < this.config.longPeriod) return [];
-
- const tfMultipliers = {
- '5m': 5,
- '15m': 15,
- '30m': 30,
- '37m': 37,
- '1h': 60,
- '4h': 240
- };
-
- const tfGroup = tfMultipliers[targetTF] || 5;
-
- const grouped = [];
- let currentGroup = [];
- for (let i = 0; i < oneMinCandles.length; i++) {
- currentGroup.push(oneMinCandles[i]);
- if (currentGroup.length >= tfGroup) {
- grouped.push(currentGroup[tfGroup - 1]);
- currentGroup = [];
- }
- }
-
- return this.calculateHTSCandles(grouped);
- }
-
- check1HFilter(price, h1hHTS) {
- if (!this.config.use1HFilter || !h1hHTS || h1hHTS.length === 0) {
- return { confirmed: true, reasoning: '1H filter disabled or no data' };
- }
-
- const latest = h1hHTS[h1hHTS.length - 1];
- const slowLow = latest?.slowLow;
-
- if (slowLow === null || slowLow === undefined) {
- return { confirmed: true, reasoning: '1H HTS not ready' };
- }
-
- if (price < slowLow) {
- return {
- confirmed: false,
- reasoning: `1H Filter: Long rejected - price ${price.toFixed(2)} below 1H slow channel (${slowLow.toFixed(2)})`
- };
- }
-
- return {
- confirmed: true,
- reasoning: `1H Filter: Confirmed - price ${price.toFixed(2)} above 1H slow channel (${slowLow.toFixed(2)})`
- };
- }
-
- calculateEntrySignal(primaryHTS, index, position, h1hHTS = null, prevHTS = null) {
- if (!primaryHTS || index >= primaryHTS.length || index < 1) {
- return { signal: 'HOLD', confidence: 0, reasoning: 'Insufficient data' };
- }
-
- const current = primaryHTS[index];
- const currentPrice = current?.price;
-
- if (!currentPrice) {
- return { signal: 'HOLD', confidence: 0, reasoning: 'No price data' };
- }
-
- const prev = primaryHTS[index - 1];
-
- const h1hCheck = this.check1HFilter(currentPrice, h1hHTS);
- if (!h1hCheck.confirmed) {
- return {
- signal: 'HOLD',
- confidence: 0,
- reasoning: h1hCheck.reasoning,
- filterStatus: '1H_REJECTED'
- };
- }
-
- const fastLow = current?.fastLow;
- const fastHigh = current?.fastHigh;
- const slowLow = current?.slowLow;
- const slowHigh = current?.slowHigh;
-
- const prevFastLow = prev?.fastLow;
- const prevSlowLow = prev?.slowLow;
- const prevFastHigh = prev?.fastHigh;
- const prevSlowHigh = prev?.slowHigh;
-
- if (!fastLow || !slowLow) {
- return { signal: 'HOLD', confidence: 0, reasoning: 'HTS data not ready' };
- }
-
- const inPositionLong = position?.type === 'long';
- const inPositionShort = position?.type === 'short';
-
- if (inPositionLong) {
- if (currentPrice < slowLow || fastLow < slowLow) {
- return {
- signal: 'CLOSE_LONG',
- confidence: 90,
- reasoning: `Stop Loss: Price ${currentPrice.toFixed(2)} broke below slow channel (${slowLow.toFixed(2)})`,
- stopPrice: slowLow
- };
- }
- return {
- signal: 'HOLD',
- confidence: 50,
- reasoning: `Long open, holding - price ${currentPrice.toFixed(2)} above stop at ${slowLow.toFixed(2)}`,
- stopPrice: slowLow
- };
- }
-
- if (inPositionShort) {
- if (currentPrice > slowHigh || fastHigh > slowHigh) {
- return {
- signal: 'CLOSE_SHORT',
- confidence: 90,
- reasoning: `Stop Loss: Price ${currentPrice.toFixed(2)} broke above slow channel (${slowHigh.toFixed(2)})`,
- stopPrice: slowHigh
- };
- }
- return {
- signal: 'HOLD',
- confidence: 50,
- reasoning: `Short open, holding - price ${currentPrice.toFixed(2)} below stop at ${slowHigh.toFixed(2)}`,
- stopPrice: slowHigh
- };
- }
-
- if (prevFastLow <= prevSlowLow && fastLow > slowLow) {
- return {
- signal: 'BUY',
- confidence: 75,
- reasoning: `Long Entry: Fast Low (${fastLow.toFixed(2)}) crossed above Slow Low (${slowLow.toFixed(2)})`,
- entryType: 'CROSSOVER'
- };
- }
-
- if (prevFastHigh >= prevSlowHigh && fastHigh < slowHigh) {
- return {
- signal: 'SELL',
- confidence: 75,
- reasoning: `Short Entry: Fast High (${fastHigh.toFixed(2)}) crossed below Slow High (${slowHigh.toFixed(2)})`,
- entryType: 'CROSSUNDER'
- };
- }
-
- const bullAlignment = slowLow < slowHigh && fastLow > slowLow;
- const bearAlignment = slowLow < slowHigh && fastHigh < slowHigh;
-
- if (bullAlignment && currentPrice > slowLow) {
- return {
- signal: 'BUY',
- confidence: 50,
- reasoning: `Long Entry: Price ${currentPrice.toFixed(2)} above Slow Low with bullish channel alignment`,
- entryType: 'ALIGNED'
- };
- }
-
- if (bearAlignment && currentPrice < slowHigh) {
- return {
- signal: 'SELL',
- confidence: 50,
- reasoning: `Short Entry: Price ${currentPrice.toFixed(2)} below Slow High with bearish channel alignment`,
- entryType: 'ALIGNED'
- };
- }
-
- return {
- signal: 'HOLD',
- confidence: 10,
- reasoning: 'No clear signal - waiting for crossover'
- };
- }
-
- computeStopLoss(position, currentHTS, index) {
- if (!position || !currentHTS || index >= currentHTS.length) return null;
-
- const current = currentHTS[index];
- if (!current) return null;
-
- if (position.type === 'long') {
- return current.slowLow;
- }
-
- if (position.type === 'short') {
- return current.slowHigh;
- }
-
- return null;
- }
-
- calculateHTSCandles(candles, periodMult = 1) {
- if (!candles || candles.length < this.config.longPeriod) return [];
-
- const shortPeriod = this.config.shortPeriod;
- const longPeriod = this.config.longPeriod;
- const maType = this.config.maType;
-
- const shortHigh = MA.get(maType, candles, shortPeriod, 'high');
- const shortLow = MA.get(maType, candles, shortPeriod, 'low');
- const longHigh = MA.get(maType, candles, longPeriod, 'high');
- const longLow = MA.get(maType, candles, longPeriod, 'low');
-
- const results = [];
- for (let i = 0; i < candles.length; i++) {
- if (!shortHigh[i] || !longLow[i]) continue;
-
- results.push({
- time: candles[i].time,
- fastHigh: shortHigh[i],
- fastLow: shortLow[i],
- slowHigh: longHigh[i],
- slowLow: longLow[i],
- price: candles[i].close,
- fastMidpoint: ((shortHigh[i] + shortLow[i]) / 2),
- slowMidpoint: ((longHigh[i] + longLow[i]) / 2),
- open: candles[i].open,
- high: candles[i].high,
- low: candles[i].low,
- close: candles[i].close,
- volume: candles[i].volume
- });
- }
-
- return results;
- }
-
- runSimulation(candles, oneMinCandles, h1hCandles) {
- const primaryHTS = this.calculateHTSCandles(candles);
-
- console.log('[HTS] Starting simulation');
- console.log('[HTS] Primary HTS data length:', primaryHTS.length);
- console.log('[HTS] Original candles length:', candles.length);
-
- let h1hHTS = null;
- if (h1hCandles && h1hCandles.length > 0) {
- h1hHTS = this.calculateHTSCandles(h1hCandles);
- console.log('[HTS] 1H HTS data length:', h1hHTS.length);
- }
-
- let position = null;
- const trades = [];
- let balance = 1000;
- let rejectionsCount = 0;
- let buySignals = 0;
- let sellSignals = 0;
-
- for (let i = 1; i < primaryHTS.length; i++) {
- if (!primaryHTS[i]) continue;
-
- const htsData = primaryHTS[i];
- const signal = this.calculateEntrySignal(primaryHTS, i, position, h1hHTS, primaryHTS[i - 1]);
-
- if (signal.filterStatus === '1H_REJECTED') {
- rejectionsCount++;
- continue;
- }
-
- if (signal.signal === 'BUY' && !position) {
- buySignals++;
- console.log('[HTS] BUY SIGNAL:', signal.reasoning);
- position = {
- type: 'long',
- entryPrice: htsData.price,
- entryTime: htsData.time,
- size: 1,
- stopLoss: signal.stopPrice || primaryHTS[i].slowLow
- };
- console.log('[HTS] LONG entry at', htsData.price, 'stop at', position.stopLoss);
- }
-
- if (signal.signal === 'SELL' && !position) {
- sellSignals++;
- console.log('[HTS] SELL SIGNAL:', signal.reasoning);
- position = {
- type: 'short',
- entryPrice: htsData.price,
- entryTime: htsData.time,
- size: 1,
- stopLoss: signal.stopPrice || primaryHTS[i].slowHigh
- };
- console.log('[HTS] SHORT entry at', htsData.price, 'stop at', position.stopLoss);
- }
-
- if ((signal.signal === 'CLOSE_LONG' || signal.signal === 'CLOSE_SHORT') && position) {
- const pnl = position.type === 'long'
- ? (htsData.price - position.entryPrice)
- : (position.entryPrice - htsData.price);
-
- trades.push({
- ...position,
- exitPrice: htsData.price,
- exitTime: htsData.time,
- pnl,
- pnlPct: (pnl / position.entryPrice) * 100
- });
-
- balance += pnl;
- console.log('[HTS] Position closed:', signal.signal, 'PnL:', pnl.toFixed(2));
- position = null;
- }
-
- if (position) {
- const sl = this.computeStopLoss(position, primaryHTS, i);
-
- if (sl !== null) {
- const slHit = position.type === 'long' ? htsData.price < sl : htsData.price > sl;
-
- if (slHit) {
- const pnl = position.type === 'long'
- ? (sl - position.entryPrice)
- : (position.entryPrice - sl);
-
- trades.push({
- ...position,
- exitPrice: sl,
- exitTime: htsData.time,
- pnl,
- pnlPct: (pnl / position.entryPrice) * 100,
- exitReason: 'STOP_LOSS'
- });
-
- balance += pnl;
- console.log('[HTS] Stop Loss hit, PnL:', pnl.toFixed(2));
- position = null;
- }
- }
- }
- }
-
- const winRate = trades.length > 0
- ? (trades.filter(t => t.pnl > 0).length / trades.length) * 100
- : 0;
-
- console.log('[HTS] Simulation complete:');
- console.log('[HTS] - Buy signals:', buySignals);
- console.log('[HTS] - Sell signals:', sellSignals);
- console.log('[HTS] - 1H filter rejections:', rejectionsCount);
- console.log('[HTS] - Total HTS candles processed:', primaryHTS.length);
- console.log('[HTS] - Total trades:', trades.length);
- console.log('[HTS] - Final balance:', balance.toFixed(2));
- console.log('[HTS] - Win rate:', winRate.toFixed(1) + '%');
-
- if (trades.length === 0) {
- console.warn('[HTS] No trades generated! Possible reasons:');
- if (rejectionsCount > 0) {
- console.warn(` - 1H filter rejected ${rejectionsCount} potential trades`);
- }
- if (buySignals === 0 && sellSignals === 0) {
- console.warn(' - No crossover or alignment signals detected');
- console.warn(' - Market may be in ranging/sideways mode');
- }
- console.warn(' - Consider:');
- console.warn(' * Reducing 1H filter strictness or disabling it');
- console.warn(' * Adjusting short/long periods for current timeframe');
- console.warn(' * Checking if data has sufficient price movement');
- }
-
- return {
- trades,
- balance,
- totalTrades: trades.length,
- winRate,
- finalPnL: balance - 1000,
- total_trades: trades.length,
- win_rate: winRate,
- total_pnl: balance - 1000,
- htsData: primaryHTS
- };
- }
-}
\ No newline at end of file
diff --git a/src/api/dashboard/static/js/strategies/index.js b/src/api/dashboard/static/js/strategies/index.js
deleted file mode 100644
index a14f5b4..0000000
--- a/src/api/dashboard/static/js/strategies/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { StrategyParams } from './config.js';
-export { RiskManager } from './risk-manager.js';
-export { ClientStrategyEngine } from './engine.js';
diff --git a/src/api/dashboard/static/js/strategies/risk-manager.js b/src/api/dashboard/static/js/strategies/risk-manager.js
deleted file mode 100644
index 11c66dc..0000000
--- a/src/api/dashboard/static/js/strategies/risk-manager.js
+++ /dev/null
@@ -1,17 +0,0 @@
-export class RiskManager {
- constructor(config, initialBalance = 1000) {
- this.config = config || {
- positionSizing: { method: 'percent', value: 0.1 },
- stopLoss: { enabled: true, method: 'percent', value: 0.02 },
- takeProfit: { enabled: true, method: 'percent', value: 0.04 }
- };
- this.balance = initialBalance;
- this.equity = initialBalance;
- }
- calculateSize(price) {
- if (this.config.positionSizing.method === 'percent') {
- return (this.balance * this.config.positionSizing.value) / price;
- }
- return this.config.positionSizing.value / price;
- }
-}
diff --git a/src/api/dashboard/static/js/ui/export.js b/src/api/dashboard/static/js/ui/export.js
deleted file mode 100644
index 42dfc63..0000000
--- a/src/api/dashboard/static/js/ui/export.js
+++ /dev/null
@@ -1,140 +0,0 @@
-import { downloadFile } from '../utils/index.js';
-
-export function showExportDialog() {
- if (!window.lastSimulationResults) {
- alert('Please run a simulation first');
- return;
- }
-
- const overlay = document.createElement('div');
- overlay.className = 'dialog-overlay';
- overlay.onclick = () => closeExportDialog();
- document.body.appendChild(overlay);
-
- const dialog = document.createElement('div');
- dialog.className = 'export-dialog';
- dialog.id = 'exportDialog';
- dialog.innerHTML = `
-
Parameters
diff --git a/src/api/dashboard/static/js/ui/signals-calculator.js b/src/api/dashboard/static/js/ui/signals-calculator.js
index 75c1589..7e17e24 100644
--- a/src/api/dashboard/static/js/ui/signals-calculator.js
+++ b/src/api/dashboard/static/js/ui/signals-calculator.js
@@ -236,18 +236,16 @@ function calculateMASignal(indicator, lastCandle, prevCandle, values) {
let signalType, strength, reasoning;
- if (close > ma * 1.02) {
+ if (close > ma) {
signalType = SIGNAL_TYPES.BUY;
strength = Math.min(60 + ((close - ma) / ma) * 500, 100);
- reasoning = `Price (${close.toFixed(2)}) is strongly above ${maLabel} (${ma.toFixed(2)}), bullish trend`;
- } else if (close < ma * 0.98) {
+ reasoning = `Price (${close.toFixed(2)}) is above ${maLabel} (${ma.toFixed(2)})`;
+ } else if (close < ma) {
signalType = SIGNAL_TYPES.SELL;
strength = Math.min(60 + ((ma - close) / ma) * 500, 100);
- reasoning = `Price (${close.toFixed(2)}) is strongly below ${maLabel} (${ma.toFixed(2)}), bearish trend`;
+ reasoning = `Price (${close.toFixed(2)}) is below ${maLabel} (${ma.toFixed(2)})`;
} else {
- signalType = SIGNAL_TYPES.HOLD;
- strength = 30;
- reasoning = `Price (${close.toFixed(2)}) is near ${maLabel} (${ma.toFixed(2)}), sideways/consolidating`;
+ return null;
}
console.log('[calculateMASignal] Result:', signalType, strength);
@@ -406,6 +404,36 @@ export function calculateAllIndicatorSignals() {
const signal = calculateIndicatorSignal(indicator, candles, values);
+ let currentSignal = signal;
+ let lastSignalDate = indicator.lastSignalTimestamp || null;
+ let lastSignalType = indicator.lastSignalType || null;
+
+ if (!currentSignal || !currentSignal.type) {
+ console.log('[Signals] No valid signal for', indicator.type, '- Using last signal if available');
+
+ if (lastSignalType && lastSignalDate) {
+ currentSignal = {
+ type: lastSignalType,
+ strength: 50,
+ value: candles[candles.length - 1]?.close,
+ reasoning: `No crossover (price equals MA)`
+ };
+ } else {
+ console.log('[Signals] No previous signal available - Skipping');
+ continue;
+ }
+ } else {
+ const currentCandleTimestamp = candles[candles.length - 1].time;
+
+ if (currentSignal.type !== lastSignalType || !lastSignalType) {
+ console.log('[Signals] Signal changed for', indicator.type, ':', lastSignalType, '->', currentSignal.type);
+ lastSignalDate = currentCandleTimestamp;
+ lastSignalType = currentSignal.type;
+ indicator.lastSignalTimestamp = lastSignalDate;
+ indicator.lastSignalType = lastSignalType;
+ }
+ }
+
const label = indicator.type?.toUpperCase();
const params = indicator.params && typeof indicator.params === 'object'
? Object.entries(indicator.params)
@@ -420,11 +448,12 @@ export function calculateAllIndicatorSignals() {
label: label,
params: params || null,
type: indicator.type,
- signal: signal.type,
- strength: Math.round(signal.strength),
- value: signal.value,
- reasoning: signal.reasoning,
- color: SIGNAL_COLORS[signal.type]
+ signal: currentSignal.type,
+ strength: Math.round(currentSignal.strength),
+ value: currentSignal.value,
+ reasoning: currentSignal.reasoning,
+ color: SIGNAL_COLORS[currentSignal.type],
+ lastSignalDate: lastSignalDate
});
}
diff --git a/src/api/dashboard/static/js/ui/simulation.js b/src/api/dashboard/static/js/ui/simulation.js
deleted file mode 100644
index 201b111..0000000
--- a/src/api/dashboard/static/js/ui/simulation.js
+++ /dev/null
@@ -1,567 +0,0 @@
-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';
-
-let lastSimulationResults = null;
-
-export function getLastResults() {
- return lastSimulationResults;
-}
-
-export function setLastResults(results) {
- lastSimulationResults = results;
- window.lastSimulationResults = results;
-}
-
-export async function runSimulation() {
- const strategyConfig = getStrategyConfig();
- if (!strategyConfig) {
- alert('Please select a strategy');
- return;
- }
-
- const startDateInput = document.getElementById('simStartDate').value;
- if (!startDateInput) {
- alert('Please select a start date');
- return;
- }
-
- const runBtn = document.getElementById('runSimBtn');
- runBtn.disabled = true;
- runBtn.textContent = '⏳ Running...';
-
- try {
- const start = new Date(startDateInput);
- const fetchStart = new Date(start.getTime() - 200 * 24 * 60 * 60 * 1000);
-
- if (!window.dashboard) {
- throw new Error('Dashboard not initialized');
- }
- const interval = window.dashboard.currentInterval;
- const secondaryTF = document.getElementById('simSecondaryTF').value;
- const riskPercent = parseFloat(document.getElementById('simRiskPercent').value);
- const stopLossPercent = parseFloat(document.getElementById('simStopLoss').value);
-
- const timeframes = [interval];
- if (secondaryTF && secondaryTF !== '') {
- timeframes.push(secondaryTF);
- }
-
- const query = new URLSearchParams({ symbol: 'BTC', start: fetchStart.toISOString() });
- timeframes.forEach(tf => query.append('timeframes', tf));
-
- console.log('Fetching candles with query:', query.toString());
-
- const response = await fetch(`/api/v1/candles/bulk?${query.toString()}`);
-
- if (!response.ok) {
- throw new Error(`API error: ${response.status} ${response.statusText}`);
- }
-
- const data = await response.json();
- console.log('Candle data received:', data);
- console.log('Looking for interval:', interval);
- console.log('Available timeframes:', Object.keys(data));
-
- if (!data[interval] || data[interval].length === 0) {
- throw new Error(`No candle data available for ${interval} timeframe. Check if data exists in database.`);
- }
-
- const candlesMap = {
- [interval]: data[interval].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)
- }))
- };
-
- if (secondaryTF && data[secondaryTF]) {
- candlesMap[secondaryTF] = data[secondaryTF].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)
- }));
- }
-
- const engineConfig = {
- id: strategyConfig.id,
- params: strategyConfig.params,
- 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));
-
- 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}`,
- type: 'sma',
- params: { period: period },
- 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);
- }
-
- if (results.error) throw new Error(results.error);
-
- setLastResults({
- ...results,
- config: {
- strategyId: strategyConfig.id,
- strategyName: window.availableStrategies?.find(s => s.id === strategyConfig.id)?.name || strategyConfig.id,
- timeframe: interval,
- secondaryTimeframe: secondaryTF,
- startDate: startDateInput,
- riskPercent: riskPercent,
- stopLossPercent: stopLossPercent,
- params: strategyConfig.params
- },
- runAt: new Date().toISOString()
- });
-
- displayEnhancedResults(lastSimulationResults);
-
- document.getElementById('resultsSection').style.display = 'block';
-
- if (window.dashboard && candlesMap[interval]) {
- const chartData = candlesMap[interval].map(c => ({
- time: c.time,
- open: c.open,
- high: c.high,
- low: c.low,
- close: c.close
- }));
- window.dashboard.candleSeries.setData(chartData);
- window.dashboard.allData.set(interval, chartData);
- console.log(`Chart updated with ${chartData.length} candles from simulation range`);
- }
-
- 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);
- } finally {
- runBtn.disabled = false;
- runBtn.textContent = '▶ Run Simulation';
- }
-}
-
-export function displayEnhancedResults(simulation) {
- const results = simulation.results || simulation;
-
- document.getElementById('simTrades').textContent = results.total_trades || '0';
- document.getElementById('simWinRate').textContent = (results.win_rate || 0).toFixed(1) + '%';
-
- const pnl = results.total_pnl || 0;
- const pnlElement = document.getElementById('simPnL');
- pnlElement.textContent = (pnl >= 0 ? '+' : '') + '$' + pnl.toFixed(2);
- pnlElement.style.color = pnl >= 0 ? '#4caf50' : '#f44336';
-
- let grossProfit = 0;
- let grossLoss = 0;
- (results.trades || []).forEach(trade => {
- if (trade.pnl > 0) grossProfit += trade.pnl;
- else grossLoss += Math.abs(trade.pnl);
- });
- const profitFactor = grossLoss > 0 ? (grossProfit / grossLoss).toFixed(2) : grossProfit > 0 ? '∞' : '0';
- document.getElementById('simProfitFactor').textContent = profitFactor;
-
- drawEquitySparkline(results);
-}
-
-function drawEquitySparkline(results) {
- const container = document.getElementById('equitySparkline');
- if (!container || !results.trades || results.trades.length === 0) {
- container.innerHTML = '
No trades
';
- return;
- }
-
- let equity = 1000;
- const equityData = [{ time: results.trades[0].entryTime, equity: equity }];
-
- results.trades.forEach(trade => {
- equity += trade.pnl;
- equityData.push({ time: trade.exitTime, equity: equity });
- });
-
- if (lastSimulationResults) {
- lastSimulationResults.equity_curve = equityData;
- }
-
- container.innerHTML = '
';
- const canvas = document.getElementById('sparklineCanvas');
- const ctx = canvas.getContext('2d');
-
- const minEquity = Math.min(...equityData.map(d => d.equity));
- const maxEquity = Math.max(...equityData.map(d => d.equity));
- const range = maxEquity - minEquity || 1;
-
- ctx.strokeStyle = equityData[equityData.length - 1].equity >= equityData[0].equity ? '#4caf50' : '#f44336';
- ctx.lineWidth = 2;
- ctx.beginPath();
-
- equityData.forEach((point, i) => {
- const x = (i / (equityData.length - 1)) * canvas.width;
- const y = canvas.height - ((point.equity - minEquity) / range) * canvas.height;
-
- if (i === 0) ctx.moveTo(x, y);
- else ctx.lineTo(x, y);
- });
-
- ctx.stroke();
-
- ctx.fillStyle = '#888';
- ctx.font = '9px sans-serif';
- ctx.fillText('$' + equityData[0].equity.toFixed(0), 2, canvas.height - 2);
- ctx.fillText('$' + equityData[equityData.length - 1].equity.toFixed(0), canvas.width - 30, 10);
-}
-
-let tradeLineSeries = [];
-let htsVisualizer = null;
-let tradeMarkerSeries = [];
-
-export function showSimulationMarkers() {
- const results = getLastResults();
-
- 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);
- 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);
- }
-
- 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);
-
- 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() {
- try {
- if (window.dashboard && window.dashboard.candleSeries && typeof window.dashboard.candleSeries.setMarkers === 'function') {
- window.dashboard.candleSeries.setMarkers([]);
- }
- } catch (e) {
- // Ignore errors clearing markers
- }
-
- try {
- tradeLineSeries.forEach(series => {
- try {
- if (window.dashboard && window.dashboard.chart) {
- window.dashboard.chart.removeSeries(series);
- }
- } catch (e) {
- // Series might already be removed
- }
- });
- } 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() {
- clearSimulationMarkers();
-
- setLastResults(null);
-
- const resultsSection = document.getElementById('resultsSection');
- if (resultsSection) {
- resultsSection.style.display = 'none';
- }
-
- const simTrades = document.getElementById('simTrades');
- const simWinRate = document.getElementById('simWinRate');
- const simPnL = document.getElementById('simPnL');
- const simProfitFactor = document.getElementById('simProfitFactor');
- const equitySparkline = document.getElementById('equitySparkline');
-
- if (simTrades) simTrades.textContent = '0';
- if (simWinRate) simWinRate.textContent = '0%';
- if (simPnL) {
- simPnL.textContent = '$0.00';
- simPnL.style.color = '';
- }
- if (simProfitFactor) simProfitFactor.textContent = '0';
- if (equitySparkline) equitySparkline.innerHTML = '';
-}
-
-function getStrategyConfig() {
- const strategyId = window.currentStrategy;
- if (!strategyId) return null;
-
- const params = {};
- const paramDefs = window.StrategyParams?.[strategyId] || [];
-
- paramDefs.forEach(def => {
- const input = document.getElementById(`param_${def.name}`);
- if (input) {
- params[def.name] = def.type === 'number' ? parseFloat(input.value) : input.value;
- }
- });
-
- return {
- id: strategyId,
- params: params
- };
-}
diff --git a/src/api/dashboard/static/js/ui/storage.js b/src/api/dashboard/static/js/ui/storage.js
deleted file mode 100644
index 48b2fa3..0000000
--- a/src/api/dashboard/static/js/ui/storage.js
+++ /dev/null
@@ -1,47 +0,0 @@
-export const SimulationStorage = {
- STORAGE_KEY: 'btc_bot_simulations',
-
- getAll() {
- try {
- const data = localStorage.getItem(this.STORAGE_KEY);
- return data ? JSON.parse(data) : [];
- } catch (e) {
- console.error('Error reading simulations:', e);
- return [];
- }
- },
-
- save(simulation) {
- try {
- const simulations = this.getAll();
- simulation.id = simulation.id || 'sim_' + Date.now();
- simulation.createdAt = new Date().toISOString();
- simulations.push(simulation);
- localStorage.setItem(this.STORAGE_KEY, JSON.stringify(simulations));
- return simulation.id;
- } catch (e) {
- console.error('Error saving simulation:', e);
- return null;
- }
- },
-
- delete(id) {
- try {
- let simulations = this.getAll();
- simulations = simulations.filter(s => s.id !== id);
- localStorage.setItem(this.STORAGE_KEY, JSON.stringify(simulations));
- return true;
- } catch (e) {
- console.error('Error deleting simulation:', e);
- return false;
- }
- },
-
- get(id) {
- return this.getAll().find(s => s.id === id);
- },
-
- clear() {
- localStorage.removeItem(this.STORAGE_KEY);
- }
-};
diff --git a/src/api/dashboard/static/js/ui/strategies-panel.js b/src/api/dashboard/static/js/ui/strategies-panel.js
deleted file mode 100644
index 140cfb2..0000000
--- a/src/api/dashboard/static/js/ui/strategies-panel.js
+++ /dev/null
@@ -1,318 +0,0 @@
-import { StrategyParams } from '../strategies/config.js';
-
-let currentStrategy = null;
-
-export function getCurrentStrategy() {
- return currentStrategy;
-}
-
-export function setCurrentStrategy(strategyId) {
- currentStrategy = strategyId;
- window.currentStrategy = strategyId;
-}
-
-export function renderStrategies(strategies) {
- const container = document.getElementById('strategyList');
-
- if (!strategies || strategies.length === 0) {
- container.innerHTML = '
No strategies available
';
- return;
- }
-
- container.innerHTML = strategies.map((s, index) => `
-
-
- ${s.name}
- ⓘ
-
- `).join('');
-
- if (strategies.length > 0) {
- selectStrategy(strategies[0].id);
- }
-
- document.getElementById('runSimBtn').disabled = false;
-}
-
-export function selectStrategy(strategyId) {
- document.querySelectorAll('.strategy-item').forEach(item => {
- item.classList.toggle('selected', item.dataset.strategyId === strategyId);
- const radio = item.querySelector('input[type="radio"]');
- if (radio) radio.checked = item.dataset.strategyId === strategyId;
- });
-
- setCurrentStrategy(strategyId);
- renderStrategyParams(strategyId);
-}
-
-export function renderStrategyParams(strategyId) {
- const container = document.getElementById('strategyParams');
- const params = StrategyParams[strategyId] || [];
-
- if (params.length === 0) {
- container.innerHTML = '';
- return;
- }
-
- container.innerHTML = params.map(param => `
-
-
-
-
- `).join('');
-}
-
-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}`);
- }
-
- 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) {
- console.error('Error loading strategies:', error);
-
- let errorMessage = error.message;
- if (error.name === 'AbortError') {
- errorMessage = 'Request timeout - API server not responding';
- } else if (error.message.includes('Failed to fetch')) {
- errorMessage = 'Cannot connect to API server - is it running?';
- }
-
- document.getElementById('strategyList').innerHTML =
- `
- ${errorMessage}
- Check console (F12) for details
-
`;
- }
-}
-
-export function saveSimulation() {
- const results = getLastResults();
- if (!results) {
- alert('Please run a simulation first');
- return;
- }
-
- const defaultName = generateSimulationName(results.config);
- const name = prompt('Save simulation as:', defaultName);
-
- if (!name || name.trim() === '') return;
-
- const simulation = {
- name: name.trim(),
- config: results.config,
- results: {
- total_trades: results.total_trades,
- win_rate: results.win_rate,
- total_pnl: results.total_pnl,
- trades: results.trades,
- equity_curve: results.equity_curve
- }
- };
-
- const id = window.SimulationStorage?.save(simulation);
- if (id) {
- renderSavedSimulations();
- alert('Simulation saved successfully!');
- } else {
- alert('Error saving simulation');
- }
-}
-
-function generateSimulationName(config) {
- if (!config) return 'Unnamed Simulation';
-
- const start = new Date(config.startDate);
- const now = new Date();
- const duration = now - start;
- const oneDay = 24 * 60 * 60 * 1000;
-
- let dateStr;
- if (duration < oneDay) {
- dateStr = start.toISOString().slice(0, 16).replace('T', ' ');
- } else {
- dateStr = start.toISOString().slice(0, 10);
- }
-
- return `${config.strategyName}_${config.timeframe}_${dateStr}`;
-}
-
-export function renderSavedSimulations() {
- const container = document.getElementById('savedSimulations');
- const simulations = window.SimulationStorage?.getAll() || [];
-
- if (simulations.length === 0) {
- container.innerHTML = '
No saved simulations
';
- return;
- }
-
- container.innerHTML = simulations.map(sim => `
-
-
- ${sim.name.length > 25 ? sim.name.slice(0, 25) + '...' : sim.name}
-
-
-
-
-
-
-
- `).join('');
-}
-
-export function loadSavedSimulation(id) {
- const sim = window.SimulationStorage?.get(id);
- if (!sim) {
- alert('Simulation not found');
- return;
- }
-
- if (sim.config) {
- document.getElementById('simSecondaryTF').value = sim.config.secondaryTimeframe || '';
- document.getElementById('simStartDate').value = sim.config.startDate || '';
- document.getElementById('simRiskPercent').value = sim.config.riskPercent || 2;
- document.getElementById('simStopLoss').value = sim.config.stopLossPercent || 2;
-
- if (sim.config.strategyId) {
- selectStrategy(sim.config.strategyId);
-
- if (sim.config.params) {
- Object.entries(sim.config.params).forEach(([key, value]) => {
- const input = document.getElementById(`param_${key}`);
- if (input) input.value = value;
- });
- }
- }
- }
-
- setLastResults(sim);
- displayEnhancedResults(sim.results);
- document.getElementById('resultsSection').style.display = 'block';
-}
-
-export function deleteSavedSimulation(id) {
- if (!confirm('Are you sure you want to delete this simulation?')) return;
-
- if (window.SimulationStorage?.delete(id)) {
- renderSavedSimulations();
- }
-}
-
-function displayEnhancedResults(simulation) {
- const results = simulation.results || simulation;
-
- document.getElementById('simTrades').textContent = results.total_trades || '0';
- document.getElementById('simWinRate').textContent = (results.win_rate || 0).toFixed(1) + '%';
-
- const pnl = results.total_pnl || 0;
- const pnlElement = document.getElementById('simPnL');
- pnlElement.textContent = (pnl >= 0 ? '+' : '') + '$' + pnl.toFixed(2);
- pnlElement.style.color = pnl >= 0 ? '#4caf50' : '#f44336';
-
- let grossProfit = 0;
- let grossLoss = 0;
- (results.trades || []).forEach(trade => {
- if (trade.pnl > 0) grossProfit += trade.pnl;
- else grossLoss += Math.abs(trade.pnl);
- });
- const profitFactor = grossLoss > 0 ? (grossProfit / grossLoss).toFixed(2) : grossProfit > 0 ? '∞' : '0';
- document.getElementById('simProfitFactor').textContent = profitFactor;
-
- drawEquitySparkline(results);
-}
-
-function drawEquitySparkline(results) {
- const container = document.getElementById('equitySparkline');
- if (!container || !results.trades || results.trades.length === 0) {
- container.innerHTML = '
No trades
';
- return;
- }
-
- let equity = 1000;
- const equityData = [{ time: results.trades[0].entryTime, equity: equity }];
-
- results.trades.forEach(trade => {
- equity += trade.pnl;
- equityData.push({ time: trade.exitTime, equity: equity });
- });
-
- const sim = getLastResults();
- if (sim) {
- sim.equity_curve = equityData;
- }
-
- container.innerHTML = '
';
- const canvas = document.getElementById('sparklineCanvas');
- const ctx = canvas.getContext('2d');
-
- const minEquity = Math.min(...equityData.map(d => d.equity));
- const maxEquity = Math.max(...equityData.map(d => d.equity));
- const range = maxEquity - minEquity || 1;
-
- ctx.strokeStyle = equityData[equityData.length - 1].equity >= equityData[0].equity ? '#4caf50' : '#f44336';
- ctx.lineWidth = 2;
- ctx.beginPath();
-
- equityData.forEach((point, i) => {
- const x = (i / (equityData.length - 1)) * canvas.width;
- const y = canvas.height - ((point.equity - minEquity) / range) * canvas.height;
-
- if (i === 0) ctx.moveTo(x, y);
- else ctx.lineTo(x, y);
- });
-
- ctx.stroke();
-
- ctx.fillStyle = '#888';
- ctx.font = '9px sans-serif';
- ctx.fillText('$' + equityData[0].equity.toFixed(0), 2, canvas.height - 2);
- ctx.fillText('$' + equityData[equityData.length - 1].equity.toFixed(0), canvas.width - 30, 10);
-}
-
-function getLastResults() {
- return window.lastSimulationResults;
-}
-
-function setLastResults(results) {
- window.lastSimulationResults = results;
-}
-
-window.selectStrategy = selectStrategy;
-window.loadSavedSimulation = loadSavedSimulation;
-window.deleteSavedSimulation = deleteSavedSimulation;
-window.renderSavedSimulations = renderSavedSimulations;
diff --git a/src/api/server.py b/src/api/server.py
index cec7c17..11d2419 100644
--- a/src/api/server.py
+++ b/src/api/server.py
@@ -25,12 +25,6 @@ from pydantic import BaseModel, Field
# Imports for backtest runner
from src.data_collector.database import DatabaseManager
from src.data_collector.indicator_engine import IndicatorEngine, IndicatorConfig
-from src.data_collector.brain import Brain
-from src.data_collector.backtester import Backtester
-
-# Imports for strategy discovery
-import importlib
-from src.strategies.base import BaseStrategy
logging.basicConfig(level=logging.INFO)
@@ -103,45 +97,6 @@ async def root():
}
-@app.get("/api/v1/strategies")
-async def list_strategies(response: Response):
- """List all available trading strategies with metadata"""
- # Prevent caching
- response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
- response.headers["Pragma"] = "no-cache"
- response.headers["Expires"] = "0"
-
- # Strategy registry from brain.py
- strategy_registry = {
- "ma_trend": "src.strategies.ma_strategy.MAStrategy",
- }
-
- strategies = []
-
- for strategy_id, class_path in strategy_registry.items():
- try:
- module_path, class_name = class_path.rsplit('.', 1)
- module = importlib.import_module(module_path)
- strategy_class = getattr(module, class_name)
-
- # Instantiate to get metadata
- strategy_instance = strategy_class()
-
- strategies.append({
- "id": strategy_id,
- "name": strategy_instance.display_name,
- "description": strategy_instance.description,
- "required_indicators": strategy_instance.required_indicators
- })
- except Exception as e:
- logger.error(f"Failed to load strategy {strategy_id}: {e}")
-
- return {
- "strategies": strategies,
- "count": len(strategies)
- }
-
-
@app.get("/api/v1/candles")
async def get_candles(
symbol: str = Query("BTC", description="Trading pair symbol"),
@@ -417,50 +372,6 @@ async def list_backtests(symbol: Optional[str] = None, limit: int = 20):
return [dict(row) for row in rows]
-class BacktestRequest(BaseModel):
- symbol: str = "BTC"
- intervals: list[str] = ["37m"]
- start_date: str = "2025-01-01" # ISO date
- end_date: Optional[str] = None
-
-
-async def run_backtest_task(req: BacktestRequest):
- """Background task to run backtest"""
- db = DatabaseManager(
- host=DB_HOST, port=DB_PORT, database=DB_NAME,
- user=DB_USER, password=DB_PASSWORD
- )
- await db.connect()
-
- try:
- # Load configs (hardcoded for now to match main.py)
- configs = [
- IndicatorConfig("ma44", "sma", 44, req.intervals),
- IndicatorConfig("ma125", "sma", 125, req.intervals)
- ]
-
- engine = IndicatorEngine(db, configs)
- brain = Brain(db, engine)
- backtester = Backtester(db, engine, brain)
-
- start = datetime.fromisoformat(req.start_date).replace(tzinfo=timezone.utc)
- end = datetime.fromisoformat(req.end_date).replace(tzinfo=timezone.utc) if req.end_date else datetime.now(timezone.utc)
-
- await backtester.run(req.symbol, req.intervals, start, end)
-
- except Exception as e:
- logger.error(f"Backtest failed: {e}")
- finally:
- await db.disconnect()
-
-
-@app.post("/api/v1/backtests")
-async def trigger_backtest(req: BacktestRequest, background_tasks: BackgroundTasks):
- """Start a backtest in the background"""
- background_tasks.add_task(run_backtest_task, req)
- return {"message": "Backtest started", "params": req.dict()}
-
-
@app.get("/api/v1/ta")
async def get_technical_analysis(
symbol: str = Query("BTC", description="Trading pair symbol"),
diff --git a/src/data_collector/__init__.py b/src/data_collector/__init__.py
index 40f086e..cf2dd9c 100644
--- a/src/data_collector/__init__.py
+++ b/src/data_collector/__init__.py
@@ -6,7 +6,6 @@ from .backfill import HyperliquidBackfill
from .custom_timeframe_generator import CustomTimeframeGenerator
from .indicator_engine import IndicatorEngine, IndicatorConfig
from .brain import Brain, Decision
-from .backtester import Backtester
__all__ = [
'HyperliquidWebSocket',
@@ -18,6 +17,5 @@ __all__ = [
'IndicatorEngine',
'IndicatorConfig',
'Brain',
- 'Decision',
- 'Backtester'
+ 'Decision'
]
diff --git a/src/data_collector/backtester.py b/src/data_collector/backtester.py
deleted file mode 100644
index c3b7b8c..0000000
--- a/src/data_collector/backtester.py
+++ /dev/null
@@ -1,391 +0,0 @@
-"""
-Backtester - Historical replay driver for IndicatorEngine + Brain
-Iterates over stored candle data to simulate live trading decisions
-"""
-
-import asyncio
-import json
-import logging
-from datetime import datetime, timezone
-from typing import Dict, List, Optional, Any
-from uuid import uuid4
-
-from .database import DatabaseManager
-from .indicator_engine import IndicatorEngine, IndicatorConfig
-from .brain import Brain, Decision
-from .simulator import Account
-from src.strategies.base import SignalType
-
-logger = logging.getLogger(__name__)
-
-
-class Backtester:
- """
- Replays historical candle data through IndicatorEngine and Brain.
- Uses Simulator (Account) to track PnL, leverage, and fees.
- """
-
- def __init__(
- self,
- db: DatabaseManager,
- indicator_engine: IndicatorEngine,
- brain: Brain,
- ):
- self.db = db
- self.indicator_engine = indicator_engine
- self.brain = brain
- self.account = Account(initial_balance=1000.0)
-
- async def run(
- self,
- symbol: str,
- intervals: List[str],
- start: datetime,
- end: datetime,
- config: Optional[Dict[str, Any]] = None,
- ) -> str:
- """
- Run a full backtest over the given time range.
- """
- backtest_id = str(uuid4())
-
- logger.info(
- f"Starting backtest {backtest_id}: {symbol} "
- f"{intervals} from {start} to {end}"
- )
-
- # Reset brain state
- self.brain.reset_state()
-
- # Reset account for this run
- self.account = Account(initial_balance=1000.0)
-
- # Store the run metadata
- await self._save_run_start(
- backtest_id, symbol, intervals, start, end, config
- )
-
- total_decisions = 0
-
- for interval in intervals:
- # Only process intervals that have indicators configured
- configured = self.indicator_engine.get_configured_intervals()
- if interval not in configured:
- logger.warning(
- f"Skipping interval {interval}: no indicators configured"
- )
- continue
-
- # Get all candle timestamps in range
- timestamps = await self._get_candle_timestamps(
- symbol, interval, start, end
- )
-
- if not timestamps:
- logger.warning(
- f"No candles found for {symbol}/{interval} in range"
- )
- continue
-
- logger.info(
- f"Backtest {backtest_id}: processing {len(timestamps)} "
- f"{interval} candles..."
- )
-
- for i, ts in enumerate(timestamps):
- # 1. Compute indicators
- raw_indicators = await self.indicator_engine.compute_at(
- symbol, interval, ts
- )
- indicators = {k: v for k, v in raw_indicators.items() if v is not None}
-
- # 2. Get Current Position info for Strategy
- current_pos = self.account.get_position_dict()
-
- # 3. Brain Evaluate
- decision: Decision = await self.brain.evaluate(
- symbol=symbol,
- interval=interval,
- timestamp=ts,
- indicators=indicators,
- backtest_id=backtest_id,
- current_position=current_pos
- )
-
- # 4. Execute Decision in Simulator
- self._execute_decision(decision)
-
- total_decisions += 1
-
- if (i + 1) % 200 == 0:
- logger.info(
- f"Backtest {backtest_id}: {i + 1}/{len(timestamps)} "
- f"{interval} candles processed. Eq: {self.account.equity:.2f}"
- )
- await asyncio.sleep(0.01)
-
- # Compute and store summary results from Simulator
- results = self.account.get_stats()
- results['total_evaluations'] = total_decisions
-
- await self._save_run_results(backtest_id, results)
-
- logger.info(
- f"Backtest {backtest_id} complete. Final Balance: {results['final_balance']:.2f}"
- )
-
- return backtest_id
-
- def _execute_decision(self, decision: Decision):
- """Translate Brain decision into Account action"""
- price = decision.price_at_decision
- time = decision.time
-
- # Open Long
- if decision.decision_type == SignalType.OPEN_LONG.value:
- self.account.open_position(time, 'long', price, leverage=1.0) # Todo: Configurable leverage
-
- # Open Short
- elif decision.decision_type == SignalType.OPEN_SHORT.value:
- self.account.open_position(time, 'short', price, leverage=1.0)
-
- # Close Long (only if we are long)
- elif decision.decision_type == SignalType.CLOSE_LONG.value:
- if self.account.current_position and self.account.current_position.side == 'long':
- self.account.close_position(time, price)
-
- # Close Short (only if we are short)
- elif decision.decision_type == SignalType.CLOSE_SHORT.value:
- if self.account.current_position and self.account.current_position.side == 'short':
- self.account.close_position(time, price)
-
- # Update equity mark-to-market
- self.account.update_equity(price)
-
- async def _get_candle_timestamps(
- self,
- symbol: str,
- interval: str,
- start: datetime,
- end: datetime,
- ) -> List[datetime]:
- """Get all candle timestamps in a range, ordered chronologically"""
- async with self.db.acquire() as conn:
- rows = await conn.fetch("""
- SELECT time FROM candles
- WHERE symbol = $1 AND interval = $2
- AND time >= $3 AND time <= $4
- ORDER BY time ASC
- """, symbol, interval, start, end)
-
- return [row["time"] for row in rows]
-
- async def _save_run_start(
- self,
- backtest_id: str,
- symbol: str,
- intervals: List[str],
- start: datetime,
- end: datetime,
- config: Optional[Dict[str, Any]],
- ) -> None:
- """Store backtest run metadata at start"""
- async with self.db.acquire() as conn:
- await conn.execute("""
- INSERT INTO backtest_runs (
- id, strategy, symbol, start_time, end_time,
- intervals, config
- )
- VALUES ($1, $2, $3, $4, $5, $6, $7)
- """,
- backtest_id,
- self.brain.strategy_name,
- symbol,
- start,
- end,
- intervals,
- json.dumps(config) if config else None,
- )
-
- async def _compute_results(self, backtest_id, symbol):
- """Deprecated: Logic moved to Account class"""
- return {}
-
- async def _save_run_results(
- self,
- backtest_id: str,
- results: Dict[str, Any],
- ) -> None:
- """Update backtest run with final results"""
- # Remove trades list from stored results (can be large)
- stored_results = {k: v for k, v in results.items() if k != "trades"}
-
- async with self.db.acquire() as conn:
- await conn.execute("""
- UPDATE backtest_runs
- SET results = $1
- WHERE id = $2
- """, json.dumps(stored_results), backtest_id)
-
- async def get_run(self, backtest_id: str) -> Optional[Dict[str, Any]]:
- """Get a specific backtest run with results"""
- async with self.db.acquire() as conn:
- row = await conn.fetchrow("""
- SELECT id, strategy, symbol, start_time, end_time,
- intervals, config, results, created_at
- FROM backtest_runs
- WHERE id = $1
- """, backtest_id)
-
- return dict(row) if row else None
-
- async def list_runs(
- self,
- symbol: Optional[str] = None,
- limit: int = 20,
- ) -> List[Dict[str, Any]]:
- """List recent backtest runs"""
- async with self.db.acquire() as conn:
- if symbol:
- rows = await conn.fetch("""
- SELECT id, strategy, symbol, start_time, end_time,
- intervals, results, created_at
- FROM backtest_runs
- WHERE symbol = $1
- ORDER BY created_at DESC
- LIMIT $2
- """, symbol, limit)
- else:
- rows = await conn.fetch("""
- SELECT id, strategy, symbol, start_time, end_time,
- intervals, results, created_at
- FROM backtest_runs
- ORDER BY created_at DESC
- LIMIT $1
- """, limit)
-
- return [dict(row) for row in rows]
-
- async def cleanup_run(self, backtest_id: str) -> int:
- """Delete all decisions and metadata for a backtest run"""
- async with self.db.acquire() as conn:
- result = await conn.execute("""
- DELETE FROM decisions WHERE backtest_id = $1
- """, backtest_id)
-
- await conn.execute("""
- DELETE FROM backtest_runs WHERE id = $1
- """, backtest_id)
-
- deleted = int(result.split()[-1]) if result else 0
- logger.info(
- f"Cleaned up backtest {backtest_id}: "
- f"{deleted} decisions deleted"
- )
- return deleted
-
-
-async def main():
- """CLI entry point for running backtests"""
- import argparse
- import os
-
- parser = argparse.ArgumentParser(
- description="Run backtest on historical data"
- )
- parser.add_argument(
- "--symbol", default="BTC", help="Symbol (default: BTC)"
- )
- parser.add_argument(
- "--intervals", nargs="+", default=["37m"],
- help="Intervals to backtest (default: 37m)"
- )
- parser.add_argument(
- "--start", required=True,
- help="Start date (ISO format, e.g., 2025-01-01)"
- )
- parser.add_argument(
- "--end", default=None,
- help="End date (ISO format, default: now)"
- )
- parser.add_argument(
- "--db-host", default=os.getenv("DB_HOST", "localhost"),
- )
- parser.add_argument(
- "--db-port", type=int, default=int(os.getenv("DB_PORT", 5432)),
- )
- parser.add_argument(
- "--db-name", default=os.getenv("DB_NAME", "btc_data"),
- )
- parser.add_argument(
- "--db-user", default=os.getenv("DB_USER", "btc_bot"),
- )
- parser.add_argument(
- "--db-password", default=os.getenv("DB_PASSWORD", ""),
- )
-
- args = parser.parse_args()
-
- logging.basicConfig(
- level=logging.INFO,
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
- )
-
- # Parse dates
- start = datetime.fromisoformat(args.start).replace(tzinfo=timezone.utc)
- end = (
- datetime.fromisoformat(args.end).replace(tzinfo=timezone.utc)
- if args.end
- else datetime.now(timezone.utc)
- )
-
- # Initialize components
- db = DatabaseManager(
- host=args.db_host,
- port=args.db_port,
- database=args.db_name,
- user=args.db_user,
- password=args.db_password,
- )
- await db.connect()
-
- try:
- # Default indicator configs (MA44 + MA125 on selected intervals)
- configs = [
- IndicatorConfig("ma44", "sma", 44, args.intervals),
- IndicatorConfig("ma125", "sma", 125, args.intervals),
- ]
-
- indicator_engine = IndicatorEngine(db, configs)
- brain = Brain(db, indicator_engine)
- backtester = Backtester(db, indicator_engine, brain)
-
- # Run the backtest
- backtest_id = await backtester.run(
- symbol=args.symbol,
- intervals=args.intervals,
- start=start,
- end=end,
- )
-
- # Print results
- run = await backtester.get_run(backtest_id)
- if run and run.get("results"):
- results = json.loads(run["results"]) if isinstance(run["results"], str) else run["results"]
- print("\n=== Backtest Results ===")
- print(f"ID: {backtest_id}")
- print(f"Strategy: {run['strategy']}")
- print(f"Period: {run['start_time']} to {run['end_time']}")
- print(f"Intervals: {run['intervals']}")
- print(f"Total evaluations: {results.get('total_evaluations', 0)}")
- print(f"Total trades: {results.get('total_trades', 0)}")
- print(f"Win rate: {results.get('win_rate', 0)}%")
- print(f"Total P&L: {results.get('total_pnl_pct', 0)}%")
- print(f"Final Balance: {results.get('final_balance', 0)}")
-
- finally:
- await db.disconnect()
-
-
-if __name__ == "__main__":
- asyncio.run(main())
diff --git a/src/data_collector/brain.py b/src/data_collector/brain.py
index 37ee6fe..a8a7816 100644
--- a/src/data_collector/brain.py
+++ b/src/data_collector/brain.py
@@ -1,49 +1,25 @@
"""
-Brain - Strategy evaluation and decision logging
-Pure strategy logic separated from DB I/O for testability
+Brain - Simplified indicator evaluation
"""
import json
import logging
from dataclasses import dataclass
from datetime import datetime, timezone
-from typing import Dict, Optional, Any, List, Callable
+from typing import Dict, Optional, Any, List
from .database import DatabaseManager
from .indicator_engine import IndicatorEngine
-from src.strategies.base import BaseStrategy, StrategySignal, SignalType
-from src.strategies.ma_strategy import MAStrategy
logger = logging.getLogger(__name__)
-def _create_ma44() -> BaseStrategy:
- return MAStrategy(config={"period": 44})
-
-def _create_ma125() -> BaseStrategy:
- return MAStrategy(config={"period": 125})
-
-STRATEGY_REGISTRY: Dict[str, Callable[[], BaseStrategy]] = {
- "ma_trend": MAStrategy,
- "ma44_strategy": _create_ma44,
- "ma125_strategy": _create_ma125,
-}
-
-def load_strategy(strategy_name: str) -> BaseStrategy:
- """Load a strategy instance from registry"""
- if strategy_name not in STRATEGY_REGISTRY:
- logger.warning(f"Strategy {strategy_name} not found, defaulting to ma_trend")
- strategy_name = "ma_trend"
-
- factory = STRATEGY_REGISTRY[strategy_name]
- return factory()
-
@dataclass
class Decision:
"""A single brain evaluation result"""
time: datetime
symbol: str
interval: str
- decision_type: str # "buy", "sell", "hold" -> Now maps to SignalType
+ decision_type: str # "buy", "sell", "hold"
strategy: str
confidence: float
price_at_decision: float
@@ -71,21 +47,21 @@ class Decision:
class Brain:
"""
- Evaluates market conditions using a loaded Strategy.
+ Evaluates market conditions using indicators.
+ Simplified version without complex strategy plug-ins.
"""
def __init__(
self,
db: DatabaseManager,
indicator_engine: IndicatorEngine,
- strategy: str = "ma44_strategy",
+ strategy: str = "default",
):
self.db = db
self.indicator_engine = indicator_engine
self.strategy_name = strategy
- self.active_strategy: BaseStrategy = load_strategy(strategy)
- logger.info(f"Brain initialized with strategy: {self.active_strategy.name}")
+ logger.info("Brain initialized (Simplified)")
async def evaluate(
self,
@@ -120,23 +96,19 @@ class Brain:
"volume": float(candle["volume"]),
}
- # Delegate to Strategy
- signal: StrategySignal = self.active_strategy.analyze(
- candle_dict, indicators, current_position
- )
-
- # Build decision
+ # Simple crossover logic example if needed, otherwise just return HOLD
+ # For now, we just return a neutral decision as "Strategies" are removed
decision = Decision(
time=timestamp,
symbol=symbol,
interval=interval,
- decision_type=signal.type.value,
+ decision_type="hold",
strategy=self.strategy_name,
- confidence=signal.confidence,
+ confidence=0.0,
price_at_decision=price,
indicator_snapshot=indicators,
candle_snapshot=candle_dict,
- reasoning=signal.reasoning,
+ reasoning="Strategy logic removed - Dashboard shows indicators",
backtest_id=backtest_id,
)
@@ -178,8 +150,6 @@ class Brain:
async def _store_decision(self, decision: Decision) -> None:
"""Write decision to the decisions table"""
- # Note: We might want to skip writing every single HOLD to DB to save space if simulating millions of candles
- # But keeping it for now for full traceability
async with self.db.acquire() as conn:
await conn.execute("""
INSERT INTO decisions (
diff --git a/src/data_collector/simulator.py b/src/data_collector/simulator.py
deleted file mode 100644
index c02d0ba..0000000
--- a/src/data_collector/simulator.py
+++ /dev/null
@@ -1,160 +0,0 @@
-"""
-Simulator
-Handles account accounting, leverage, fees, and position management for backtesting.
-"""
-
-from dataclasses import dataclass
-from typing import Optional, List, Dict, Any
-from datetime import datetime
-from .brain import Decision # We might need to decouple this later, but reusing for now
-
-@dataclass
-class Trade:
- entry_time: datetime
- exit_time: Optional[datetime]
- side: str # 'long' or 'short'
- entry_price: float
- exit_price: Optional[float]
- size: float # Quantity of asset
- leverage: float
- pnl: float = 0.0
- pnl_percent: float = 0.0
- fees: float = 0.0
- status: str = 'open' # 'open', 'closed'
-
-class Account:
- def __init__(self, initial_balance: float = 1000.0, maker_fee: float = 0.0002, taker_fee: float = 0.0005):
- self.initial_balance = initial_balance
- self.balance = initial_balance
- self.equity = initial_balance
- self.maker_fee = maker_fee
- self.taker_fee = taker_fee
- self.trades: List[Trade] = []
- self.current_position: Optional[Trade] = None
- self.margin_used = 0.0
-
- def update_equity(self, current_price: float):
- """Update equity based on unrealized PnL of current position"""
- if not self.current_position:
- self.equity = self.balance
- return
-
- trade = self.current_position
- if trade.side == 'long':
- unrealized_pnl = (current_price - trade.entry_price) * trade.size
- else:
- unrealized_pnl = (trade.entry_price - current_price) * trade.size
-
- self.equity = self.balance + unrealized_pnl
-
- def open_position(self, time: datetime, side: str, price: float, leverage: float = 1.0, portion: float = 1.0):
- """
- Open a position.
- portion: 0.0 to 1.0 (percentage of available balance to use)
- """
- if self.current_position:
- # Already have a position, ignore for now (or could add to it)
- return
-
- # Calculate position size
- # Margin = (Balance * portion)
- # Position Value = Margin * Leverage
- # Size = Position Value / Price
-
- margin_to_use = self.balance * portion
- position_value = margin_to_use * leverage
- size = position_value / price
-
- # Fee (Taker)
- fee = position_value * self.taker_fee
- self.balance -= fee # Deduct fee immediately
-
- self.current_position = Trade(
- entry_time=time,
- exit_time=None,
- side=side,
- entry_price=price,
- exit_price=None,
- size=size,
- leverage=leverage,
- fees=fee
- )
- self.margin_used = margin_to_use
-
- def close_position(self, time: datetime, price: float):
- """Close the current position"""
- if not self.current_position:
- return
-
- trade = self.current_position
- position_value = trade.size * price
-
- # Calculate PnL
- if trade.side == 'long':
- pnl = (price - trade.entry_price) * trade.size
- pnl_pct = (price - trade.entry_price) / trade.entry_price * trade.leverage * 100
- else:
- pnl = (trade.entry_price - price) * trade.size
- pnl_pct = (trade.entry_price - price) / trade.entry_price * trade.leverage * 100
-
- # Fee (Taker)
- fee = position_value * self.taker_fee
- self.balance -= fee
- trade.fees += fee
-
- # Update Balance
- self.balance += pnl
- self.margin_used = 0.0
-
- # Update Trade Record
- trade.exit_time = time
- trade.exit_price = price
- trade.pnl = pnl
- trade.pnl_percent = pnl_pct
- trade.status = 'closed'
-
- self.trades.append(trade)
- self.current_position = None
- self.equity = self.balance
-
- def get_position_dict(self) -> Optional[Dict[str, Any]]:
- if not self.current_position:
- return None
- return {
- 'type': self.current_position.side,
- 'entry_price': self.current_position.entry_price,
- 'size': self.current_position.size,
- 'leverage': self.current_position.leverage
- }
-
- def get_stats(self) -> Dict[str, Any]:
- wins = [t for t in self.trades if t.pnl > 0]
- losses = [t for t in self.trades if t.pnl <= 0]
-
- total_pnl = self.balance - self.initial_balance
- total_pnl_pct = (total_pnl / self.initial_balance) * 100
-
- return {
- "initial_balance": self.initial_balance,
- "final_balance": self.balance,
- "total_pnl": total_pnl,
- "total_pnl_pct": total_pnl_pct,
- "total_trades": len(self.trades),
- "win_count": len(wins),
- "loss_count": len(losses),
- "win_rate": (len(wins) / len(self.trades) * 100) if self.trades else 0.0,
- "max_drawdown": 0.0, # Todo: implement DD tracking
- "trades": [
- {
- "entry_time": t.entry_time.isoformat(),
- "exit_time": t.exit_time.isoformat() if t.exit_time else None,
- "side": t.side,
- "entry_price": t.entry_price,
- "exit_price": t.exit_price,
- "pnl": t.pnl,
- "pnl_pct": t.pnl_percent,
- "fees": t.fees
- }
- for t in self.trades
- ]
- }
diff --git a/src/strategies/base.py b/src/strategies/base.py
deleted file mode 100644
index dc56986..0000000
--- a/src/strategies/base.py
+++ /dev/null
@@ -1,68 +0,0 @@
-"""
-Base Strategy Interface
-All strategies must inherit from this class.
-"""
-
-from abc import ABC, abstractmethod
-from dataclasses import dataclass
-from typing import Dict, Any, List, Optional
-from enum import Enum
-
-class SignalType(Enum):
- OPEN_LONG = "open_long"
- OPEN_SHORT = "open_short"
- CLOSE_LONG = "close_long"
- CLOSE_SHORT = "close_short"
- HOLD = "hold"
-
-@dataclass
-class StrategySignal:
- type: SignalType
- confidence: float
- reasoning: str
-
-class BaseStrategy(ABC):
- def __init__(self, config: Optional[Dict[str, Any]] = None):
- self.config = config or {}
-
- @property
- @abstractmethod
- def name(self) -> str:
- """Unique identifier for the strategy"""
- pass
-
- @property
- @abstractmethod
- def required_indicators(self) -> List[str]:
- """List of indicator names required by this strategy (e.g. ['ma44'])"""
- pass
-
- @property
- @abstractmethod
- def display_name(self) -> str:
- """User-friendly name for display in UI (e.g. 'MA44 Crossover')"""
- pass
-
- @property
- @abstractmethod
- def description(self) -> str:
- """Detailed description of how the strategy works"""
- pass
-
- @abstractmethod
- def analyze(
- self,
- candle: Dict[str, Any],
- indicators: Dict[str, float],
- current_position: Optional[Dict[str, Any]] = None
- ) -> StrategySignal:
- """
- Analyze market data and return a trading signal.
-
- Args:
- candle: Dictionary containing 'close', 'open', 'high', 'low', 'volume', 'time'
- indicators: Dictionary of pre-computed indicator values
- current_position: Details about current open position (if any)
- {'type': 'long'/'short', 'entry_price': float, 'size': float}
- """
- pass
diff --git a/src/strategies/ma_strategy.py b/src/strategies/ma_strategy.py
deleted file mode 100644
index 8a69ab8..0000000
--- a/src/strategies/ma_strategy.py
+++ /dev/null
@@ -1,77 +0,0 @@
-"""
-Moving Average Strategy
-Configurable trend following strategy.
-- Long when Price > MA(period)
-- Short when Price < MA(period)
-"""
-
-from typing import Dict, Any, List, Optional
-from .base import BaseStrategy, StrategySignal, SignalType
-
-class MAStrategy(BaseStrategy):
- """
- Configurable Moving Average Strategy.
-
- Config:
- - period: int - MA period (default: 44)
- """
-
- DEFAULT_PERIOD = 44
-
- @property
- def name(self) -> str:
- return "ma_trend"
-
- @property
- def required_indicators(self) -> List[str]:
- # Dynamic based on config
- period = self.config.get('period', self.DEFAULT_PERIOD)
- return [f"ma{period}"]
-
- @property
- def display_name(self) -> str:
- return "MA Strategy"
-
- @property
- def description(self) -> str:
- return "Configurable Moving Average strategy. Parameters: period (5-500, default: 44). Goes long when price > MA(period), short when price < MA(period). Optional multi-timeframe trend filter available."
-
- def analyze(
- self,
- candle: Dict[str, Any],
- indicators: Dict[str, float],
- current_position: Optional[Dict[str, Any]] = None
- ) -> StrategySignal:
-
- period = self.config.get('period', self.DEFAULT_PERIOD)
- ma_key = f"ma{period}"
-
- price = candle['close']
- ma_value = indicators.get(ma_key)
-
- if ma_value is None:
- return StrategySignal(SignalType.HOLD, 0.0, f"MA{period} not available")
-
- # Current position state
- is_long = current_position and current_position.get('type') == 'long'
- is_short = current_position and current_position.get('type') == 'short'
-
- # Logic: Price > MA -> Bullish
- if price > ma_value:
- if is_long:
- return StrategySignal(SignalType.HOLD, 1.0, f"Price {price:.2f} > MA{period} {ma_value:.2f}. Stay Long.")
- elif is_short:
- return StrategySignal(SignalType.CLOSE_SHORT, 1.0, f"Price {price:.2f} crossed above MA{period} {ma_value:.2f}. Close Short.")
- else:
- return StrategySignal(SignalType.OPEN_LONG, 1.0, f"Price {price:.2f} > MA{period} {ma_value:.2f}. Open Long.")
-
- # Logic: Price < MA -> Bearish
- elif price < ma_value:
- if is_short:
- return StrategySignal(SignalType.HOLD, 1.0, f"Price {price:.2f} < MA{period} {ma_value:.2f}. Stay Short.")
- elif is_long:
- return StrategySignal(SignalType.CLOSE_LONG, 1.0, f"Price {price:.2f} crossed below MA{period} {ma_value:.2f}. Close Long.")
- else:
- return StrategySignal(SignalType.OPEN_SHORT, 1.0, f"Price {price:.2f} < MA{period} {ma_value:.2f}. Open Short.")
-
- return StrategySignal(SignalType.HOLD, 0.0, f"Price == MA{period}")