Remove candle from backtest
This commit is contained in:
@@ -213,7 +213,8 @@ public class BacktestController : BaseController
|
|||||||
request.StartDate,
|
request.StartDate,
|
||||||
request.EndDate,
|
request.EndDate,
|
||||||
user,
|
user,
|
||||||
request.Save);
|
request.Save,
|
||||||
|
request.WithCandles);
|
||||||
|
|
||||||
await NotifyBacktesingSubscriberAsync(backtestResult);
|
await NotifyBacktesingSubscriberAsync(backtestResult);
|
||||||
|
|
||||||
@@ -274,4 +275,10 @@ public class RunBacktestRequest
|
|||||||
/// Whether to save the backtest results
|
/// Whether to save the backtest results
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Save { get; set; } = false;
|
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;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Hubs;
|
using Managing.Application.Hubs;
|
||||||
using Managing.Application.ManageBot.Commands;
|
using Managing.Application.ManageBot.Commands;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Shared.Helpers;
|
using Managing.Domain.Shared.Helpers;
|
||||||
using Managing.Domain.Statistics;
|
using Managing.Domain.Statistics;
|
||||||
|
using Managing.Domain.Strategies;
|
||||||
|
using Managing.Domain.Strategies.Base;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@@ -30,6 +34,7 @@ public class DataController : ControllerBase
|
|||||||
private readonly IStatisticService _statisticService;
|
private readonly IStatisticService _statisticService;
|
||||||
private readonly IHubContext<CandleHub> _hubContext;
|
private readonly IHubContext<CandleHub> _hubContext;
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
|
private readonly ITradingService _tradingService;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DataController"/> class.
|
/// 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="statisticService">Service for statistical analysis.</param>
|
||||||
/// <param name="hubContext">SignalR hub context for real-time communication.</param>
|
/// <param name="hubContext">SignalR hub context for real-time communication.</param>
|
||||||
/// <param name="mediator">Mediator for handling commands and queries.</param>
|
/// <param name="mediator">Mediator for handling commands and queries.</param>
|
||||||
|
/// <param name="tradingService">Service for trading operations.</param>
|
||||||
public DataController(
|
public DataController(
|
||||||
IExchangeService exchangeService,
|
IExchangeService exchangeService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
ICacheService cacheService,
|
ICacheService cacheService,
|
||||||
IStatisticService statisticService,
|
IStatisticService statisticService,
|
||||||
IHubContext<CandleHub> hubContext,
|
IHubContext<CandleHub> hubContext,
|
||||||
IMediator mediator)
|
IMediator mediator,
|
||||||
|
ITradingService tradingService)
|
||||||
{
|
{
|
||||||
_exchangeService = exchangeService;
|
_exchangeService = exchangeService;
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
@@ -54,6 +61,7 @@ public class DataController : ControllerBase
|
|||||||
_statisticService = statisticService;
|
_statisticService = statisticService;
|
||||||
_hubContext = hubContext;
|
_hubContext = hubContext;
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
|
_tradingService = tradingService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -211,19 +219,55 @@ public class DataController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves candle data for a given exchange, ticker, start date, and timeframe.
|
/// Retrieves candles with indicators values for backtest details display.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="exchange">The exchange to retrieve candles from.</param>
|
/// <param name="exchange">The trading exchange.</param>
|
||||||
/// <param name="ticker">The ticker symbol to retrieve candles for.</param>
|
/// <param name="ticker">The ticker symbol.</param>
|
||||||
/// <param name="startDate">The start date for the candle data.</param>
|
/// <param name="startDate">The start date for the candles.</param>
|
||||||
/// <param name="timeframe">The timeframe for the candle data.</param>
|
/// <param name="endDate">The end date for the candles.</param>
|
||||||
/// <returns>A list of <see cref="Candle"/> objects.</returns>
|
/// <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]
|
[Authorize]
|
||||||
[HttpGet("GetCandles")]
|
[HttpPost("GetCandlesWithIndicators")]
|
||||||
public async Task<ActionResult<List<Candle>>> GetCandles(TradingExchanges exchange, Ticker ticker,
|
public async Task<ActionResult<CandlesWithIndicatorsResponse>> GetCandlesWithIndicators(
|
||||||
DateTime startDate, Timeframe timeframe)
|
[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>
|
/// <summary>
|
||||||
@@ -605,4 +649,35 @@ public class DataController : ControllerBase
|
|||||||
|
|
||||||
return Ok(response);
|
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="endDate">The end date for the backtest</param>
|
||||||
/// <param name="user">The user running the backtest (optional)</param>
|
/// <param name="user">The user running the backtest (optional)</param>
|
||||||
/// <param name="save">Whether to save the backtest results</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>
|
/// <returns>The backtest results</returns>
|
||||||
Task<Backtest> RunTradingBotBacktest(
|
Task<Backtest> RunTradingBotBacktest(
|
||||||
TradingBotConfig config,
|
TradingBotConfig config,
|
||||||
DateTime startDate,
|
DateTime startDate,
|
||||||
DateTime endDate,
|
DateTime endDate,
|
||||||
User user = null,
|
User user = null,
|
||||||
bool save = false);
|
bool save = false,
|
||||||
|
bool withCandles = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a trading bot backtest with pre-loaded candles.
|
/// 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="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
|
||||||
/// <param name="candles">The candles to use for backtesting</param>
|
/// <param name="candles">The candles to use for backtesting</param>
|
||||||
/// <param name="user">The user running the backtest (optional)</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>
|
/// <returns>The backtest results</returns>
|
||||||
Task<Backtest> RunTradingBotBacktest(
|
Task<Backtest> RunTradingBotBacktest(
|
||||||
TradingBotConfig config,
|
TradingBotConfig config,
|
||||||
List<Candle> candles,
|
List<Candle> candles,
|
||||||
User user = null);
|
User user = null,
|
||||||
|
bool withCandles = false);
|
||||||
|
|
||||||
// Additional methods for backtest management
|
// Additional methods for backtest management
|
||||||
bool DeleteBacktest(string id);
|
bool DeleteBacktest(string id);
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Statistics;
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
|
using Managing.Domain.Strategies.Base;
|
||||||
using Managing.Domain.Synth.Models;
|
using Managing.Domain.Synth.Models;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Infrastructure.Evm.Models.Privy;
|
using Managing.Infrastructure.Evm.Models.Privy;
|
||||||
@@ -50,4 +52,14 @@ public interface ITradingService
|
|||||||
|
|
||||||
Task<SynthRiskResult> MonitorSynthPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice,
|
Task<SynthRiskResult> MonitorSynthPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice,
|
||||||
decimal liquidationPrice, string positionIdentifier, TradingBotConfig botConfig);
|
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
|
// 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);
|
var json = JsonConvert.SerializeObject(backtestResult, Formatting.None);
|
||||||
File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-{Guid.NewGuid()}.json", json);
|
File.WriteAllText($"{ticker.ToString()}-{timeframe.ToString()}-{Guid.NewGuid()}.json", json);
|
||||||
@@ -138,7 +138,7 @@ namespace Managing.Application.Tests
|
|||||||
|
|
||||||
// Act
|
// Act
|
||||||
var backtestResult = await _backtester.RunTradingBotBacktest(config, DateTime.UtcNow.AddDays(-6),
|
var backtestResult = await _backtester.RunTradingBotBacktest(config, DateTime.UtcNow.AddDays(-6),
|
||||||
DateTime.UtcNow, null, false);
|
DateTime.UtcNow, null, false, false);
|
||||||
//WriteCsvReport(backtestResult.GetStringReport());
|
//WriteCsvReport(backtestResult.GetStringReport());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -188,7 +188,7 @@ namespace Managing.Application.Tests
|
|||||||
|
|
||||||
// Act
|
// Act
|
||||||
var backtestResult = await _backtester.RunTradingBotBacktest(config, DateTime.UtcNow.AddDays(-6),
|
var backtestResult = await _backtester.RunTradingBotBacktest(config, DateTime.UtcNow.AddDays(-6),
|
||||||
DateTime.UtcNow, null, false);
|
DateTime.UtcNow, null, false, false);
|
||||||
WriteCsvReport(backtestResult.GetStringReport());
|
WriteCsvReport(backtestResult.GetStringReport());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -275,7 +275,7 @@ namespace Managing.Application.Tests
|
|||||||
FlipOnlyWhenInProfit = true,
|
FlipOnlyWhenInProfit = true,
|
||||||
MaxPositionTimeHours = null,
|
MaxPositionTimeHours = null,
|
||||||
CloseEarlyWhenProfitable = false
|
CloseEarlyWhenProfitable = false
|
||||||
}, candles, null).Result,
|
}, candles, null, false).Result,
|
||||||
BotType.FlippingBot => _backtester.RunTradingBotBacktest(new TradingBotConfig
|
BotType.FlippingBot => _backtester.RunTradingBotBacktest(new TradingBotConfig
|
||||||
{
|
{
|
||||||
AccountName = _account.Name,
|
AccountName = _account.Name,
|
||||||
@@ -294,7 +294,7 @@ namespace Managing.Application.Tests
|
|||||||
FlipOnlyWhenInProfit = true,
|
FlipOnlyWhenInProfit = true,
|
||||||
MaxPositionTimeHours = null,
|
MaxPositionTimeHours = null,
|
||||||
CloseEarlyWhenProfitable = false
|
CloseEarlyWhenProfitable = false
|
||||||
}, candles, null).Result,
|
}, candles, null, false).Result,
|
||||||
_ => throw new NotImplementedException(),
|
_ => throw new NotImplementedException(),
|
||||||
};
|
};
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
|
|||||||
@@ -287,6 +287,7 @@ public class StatisticService : IStatisticService
|
|||||||
DateTime.Now.AddDays(-7),
|
DateTime.Now.AddDays(-7),
|
||||||
DateTime.Now,
|
DateTime.Now,
|
||||||
null,
|
null,
|
||||||
|
false,
|
||||||
false);
|
false);
|
||||||
|
|
||||||
return backtest.Signals;
|
return backtest.Signals;
|
||||||
|
|||||||
@@ -65,18 +65,20 @@ namespace Managing.Application.Backtesting
|
|||||||
/// <param name="endDate">The end date for the backtest</param>
|
/// <param name="endDate">The end date for the backtest</param>
|
||||||
/// <param name="user">The user running the backtest (optional)</param>
|
/// <param name="user">The user running the backtest (optional)</param>
|
||||||
/// <param name="save">Whether to save the backtest results</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>
|
/// <returns>The backtest results</returns>
|
||||||
public async Task<Backtest> RunTradingBotBacktest(
|
public async Task<Backtest> RunTradingBotBacktest(
|
||||||
TradingBotConfig config,
|
TradingBotConfig config,
|
||||||
DateTime startDate,
|
DateTime startDate,
|
||||||
DateTime endDate,
|
DateTime endDate,
|
||||||
User user = null,
|
User user = null,
|
||||||
bool save = false)
|
bool save = false,
|
||||||
|
bool withCandles = false)
|
||||||
{
|
{
|
||||||
var account = await GetAccountFromConfig(config);
|
var account = await GetAccountFromConfig(config);
|
||||||
var candles = GetCandles(account, config.Ticker, config.Timeframe, startDate, endDate);
|
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
|
// Set start and end dates
|
||||||
result.StartDate = startDate;
|
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="config">The trading bot configuration (must include Scenario object or ScenarioName)</param>
|
||||||
/// <param name="candles">The candles to use for backtesting</param>
|
/// <param name="candles">The candles to use for backtesting</param>
|
||||||
/// <param name="user">The user running the backtest (optional)</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>
|
/// <returns>The backtest results</returns>
|
||||||
public async Task<Backtest> RunTradingBotBacktest(
|
public async Task<Backtest> RunTradingBotBacktest(
|
||||||
TradingBotConfig config,
|
TradingBotConfig config,
|
||||||
List<Candle> candles,
|
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>
|
/// <summary>
|
||||||
@@ -112,7 +116,8 @@ namespace Managing.Application.Backtesting
|
|||||||
private async Task<Backtest> RunBacktestWithCandles(
|
private async Task<Backtest> RunBacktestWithCandles(
|
||||||
TradingBotConfig config,
|
TradingBotConfig config,
|
||||||
List<Candle> candles,
|
List<Candle> candles,
|
||||||
User user = null)
|
User user = null,
|
||||||
|
bool withCandles = false)
|
||||||
{
|
{
|
||||||
var tradingBot = _botFactory.CreateBacktestTradingBot(config);
|
var tradingBot = _botFactory.CreateBacktestTradingBot(config);
|
||||||
|
|
||||||
@@ -128,7 +133,7 @@ namespace Managing.Application.Backtesting
|
|||||||
tradingBot.User = user;
|
tradingBot.User = user;
|
||||||
await tradingBot.LoadAccount();
|
await tradingBot.LoadAccount();
|
||||||
|
|
||||||
var result = GetBacktestingResult(config, tradingBot, candles);
|
var result = GetBacktestingResult(config, tradingBot, candles, withCandles);
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
@@ -168,7 +173,8 @@ namespace Managing.Application.Backtesting
|
|||||||
private Backtest GetBacktestingResult(
|
private Backtest GetBacktestingResult(
|
||||||
TradingBotConfig config,
|
TradingBotConfig config,
|
||||||
ITradingBot bot,
|
ITradingBot bot,
|
||||||
List<Candle> candles)
|
List<Candle> candles,
|
||||||
|
bool withCandles = false)
|
||||||
{
|
{
|
||||||
if (candles == null || candles.Count == 0)
|
if (candles == null || candles.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -211,7 +217,12 @@ namespace Managing.Application.Backtesting
|
|||||||
|
|
||||||
bot.Candles = new HashSet<Candle>(candles);
|
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 finalPnl = bot.GetProfitAndLoss();
|
||||||
var winRate = bot.GetWinRate();
|
var winRate = bot.GetWinRate();
|
||||||
@@ -236,7 +247,8 @@ namespace Managing.Application.Backtesting
|
|||||||
|
|
||||||
var score = BacktestScorer.CalculateTotalScore(scoringParams);
|
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,
|
FinalPnl = finalPnl,
|
||||||
WinRate = winRate,
|
WinRate = winRate,
|
||||||
@@ -246,7 +258,7 @@ namespace Managing.Application.Backtesting
|
|||||||
WalletBalances = bot.WalletBalances.ToList(),
|
WalletBalances = bot.WalletBalances.ToList(),
|
||||||
Statistics = stats,
|
Statistics = stats,
|
||||||
OptimizedMoneyManagement = optimizedMoneyManagement,
|
OptimizedMoneyManagement = optimizedMoneyManagement,
|
||||||
IndicatorsValues = AggregateValues(indicatorsValues, bot.IndicatorsValues),
|
IndicatorsValues = withCandles ? AggregateValues(indicatorsValues, bot.IndicatorsValues) : new Dictionary<IndicatorType, IndicatorsResultBase>(),
|
||||||
Score = score,
|
Score = score,
|
||||||
Id = Guid.NewGuid().ToString()
|
Id = Guid.NewGuid().ToString()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
using Managing.Application.Abstractions.Repositories;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Core.FixedSizedQueue;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Shared.Helpers;
|
using Managing.Domain.Shared.Helpers;
|
||||||
using Managing.Domain.Statistics;
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
|
using Managing.Domain.Strategies.Base;
|
||||||
using Managing.Domain.Synth.Models;
|
using Managing.Domain.Synth.Models;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Infrastructure.Evm.Models.Privy;
|
using Managing.Infrastructure.Evm.Models.Privy;
|
||||||
@@ -423,4 +426,50 @@ public class TradingService : ITradingService
|
|||||||
return await _synthPredictionService.MonitorPositionRiskAsync(ticker, direction, currentPrice, liquidationPrice,
|
return await _synthPredictionService.MonitorPositionRiskAsync(ticker, direction, currentPrice, liquidationPrice,
|
||||||
positionIdentifier, botConfig);
|
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 {CardPositionItem, TradeChart} from '..'
|
||||||
import {Backtest} from '../../../generated/ManagingApi'
|
import {
|
||||||
|
Backtest,
|
||||||
|
CandlesWithIndicatorsResponse,
|
||||||
|
DataClient,
|
||||||
|
GetCandlesWithIndicatorsRequest,
|
||||||
|
IndicatorType,
|
||||||
|
SignalType
|
||||||
|
} from '../../../generated/ManagingApi'
|
||||||
import {CardPosition, CardText} from '../../mollecules'
|
import {CardPosition, CardText} from '../../mollecules'
|
||||||
|
import {useQuery} from '@tanstack/react-query'
|
||||||
|
import useApiUrlStore from '../../../app/store/apiStore'
|
||||||
|
|
||||||
interface IBacktestRowDetailsProps {
|
interface IBacktestRowDetailsProps {
|
||||||
backtest: Backtest;
|
backtest: Backtest;
|
||||||
@@ -9,11 +18,60 @@ interface IBacktestRowDetailsProps {
|
|||||||
const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||||
backtest
|
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 {
|
const {
|
||||||
candles,
|
|
||||||
positions,
|
positions,
|
||||||
walletBalances,
|
walletBalances,
|
||||||
indicatorsValues,
|
|
||||||
signals,
|
signals,
|
||||||
statistics,
|
statistics,
|
||||||
config
|
config
|
||||||
@@ -243,6 +301,12 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="grid grid-flow-row">
|
<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">
|
<div className="grid grid-cols-4 p-5">
|
||||||
<CardPosition
|
<CardPosition
|
||||||
positivePosition={true}
|
positivePosition={true}
|
||||||
@@ -351,18 +415,20 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
|||||||
content={getAverageTradesPerDay() + " trades/day"}
|
content={getAverageTradesPerDay() + " trades/day"}
|
||||||
></CardText>
|
></CardText>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
{!isLoadingCandles && (
|
||||||
<figure className="w-full">
|
<div className="w-full">
|
||||||
<TradeChart
|
<figure className="w-full">
|
||||||
candles={candles}
|
<TradeChart
|
||||||
positions={positions}
|
candles={candles}
|
||||||
walletBalances={walletBalances}
|
positions={positions}
|
||||||
indicatorsValues={indicatorsValues}
|
walletBalances={walletBalances}
|
||||||
signals={signals}
|
indicatorsValues={indicatorsValues}
|
||||||
height={1000}
|
signals={signals}
|
||||||
></TradeChart>
|
height={1000}
|
||||||
</figure>
|
></TradeChart>
|
||||||
</div>
|
</figure>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1087,29 +1087,17 @@ export class DataClient extends AuthorizedApiBase {
|
|||||||
return Promise.resolve<SpotlightOverview>(null as any);
|
return Promise.resolve<SpotlightOverview>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
data_GetCandles(exchange: TradingExchanges | undefined, ticker: Ticker | undefined, startDate: Date | undefined, timeframe: Timeframe | undefined): Promise<Candle[]> {
|
data_GetCandlesWithIndicators(request: GetCandlesWithIndicatorsRequest): Promise<CandlesWithIndicatorsResponse> {
|
||||||
let url_ = this.baseUrl + "/Data/GetCandles?";
|
let url_ = this.baseUrl + "/Data/GetCandlesWithIndicators";
|
||||||
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) + "&";
|
|
||||||
url_ = url_.replace(/[?&]$/, "");
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
const content_ = JSON.stringify(request);
|
||||||
|
|
||||||
let options_: RequestInit = {
|
let options_: RequestInit = {
|
||||||
method: "GET",
|
body: content_,
|
||||||
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
"Accept": "application/json"
|
"Accept": "application/json"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1117,17 +1105,17 @@ export class DataClient extends AuthorizedApiBase {
|
|||||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||||
return this.http.fetch(url_, transformedOptions_);
|
return this.http.fetch(url_, transformedOptions_);
|
||||||
}).then((_response: Response) => {
|
}).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;
|
const status = response.status;
|
||||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
if (status === 200) {
|
if (status === 200) {
|
||||||
return response.text().then((_responseText) => {
|
return response.text().then((_responseText) => {
|
||||||
let result200: any = null;
|
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;
|
return result200;
|
||||||
});
|
});
|
||||||
} else if (status !== 200 && status !== 204) {
|
} 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 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> {
|
data_GetStrategiesStatistics(): Promise<StrategiesStatisticsViewModel> {
|
||||||
@@ -3470,6 +3458,7 @@ export interface RunBacktestRequest {
|
|||||||
startDate?: Date;
|
startDate?: Date;
|
||||||
endDate?: Date;
|
endDate?: Date;
|
||||||
save?: boolean;
|
save?: boolean;
|
||||||
|
withCandles?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TradingBotConfigRequest {
|
export interface TradingBotConfigRequest {
|
||||||
@@ -3589,6 +3578,19 @@ export interface TickerSignal {
|
|||||||
oneDay: Signal[];
|
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 {
|
export interface StrategiesStatisticsViewModel {
|
||||||
totalStrategiesRunning?: number;
|
totalStrategiesRunning?: number;
|
||||||
changeInLast24Hours?: number;
|
changeInLast24Hours?: number;
|
||||||
|
|||||||
@@ -598,6 +598,7 @@ export interface RunBacktestRequest {
|
|||||||
startDate?: Date;
|
startDate?: Date;
|
||||||
endDate?: Date;
|
endDate?: Date;
|
||||||
save?: boolean;
|
save?: boolean;
|
||||||
|
withCandles?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TradingBotConfigRequest {
|
export interface TradingBotConfigRequest {
|
||||||
@@ -717,6 +718,19 @@ export interface TickerSignal {
|
|||||||
oneDay: Signal[];
|
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 {
|
export interface StrategiesStatisticsViewModel {
|
||||||
totalStrategiesRunning?: number;
|
totalStrategiesRunning?: number;
|
||||||
changeInLast24Hours?: number;
|
changeInLast24Hours?: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user