Remove candle from backtest
This commit is contained in:
@@ -213,7 +213,8 @@ public class BacktestController : BaseController
|
||||
request.StartDate,
|
||||
request.EndDate,
|
||||
user,
|
||||
request.Save);
|
||||
request.Save,
|
||||
request.WithCandles);
|
||||
|
||||
await NotifyBacktesingSubscriberAsync(backtestResult);
|
||||
|
||||
@@ -274,4 +275,10 @@ public class RunBacktestRequest
|
||||
/// Whether to save the backtest results
|
||||
/// </summary>
|
||||
public bool Save { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to include candles and indicators values in the response.
|
||||
/// Set to false to reduce response size dramatically.
|
||||
/// </summary>
|
||||
public bool WithCandles { get; set; } = false;
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
using Managing.Api.Models.Responses;
|
||||
using Managing.Api.Models.Requests;
|
||||
using Managing.Api.Models.Responses;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Hubs;
|
||||
using Managing.Application.ManageBot.Commands;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
using Managing.Domain.Trades;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@@ -30,6 +34,7 @@ public class DataController : ControllerBase
|
||||
private readonly IStatisticService _statisticService;
|
||||
private readonly IHubContext<CandleHub> _hubContext;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ITradingService _tradingService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataController"/> class.
|
||||
@@ -40,13 +45,15 @@ public class DataController : ControllerBase
|
||||
/// <param name="statisticService">Service for statistical analysis.</param>
|
||||
/// <param name="hubContext">SignalR hub context for real-time communication.</param>
|
||||
/// <param name="mediator">Mediator for handling commands and queries.</param>
|
||||
/// <param name="tradingService">Service for trading operations.</param>
|
||||
public DataController(
|
||||
IExchangeService exchangeService,
|
||||
IAccountService accountService,
|
||||
ICacheService cacheService,
|
||||
IStatisticService statisticService,
|
||||
IHubContext<CandleHub> hubContext,
|
||||
IMediator mediator)
|
||||
IMediator mediator,
|
||||
ITradingService tradingService)
|
||||
{
|
||||
_exchangeService = exchangeService;
|
||||
_accountService = accountService;
|
||||
@@ -54,6 +61,7 @@ public class DataController : ControllerBase
|
||||
_statisticService = statisticService;
|
||||
_hubContext = hubContext;
|
||||
_mediator = mediator;
|
||||
_tradingService = tradingService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -211,19 +219,55 @@ public class DataController : ControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves candle data for a given exchange, ticker, start date, and timeframe.
|
||||
/// Retrieves candles with indicators values for backtest details display.
|
||||
/// </summary>
|
||||
/// <param name="exchange">The exchange to retrieve candles from.</param>
|
||||
/// <param name="ticker">The ticker symbol to retrieve candles for.</param>
|
||||
/// <param name="startDate">The start date for the candle data.</param>
|
||||
/// <param name="timeframe">The timeframe for the candle data.</param>
|
||||
/// <returns>A list of <see cref="Candle"/> objects.</returns>
|
||||
/// <param name="exchange">The trading exchange.</param>
|
||||
/// <param name="ticker">The ticker symbol.</param>
|
||||
/// <param name="startDate">The start date for the candles.</param>
|
||||
/// <param name="endDate">The end date for the candles.</param>
|
||||
/// <param name="timeframe">The timeframe for the candles.</param>
|
||||
/// <param name="scenario">The scenario object to calculate indicators values (optional).</param>
|
||||
/// <returns>A response containing candles and indicators values.</returns>
|
||||
[Authorize]
|
||||
[HttpGet("GetCandles")]
|
||||
public async Task<ActionResult<List<Candle>>> GetCandles(TradingExchanges exchange, Ticker ticker,
|
||||
DateTime startDate, Timeframe timeframe)
|
||||
[HttpPost("GetCandlesWithIndicators")]
|
||||
public async Task<ActionResult<CandlesWithIndicatorsResponse>> GetCandlesWithIndicators(
|
||||
[FromBody] GetCandlesWithIndicatorsRequest request)
|
||||
{
|
||||
return Ok(await _exchangeService.GetCandlesInflux(exchange, ticker, startDate, timeframe));
|
||||
try
|
||||
{
|
||||
// Get candles for the specified period
|
||||
var candles = await _exchangeService.GetCandlesInflux(TradingExchanges.Evm, request.Ticker,
|
||||
request.StartDate, request.Timeframe, request.EndDate);
|
||||
|
||||
if (candles == null || candles.Count == 0)
|
||||
{
|
||||
return Ok(new CandlesWithIndicatorsResponse
|
||||
{
|
||||
Candles = new List<Candle>(),
|
||||
IndicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>()
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate indicators values if scenario is provided
|
||||
Dictionary<IndicatorType, IndicatorsResultBase> indicatorsValues = null;
|
||||
if (request.Scenario != null && request.Scenario.Indicators != null &&
|
||||
request.Scenario.Indicators.Count > 0)
|
||||
{
|
||||
// Map ScenarioRequest to domain Scenario object
|
||||
var domainScenario = MapScenarioRequestToScenario(request.Scenario);
|
||||
indicatorsValues = await _tradingService.CalculateIndicatorsValuesAsync(domainScenario, candles);
|
||||
}
|
||||
|
||||
return Ok(new CandlesWithIndicatorsResponse
|
||||
{
|
||||
Candles = candles,
|
||||
IndicatorsValues = indicatorsValues ?? new Dictionary<IndicatorType, IndicatorsResultBase>()
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"Error retrieving candles with indicators: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -605,4 +649,35 @@ public class DataController : ControllerBase
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a ScenarioRequest to a domain Scenario object.
|
||||
/// </summary>
|
||||
/// <param name="scenarioRequest">The scenario request to map.</param>
|
||||
/// <returns>A domain Scenario object.</returns>
|
||||
private Scenario MapScenarioRequestToScenario(ScenarioRequest scenarioRequest)
|
||||
{
|
||||
var scenario = new Scenario(scenarioRequest.Name, scenarioRequest.LoopbackPeriod);
|
||||
|
||||
foreach (var indicatorRequest in scenarioRequest.Indicators)
|
||||
{
|
||||
var indicator = new Indicator(indicatorRequest.Name, indicatorRequest.Type)
|
||||
{
|
||||
SignalType = indicatorRequest.SignalType,
|
||||
MinimumHistory = indicatorRequest.MinimumHistory,
|
||||
Period = indicatorRequest.Period,
|
||||
FastPeriods = indicatorRequest.FastPeriods,
|
||||
SlowPeriods = indicatorRequest.SlowPeriods,
|
||||
SignalPeriods = indicatorRequest.SignalPeriods,
|
||||
Multiplier = indicatorRequest.Multiplier,
|
||||
SmoothPeriods = indicatorRequest.SmoothPeriods,
|
||||
StochPeriods = indicatorRequest.StochPeriods,
|
||||
CyclePeriods = indicatorRequest.CyclePeriods
|
||||
};
|
||||
|
||||
scenario.AddIndicator(indicator);
|
||||
}
|
||||
|
||||
return scenario;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.Models.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Request model for getting candles with indicators.
|
||||
/// </summary>
|
||||
public class GetCandlesWithIndicatorsRequest
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The ticker symbol.
|
||||
/// </summary>
|
||||
public Ticker Ticker { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start date for the candle data.
|
||||
/// </summary>
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The end date for the candle data.
|
||||
/// </summary>
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The timeframe for the candles.
|
||||
/// </summary>
|
||||
public Timeframe Timeframe { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional scenario for calculating indicators.
|
||||
/// </summary>
|
||||
public ScenarioRequest Scenario { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.Models.Responses;
|
||||
|
||||
/// <summary>
|
||||
/// Response model for candles with indicators values.
|
||||
/// </summary>
|
||||
public class CandlesWithIndicatorsResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of candles.
|
||||
/// </summary>
|
||||
public List<Candle> Candles { get; set; } = new List<Candle>();
|
||||
|
||||
/// <summary>
|
||||
/// The calculated indicators values.
|
||||
/// </summary>
|
||||
public Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; } = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||
}
|
||||
@@ -16,13 +16,15 @@ namespace Managing.Application.Abstractions.Services
|
||||
/// <param name="endDate">The end date for the backtest</param>
|
||||
/// <param name="user">The user running the backtest (optional)</param>
|
||||
/// <param name="save">Whether to save the backtest results</param>
|
||||
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
||||
/// <returns>The backtest results</returns>
|
||||
Task<Backtest> RunTradingBotBacktest(
|
||||
TradingBotConfig config,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
User user = null,
|
||||
bool save = false);
|
||||
bool save = false,
|
||||
bool withCandles = false);
|
||||
|
||||
/// <summary>
|
||||
/// Runs a trading bot backtest with pre-loaded candles.
|
||||
@@ -31,11 +33,13 @@ namespace Managing.Application.Abstractions.Services
|
||||
/// <param name="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
|
||||
/// <param name="candles">The candles to use for backtesting</param>
|
||||
/// <param name="user">The user running the backtest (optional)</param>
|
||||
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
||||
/// <returns>The backtest results</returns>
|
||||
Task<Backtest> RunTradingBotBacktest(
|
||||
TradingBotConfig config,
|
||||
List<Candle> candles,
|
||||
User user = null);
|
||||
User user = null,
|
||||
bool withCandles = false);
|
||||
|
||||
// Additional methods for backtest management
|
||||
bool DeleteBacktest(string id);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
using Managing.Domain.Synth.Models;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Evm.Models.Privy;
|
||||
@@ -50,4 +52,14 @@ public interface ITradingService
|
||||
|
||||
Task<SynthRiskResult> MonitorSynthPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice,
|
||||
decimal liquidationPrice, string positionIdentifier, TradingBotConfig botConfig);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates indicators values for a given scenario and candles.
|
||||
/// </summary>
|
||||
/// <param name="scenario">The scenario containing indicators.</param>
|
||||
/// <param name="candles">The candles to calculate indicators for.</param>
|
||||
/// <returns>A dictionary of indicator types to their calculated values.</returns>
|
||||
Task<Dictionary<IndicatorType, IndicatorsResultBase>> CalculateIndicatorsValuesAsync(
|
||||
Scenario scenario,
|
||||
List<Candle> candles);
|
||||
}
|
||||
@@ -87,7 +87,7 @@ namespace Managing.Application.Tests
|
||||
};
|
||||
|
||||
// Act
|
||||
var backtestResult = await _backtester.RunTradingBotBacktest(config, localCandles.TakeLast(500).ToList());
|
||||
var backtestResult = await _backtester.RunTradingBotBacktest(config, localCandles.TakeLast(500).ToList(), null, false);
|
||||
|
||||
var json = JsonConvert.SerializeObject(backtestResult, Formatting.None);
|
||||
File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-{Guid.NewGuid()}.json", json);
|
||||
@@ -138,7 +138,7 @@ namespace Managing.Application.Tests
|
||||
|
||||
// Act
|
||||
var backtestResult = await _backtester.RunTradingBotBacktest(config, DateTime.UtcNow.AddDays(-6),
|
||||
DateTime.UtcNow, null, false);
|
||||
DateTime.UtcNow, null, false, false);
|
||||
//WriteCsvReport(backtestResult.GetStringReport());
|
||||
|
||||
// Assert
|
||||
@@ -188,7 +188,7 @@ namespace Managing.Application.Tests
|
||||
|
||||
// Act
|
||||
var backtestResult = await _backtester.RunTradingBotBacktest(config, DateTime.UtcNow.AddDays(-6),
|
||||
DateTime.UtcNow, null, false);
|
||||
DateTime.UtcNow, null, false, false);
|
||||
WriteCsvReport(backtestResult.GetStringReport());
|
||||
|
||||
// Assert
|
||||
@@ -275,7 +275,7 @@ namespace Managing.Application.Tests
|
||||
FlipOnlyWhenInProfit = true,
|
||||
MaxPositionTimeHours = null,
|
||||
CloseEarlyWhenProfitable = false
|
||||
}, candles, null).Result,
|
||||
}, candles, null, false).Result,
|
||||
BotType.FlippingBot => _backtester.RunTradingBotBacktest(new TradingBotConfig
|
||||
{
|
||||
AccountName = _account.Name,
|
||||
@@ -294,7 +294,7 @@ namespace Managing.Application.Tests
|
||||
FlipOnlyWhenInProfit = true,
|
||||
MaxPositionTimeHours = null,
|
||||
CloseEarlyWhenProfitable = false
|
||||
}, candles, null).Result,
|
||||
}, candles, null, false).Result,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
timer.Stop();
|
||||
|
||||
@@ -287,6 +287,7 @@ public class StatisticService : IStatisticService
|
||||
DateTime.Now.AddDays(-7),
|
||||
DateTime.Now,
|
||||
null,
|
||||
false,
|
||||
false);
|
||||
|
||||
return backtest.Signals;
|
||||
|
||||
@@ -65,18 +65,20 @@ namespace Managing.Application.Backtesting
|
||||
/// <param name="endDate">The end date for the backtest</param>
|
||||
/// <param name="user">The user running the backtest (optional)</param>
|
||||
/// <param name="save">Whether to save the backtest results</param>
|
||||
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
||||
/// <returns>The backtest results</returns>
|
||||
public async Task<Backtest> RunTradingBotBacktest(
|
||||
TradingBotConfig config,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
User user = null,
|
||||
bool save = false)
|
||||
bool save = false,
|
||||
bool withCandles = false)
|
||||
{
|
||||
var account = await GetAccountFromConfig(config);
|
||||
var candles = GetCandles(account, config.Ticker, config.Timeframe, startDate, endDate);
|
||||
|
||||
var result = await RunBacktestWithCandles(config, candles, user);
|
||||
var result = await RunBacktestWithCandles(config, candles, user, withCandles);
|
||||
|
||||
// Set start and end dates
|
||||
result.StartDate = startDate;
|
||||
@@ -97,13 +99,15 @@ namespace Managing.Application.Backtesting
|
||||
/// <param name="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
|
||||
/// <param name="candles">The candles to use for backtesting</param>
|
||||
/// <param name="user">The user running the backtest (optional)</param>
|
||||
/// <param name="withCandles">Whether to include candles and indicators values in the response</param>
|
||||
/// <returns>The backtest results</returns>
|
||||
public async Task<Backtest> RunTradingBotBacktest(
|
||||
TradingBotConfig config,
|
||||
List<Candle> candles,
|
||||
User user = null)
|
||||
User user = null,
|
||||
bool withCandles = false)
|
||||
{
|
||||
return await RunBacktestWithCandles(config, candles, user);
|
||||
return await RunBacktestWithCandles(config, candles, user, withCandles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -112,7 +116,8 @@ namespace Managing.Application.Backtesting
|
||||
private async Task<Backtest> RunBacktestWithCandles(
|
||||
TradingBotConfig config,
|
||||
List<Candle> candles,
|
||||
User user = null)
|
||||
User user = null,
|
||||
bool withCandles = false)
|
||||
{
|
||||
var tradingBot = _botFactory.CreateBacktestTradingBot(config);
|
||||
|
||||
@@ -128,7 +133,7 @@ namespace Managing.Application.Backtesting
|
||||
tradingBot.User = user;
|
||||
await tradingBot.LoadAccount();
|
||||
|
||||
var result = GetBacktestingResult(config, tradingBot, candles);
|
||||
var result = GetBacktestingResult(config, tradingBot, candles, withCandles);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
@@ -168,7 +173,8 @@ namespace Managing.Application.Backtesting
|
||||
private Backtest GetBacktestingResult(
|
||||
TradingBotConfig config,
|
||||
ITradingBot bot,
|
||||
List<Candle> candles)
|
||||
List<Candle> candles,
|
||||
bool withCandles = false)
|
||||
{
|
||||
if (candles == null || candles.Count == 0)
|
||||
{
|
||||
@@ -211,7 +217,12 @@ namespace Managing.Application.Backtesting
|
||||
|
||||
bot.Candles = new HashSet<Candle>(candles);
|
||||
|
||||
var indicatorsValues = GetIndicatorsValues(bot.Config.Scenario.Indicators, candles);
|
||||
// Only calculate indicators values if withCandles is true
|
||||
Dictionary<IndicatorType, IndicatorsResultBase> indicatorsValues = null;
|
||||
if (withCandles)
|
||||
{
|
||||
indicatorsValues = GetIndicatorsValues(bot.Config.Scenario.Indicators, candles);
|
||||
}
|
||||
|
||||
var finalPnl = bot.GetProfitAndLoss();
|
||||
var winRate = bot.GetWinRate();
|
||||
@@ -236,7 +247,8 @@ namespace Managing.Application.Backtesting
|
||||
|
||||
var score = BacktestScorer.CalculateTotalScore(scoringParams);
|
||||
|
||||
var result = new Backtest(config, bot.Positions, bot.Signals.ToList(), candles)
|
||||
// Create backtest result with conditional candles and indicators values
|
||||
var result = new Backtest(config, bot.Positions, bot.Signals.ToList(), withCandles ? candles : new List<Candle>())
|
||||
{
|
||||
FinalPnl = finalPnl,
|
||||
WinRate = winRate,
|
||||
@@ -246,7 +258,7 @@ namespace Managing.Application.Backtesting
|
||||
WalletBalances = bot.WalletBalances.ToList(),
|
||||
Statistics = stats,
|
||||
OptimizedMoneyManagement = optimizedMoneyManagement,
|
||||
IndicatorsValues = AggregateValues(indicatorsValues, bot.IndicatorsValues),
|
||||
IndicatorsValues = withCandles ? AggregateValues(indicatorsValues, bot.IndicatorsValues) : new Dictionary<IndicatorType, IndicatorsResultBase>(),
|
||||
Score = score,
|
||||
Id = Guid.NewGuid().ToString()
|
||||
};
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Core.FixedSizedQueue;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
using Managing.Domain.Synth.Models;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Evm.Models.Privy;
|
||||
@@ -423,4 +426,50 @@ public class TradingService : ITradingService
|
||||
return await _synthPredictionService.MonitorPositionRiskAsync(ticker, direction, currentPrice, liquidationPrice,
|
||||
positionIdentifier, botConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates indicators values for a given scenario and candles.
|
||||
/// </summary>
|
||||
/// <param name="scenario">The scenario containing indicators.</param>
|
||||
/// <param name="candles">The candles to calculate indicators for.</param>
|
||||
/// <returns>A dictionary of indicator types to their calculated values.</returns>
|
||||
public async Task<Dictionary<IndicatorType, IndicatorsResultBase>> CalculateIndicatorsValuesAsync(
|
||||
Scenario scenario,
|
||||
List<Candle> candles)
|
||||
{
|
||||
var indicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||
|
||||
if (scenario?.Indicators == null || scenario.Indicators.Count == 0)
|
||||
{
|
||||
return indicatorsValues;
|
||||
}
|
||||
|
||||
// Convert candles to FixedSizeQueue for indicators
|
||||
var fixedCandles = new FixedSizeQueue<Candle>(10000);
|
||||
foreach (var candle in candles)
|
||||
{
|
||||
fixedCandles.Enqueue(candle);
|
||||
}
|
||||
|
||||
// Build indicators from scenario
|
||||
foreach (var indicator in scenario.Indicators)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Build the indicator using ScenarioHelpers
|
||||
var builtIndicator = ScenarioHelpers.BuildIndicator(indicator, 10000);
|
||||
builtIndicator.Candles = fixedCandles;
|
||||
|
||||
indicatorsValues[indicator.Type] = builtIndicator.GetIndicatorValues();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the error but continue with other indicators
|
||||
_logger.LogError(ex, "Error calculating indicator {IndicatorName}: {ErrorMessage}",
|
||||
indicator.Name, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return indicatorsValues;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,15 @@
|
||||
import {CardPositionItem, TradeChart} from '..'
|
||||
import {Backtest} from '../../../generated/ManagingApi'
|
||||
import {
|
||||
Backtest,
|
||||
CandlesWithIndicatorsResponse,
|
||||
DataClient,
|
||||
GetCandlesWithIndicatorsRequest,
|
||||
IndicatorType,
|
||||
SignalType
|
||||
} from '../../../generated/ManagingApi'
|
||||
import {CardPosition, CardText} from '../../mollecules'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
|
||||
interface IBacktestRowDetailsProps {
|
||||
backtest: Backtest;
|
||||
@@ -9,11 +18,60 @@ interface IBacktestRowDetailsProps {
|
||||
const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
backtest
|
||||
}) => {
|
||||
const {apiUrl} = useApiUrlStore();
|
||||
const dataClient = new DataClient({}, apiUrl);
|
||||
|
||||
// Use TanStack Query to load candles with indicators
|
||||
const {data: candlesData, isLoading: isLoadingCandles, error} = useQuery({
|
||||
queryKey: ['candlesWithIndicators', backtest.id, backtest.config?.scenario?.name],
|
||||
queryFn: async (): Promise<CandlesWithIndicatorsResponse> => {
|
||||
// Only fetch if no candles are present
|
||||
if (backtest.candles && backtest.candles.length > 0) {
|
||||
return {
|
||||
candles: backtest.candles,
|
||||
indicatorsValues: backtest.indicatorsValues || {}
|
||||
};
|
||||
}
|
||||
|
||||
const request: GetCandlesWithIndicatorsRequest = {
|
||||
ticker: backtest.config.ticker,
|
||||
startDate: backtest.startDate,
|
||||
endDate: backtest.endDate,
|
||||
timeframe: backtest.config.timeframe,
|
||||
scenario: backtest.config?.scenario ? {
|
||||
name: backtest.config.scenario.name || '',
|
||||
indicators: backtest.config.scenario.indicators?.map(indicator => ({
|
||||
name: indicator.name || '',
|
||||
type: indicator.type || IndicatorType.RsiDivergence,
|
||||
signalType: indicator.signalType || SignalType.Signal,
|
||||
minimumHistory: indicator.minimumHistory || 0,
|
||||
period: indicator.period,
|
||||
fastPeriods: indicator.fastPeriods,
|
||||
slowPeriods: indicator.slowPeriods,
|
||||
signalPeriods: indicator.signalPeriods,
|
||||
multiplier: indicator.multiplier,
|
||||
smoothPeriods: indicator.smoothPeriods,
|
||||
stochPeriods: indicator.stochPeriods,
|
||||
cyclePeriods: indicator.cyclePeriods
|
||||
})) || [],
|
||||
loopbackPeriod: backtest.config.scenario.loopbackPeriod
|
||||
} : undefined
|
||||
};
|
||||
|
||||
return await dataClient.data_GetCandlesWithIndicators(request);
|
||||
},
|
||||
enabled: !backtest.candles || backtest.candles.length === 0, // Only run query if no candles exist
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime)
|
||||
});
|
||||
|
||||
// Use the data from query or fallback to backtest data
|
||||
const candles = candlesData?.candles || backtest.candles || [];
|
||||
const indicatorsValues = candlesData?.indicatorsValues || backtest.indicatorsValues || {};
|
||||
|
||||
const {
|
||||
candles,
|
||||
positions,
|
||||
walletBalances,
|
||||
indicatorsValues,
|
||||
signals,
|
||||
statistics,
|
||||
config
|
||||
@@ -243,6 +301,12 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-flow-row">
|
||||
{isLoadingCandles && (
|
||||
<div className="flex justify-center items-center p-4">
|
||||
<div className="loading loading-spinner loading-lg"></div>
|
||||
<span className="ml-2">Loading candles with indicators...</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-4 p-5">
|
||||
<CardPosition
|
||||
positivePosition={true}
|
||||
@@ -351,18 +415,20 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
content={getAverageTradesPerDay() + " trades/day"}
|
||||
></CardText>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<figure className="w-full">
|
||||
<TradeChart
|
||||
candles={candles}
|
||||
positions={positions}
|
||||
walletBalances={walletBalances}
|
||||
indicatorsValues={indicatorsValues}
|
||||
signals={signals}
|
||||
height={1000}
|
||||
></TradeChart>
|
||||
</figure>
|
||||
</div>
|
||||
{!isLoadingCandles && (
|
||||
<div className="w-full">
|
||||
<figure className="w-full">
|
||||
<TradeChart
|
||||
candles={candles}
|
||||
positions={positions}
|
||||
walletBalances={walletBalances}
|
||||
indicatorsValues={indicatorsValues}
|
||||
signals={signals}
|
||||
height={1000}
|
||||
></TradeChart>
|
||||
</figure>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1087,29 +1087,17 @@ export class DataClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<SpotlightOverview>(null as any);
|
||||
}
|
||||
|
||||
data_GetCandles(exchange: TradingExchanges | undefined, ticker: Ticker | undefined, startDate: Date | undefined, timeframe: Timeframe | undefined): Promise<Candle[]> {
|
||||
let url_ = this.baseUrl + "/Data/GetCandles?";
|
||||
if (exchange === null)
|
||||
throw new Error("The parameter 'exchange' cannot be null.");
|
||||
else if (exchange !== undefined)
|
||||
url_ += "exchange=" + encodeURIComponent("" + exchange) + "&";
|
||||
if (ticker === null)
|
||||
throw new Error("The parameter 'ticker' cannot be null.");
|
||||
else if (ticker !== undefined)
|
||||
url_ += "ticker=" + encodeURIComponent("" + ticker) + "&";
|
||||
if (startDate === null)
|
||||
throw new Error("The parameter 'startDate' cannot be null.");
|
||||
else if (startDate !== undefined)
|
||||
url_ += "startDate=" + encodeURIComponent(startDate ? "" + startDate.toISOString() : "") + "&";
|
||||
if (timeframe === null)
|
||||
throw new Error("The parameter 'timeframe' cannot be null.");
|
||||
else if (timeframe !== undefined)
|
||||
url_ += "timeframe=" + encodeURIComponent("" + timeframe) + "&";
|
||||
data_GetCandlesWithIndicators(request: GetCandlesWithIndicatorsRequest): Promise<CandlesWithIndicatorsResponse> {
|
||||
let url_ = this.baseUrl + "/Data/GetCandlesWithIndicators";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(request);
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "GET",
|
||||
body: content_,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
@@ -1117,17 +1105,17 @@ export class DataClient extends AuthorizedApiBase {
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processData_GetCandles(_response);
|
||||
return this.processData_GetCandlesWithIndicators(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processData_GetCandles(response: Response): Promise<Candle[]> {
|
||||
protected processData_GetCandlesWithIndicators(response: Response): Promise<CandlesWithIndicatorsResponse> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Candle[];
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as CandlesWithIndicatorsResponse;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
@@ -1135,7 +1123,7 @@ export class DataClient extends AuthorizedApiBase {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<Candle[]>(null as any);
|
||||
return Promise.resolve<CandlesWithIndicatorsResponse>(null as any);
|
||||
}
|
||||
|
||||
data_GetStrategiesStatistics(): Promise<StrategiesStatisticsViewModel> {
|
||||
@@ -3470,6 +3458,7 @@ export interface RunBacktestRequest {
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
save?: boolean;
|
||||
withCandles?: boolean;
|
||||
}
|
||||
|
||||
export interface TradingBotConfigRequest {
|
||||
@@ -3589,6 +3578,19 @@ export interface TickerSignal {
|
||||
oneDay: Signal[];
|
||||
}
|
||||
|
||||
export interface CandlesWithIndicatorsResponse {
|
||||
candles?: Candle[] | null;
|
||||
indicatorsValues?: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; } | null;
|
||||
}
|
||||
|
||||
export interface GetCandlesWithIndicatorsRequest {
|
||||
ticker?: Ticker;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
timeframe?: Timeframe;
|
||||
scenario?: ScenarioRequest | null;
|
||||
}
|
||||
|
||||
export interface StrategiesStatisticsViewModel {
|
||||
totalStrategiesRunning?: number;
|
||||
changeInLast24Hours?: number;
|
||||
|
||||
@@ -598,6 +598,7 @@ export interface RunBacktestRequest {
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
save?: boolean;
|
||||
withCandles?: boolean;
|
||||
}
|
||||
|
||||
export interface TradingBotConfigRequest {
|
||||
@@ -717,6 +718,19 @@ export interface TickerSignal {
|
||||
oneDay: Signal[];
|
||||
}
|
||||
|
||||
export interface CandlesWithIndicatorsResponse {
|
||||
candles?: Candle[] | null;
|
||||
indicatorsValues?: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; } | null;
|
||||
}
|
||||
|
||||
export interface GetCandlesWithIndicatorsRequest {
|
||||
ticker?: Ticker;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
timeframe?: Timeframe;
|
||||
scenario?: ScenarioRequest | null;
|
||||
}
|
||||
|
||||
export interface StrategiesStatisticsViewModel {
|
||||
totalStrategiesRunning?: number;
|
||||
changeInLast24Hours?: number;
|
||||
|
||||
Reference in New Issue
Block a user