Update bot market type
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Managing.Api.Models.Requests;
|
using Managing.Api.Models.Requests;
|
||||||
using Managing.Api.Models.Responses;
|
using Managing.Api.Models.Responses;
|
||||||
using Managing.Application.Abstractions.Repositories;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
@@ -35,6 +35,7 @@ public class BacktestController : BaseController
|
|||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly IMoneyManagementService _moneyManagementService;
|
private readonly IMoneyManagementService _moneyManagementService;
|
||||||
private readonly IGeneticService _geneticService;
|
private readonly IGeneticService _geneticService;
|
||||||
|
private readonly IFlagsmithService _flagsmithService;
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
private readonly ILogger<BacktestController> _logger;
|
private readonly ILogger<BacktestController> _logger;
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ public class BacktestController : BaseController
|
|||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMoneyManagementService moneyManagementService,
|
IMoneyManagementService moneyManagementService,
|
||||||
IGeneticService geneticService,
|
IGeneticService geneticService,
|
||||||
|
IFlagsmithService flagsmithService,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IServiceScopeFactory serviceScopeFactory,
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
ILogger<BacktestController> logger) : base(userService)
|
ILogger<BacktestController> logger) : base(userService)
|
||||||
@@ -63,6 +65,7 @@ public class BacktestController : BaseController
|
|||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_moneyManagementService = moneyManagementService;
|
_moneyManagementService = moneyManagementService;
|
||||||
_geneticService = geneticService;
|
_geneticService = geneticService;
|
||||||
|
_flagsmithService = flagsmithService;
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
@@ -152,7 +155,8 @@ public class BacktestController : BaseController
|
|||||||
[FromQuery] string? indicators = null,
|
[FromQuery] string? indicators = null,
|
||||||
[FromQuery] double? durationMinDays = null,
|
[FromQuery] double? durationMinDays = null,
|
||||||
[FromQuery] double? durationMaxDays = null,
|
[FromQuery] double? durationMaxDays = null,
|
||||||
[FromQuery] string? name = null)
|
[FromQuery] string? name = null,
|
||||||
|
[FromQuery] TradingType? tradingType = null)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
|
|
||||||
@@ -211,7 +215,8 @@ public class BacktestController : BaseController
|
|||||||
Tickers = tickerList,
|
Tickers = tickerList,
|
||||||
Indicators = indicatorList,
|
Indicators = indicatorList,
|
||||||
DurationMin = durationMinDays.HasValue ? TimeSpan.FromDays(durationMinDays.Value) : (TimeSpan?)null,
|
DurationMin = durationMinDays.HasValue ? TimeSpan.FromDays(durationMinDays.Value) : (TimeSpan?)null,
|
||||||
DurationMax = durationMaxDays.HasValue ? TimeSpan.FromDays(durationMaxDays.Value) : (TimeSpan?)null
|
DurationMax = durationMaxDays.HasValue ? TimeSpan.FromDays(durationMaxDays.Value) : (TimeSpan?)null,
|
||||||
|
TradingType = tradingType
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -317,7 +322,8 @@ public class BacktestController : BaseController
|
|||||||
ScoreMessage = b.ScoreMessage,
|
ScoreMessage = b.ScoreMessage,
|
||||||
InitialBalance = b.InitialBalance,
|
InitialBalance = b.InitialBalance,
|
||||||
NetPnl = b.NetPnl,
|
NetPnl = b.NetPnl,
|
||||||
PositionCount = b.PositionCount
|
PositionCount = b.PositionCount,
|
||||||
|
TradingType = b.Config.TradingType
|
||||||
}),
|
}),
|
||||||
TotalCount = totalCount,
|
TotalCount = totalCount,
|
||||||
CurrentPage = page,
|
CurrentPage = page,
|
||||||
@@ -354,7 +360,8 @@ public class BacktestController : BaseController
|
|||||||
[FromQuery] string? indicators = null,
|
[FromQuery] string? indicators = null,
|
||||||
[FromQuery] double? durationMinDays = null,
|
[FromQuery] double? durationMinDays = null,
|
||||||
[FromQuery] double? durationMaxDays = null,
|
[FromQuery] double? durationMaxDays = null,
|
||||||
[FromQuery] string? name = null)
|
[FromQuery] string? name = null,
|
||||||
|
[FromQuery] TradingType? tradingType = null)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
|
|
||||||
@@ -427,7 +434,8 @@ public class BacktestController : BaseController
|
|||||||
Tickers = tickerList,
|
Tickers = tickerList,
|
||||||
Indicators = indicatorList,
|
Indicators = indicatorList,
|
||||||
DurationMin = durationMinDays.HasValue ? TimeSpan.FromDays(durationMinDays.Value) : (TimeSpan?)null,
|
DurationMin = durationMinDays.HasValue ? TimeSpan.FromDays(durationMinDays.Value) : (TimeSpan?)null,
|
||||||
DurationMax = durationMaxDays.HasValue ? TimeSpan.FromDays(durationMaxDays.Value) : (TimeSpan?)null
|
DurationMax = durationMaxDays.HasValue ? TimeSpan.FromDays(durationMaxDays.Value) : (TimeSpan?)null,
|
||||||
|
TradingType = tradingType
|
||||||
};
|
};
|
||||||
|
|
||||||
var (backtests, totalCount) =
|
var (backtests, totalCount) =
|
||||||
@@ -459,7 +467,8 @@ public class BacktestController : BaseController
|
|||||||
ScoreMessage = b.ScoreMessage,
|
ScoreMessage = b.ScoreMessage,
|
||||||
InitialBalance = b.InitialBalance,
|
InitialBalance = b.InitialBalance,
|
||||||
NetPnl = b.NetPnl,
|
NetPnl = b.NetPnl,
|
||||||
PositionCount = b.PositionCount
|
PositionCount = b.PositionCount,
|
||||||
|
TradingType = b.Config.TradingType
|
||||||
}),
|
}),
|
||||||
TotalCount = totalCount,
|
TotalCount = totalCount,
|
||||||
CurrentPage = page,
|
CurrentPage = page,
|
||||||
@@ -651,6 +660,20 @@ public class BacktestController : BaseController
|
|||||||
{
|
{
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
|
|
||||||
|
// Check if trading type is futures and verify the user has permission via feature flag
|
||||||
|
if (request.UniversalConfig.TradingType == TradingType.Futures ||
|
||||||
|
request.UniversalConfig.TradingType == TradingType.BacktestFutures)
|
||||||
|
{
|
||||||
|
var isTradingFutureEnabled = await _flagsmithService.IsFeatureEnabledAsync(user.Name, "trading_future");
|
||||||
|
|
||||||
|
if (!isTradingFutureEnabled)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("User {UserName} attempted to create futures bundle backtest but does not have the trading_future feature flag enabled",
|
||||||
|
user.Name);
|
||||||
|
return Forbid("Futures trading is not enabled for your account. Please contact support to enable this feature.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(request.UniversalConfig.ScenarioName) && request.UniversalConfig.Scenario == null)
|
if (string.IsNullOrEmpty(request.UniversalConfig.ScenarioName) && request.UniversalConfig.Scenario == null)
|
||||||
{
|
{
|
||||||
return BadRequest("Either scenario name or scenario object is required in universal configuration");
|
return BadRequest("Either scenario name or scenario object is required in universal configuration");
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Managing.Domain.Backtests;
|
using Managing.Domain.Backtests;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Api.Models.Requests;
|
namespace Managing.Api.Models.Requests;
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ public class LightBacktestResponse
|
|||||||
[Required] public decimal InitialBalance { get; set; }
|
[Required] public decimal InitialBalance { get; set; }
|
||||||
[Required] public decimal NetPnl { get; set; }
|
[Required] public decimal NetPnl { get; set; }
|
||||||
[Required] public int PositionCount { get; set; }
|
[Required] public int PositionCount { get; set; }
|
||||||
|
[Required] public TradingType TradingType { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LightBacktestResponseMapper
|
public static class LightBacktestResponseMapper
|
||||||
@@ -47,7 +49,8 @@ public static class LightBacktestResponseMapper
|
|||||||
ScoreMessage = b.ScoreMessage,
|
ScoreMessage = b.ScoreMessage,
|
||||||
InitialBalance = b.InitialBalance,
|
InitialBalance = b.InitialBalance,
|
||||||
NetPnl = b.NetPnl,
|
NetPnl = b.NetPnl,
|
||||||
PositionCount = b.PositionCount
|
PositionCount = b.PositionCount,
|
||||||
|
TradingType = b.Config.TradingType
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.Indicators;
|
using Managing.Domain.Indicators;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
@@ -82,6 +82,12 @@ namespace Managing.Api.Models.Responses
|
|||||||
[Required]
|
[Required]
|
||||||
public Ticker Ticker { get; set; }
|
public Ticker Ticker { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The trading type (Futures, Spot, etc.)
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public TradingType TradingType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The agent name of the master bot's owner (for copy trading bots)
|
/// The agent name of the master bot's owner (for copy trading bots)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -6,27 +6,27 @@ namespace Managing.Application.Abstractions.Services;
|
|||||||
public interface IFlagsmithService
|
public interface IFlagsmithService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets flags for a specific user identity
|
/// Gets flags for a specific user. The username is hashed internally for privacy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="identity">The user identity identifier</param>
|
/// <param name="username">The username to get flags for</param>
|
||||||
/// <returns>Flags object for the identity</returns>
|
/// <returns>Flags object for the user</returns>
|
||||||
Task<IFlagsmithFlags> GetIdentityFlagsAsync(string identity);
|
Task<IFlagsmithFlags> GetIdentityFlagsAsync(string username);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if a feature is enabled for a specific identity
|
/// Checks if a feature is enabled for a specific user. The username is hashed internally for privacy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="identity">The user identity identifier</param>
|
/// <param name="username">The username to check</param>
|
||||||
/// <param name="featureName">The name of the feature flag</param>
|
/// <param name="featureName">The name of the feature flag</param>
|
||||||
/// <returns>True if the feature is enabled</returns>
|
/// <returns>True if the feature is enabled</returns>
|
||||||
Task<bool> IsFeatureEnabledAsync(string identity, string featureName);
|
Task<bool> IsFeatureEnabledAsync(string username, string featureName);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the feature value for a specific identity
|
/// Gets the feature value for a specific user. The username is hashed internally for privacy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="identity">The user identity identifier</param>
|
/// <param name="username">The username to get feature value for</param>
|
||||||
/// <param name="featureName">The name of the feature flag</param>
|
/// <param name="featureName">The name of the feature flag</param>
|
||||||
/// <returns>The feature value as string</returns>
|
/// <returns>The feature value as string</returns>
|
||||||
Task<string?> GetFeatureValueAsync(string identity, string featureName);
|
Task<string?> GetFeatureValueAsync(string username, string featureName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.Abstractions.Shared;
|
namespace Managing.Application.Abstractions.Shared;
|
||||||
|
|
||||||
public class BacktestsFilter
|
public class BacktestsFilter
|
||||||
@@ -12,6 +14,7 @@ public class BacktestsFilter
|
|||||||
public IEnumerable<string>? Indicators { get; set; }
|
public IEnumerable<string>? Indicators { get; set; }
|
||||||
public TimeSpan? DurationMin { get; set; }
|
public TimeSpan? DurationMin { get; set; }
|
||||||
public TimeSpan? DurationMax { get; set; }
|
public TimeSpan? DurationMax { get; set; }
|
||||||
|
public TradingType? TradingType { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using Flagsmith;
|
using Flagsmith;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -30,30 +32,32 @@ public class FlagsmithService : IFlagsmithService
|
|||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IFlagsmithFlags> GetIdentityFlagsAsync(string identity)
|
public async Task<IFlagsmithFlags> GetIdentityFlagsAsync(string username)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(identity))
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Identity cannot be null or empty", nameof(identity));
|
throw new ArgumentException("Username cannot be null or empty", nameof(username));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hashedIdentity = HashUsername(username);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var flags = await _flagsmithClient.GetIdentityFlags(identity);
|
var flags = await _flagsmithClient.GetIdentityFlags(hashedIdentity);
|
||||||
return new FlagsmithFlagsWrapper(flags);
|
return new FlagsmithFlagsWrapper(flags);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error getting flags for identity {Identity}", identity);
|
_logger.LogError(ex, "Error getting flags for username {Username} (hashed: {HashedIdentity})", username, hashedIdentity);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> IsFeatureEnabledAsync(string identity, string featureName)
|
public async Task<bool> IsFeatureEnabledAsync(string username, string featureName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(identity))
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Identity cannot be null or empty", nameof(identity));
|
throw new ArgumentException("Username cannot be null or empty", nameof(username));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(featureName))
|
if (string.IsNullOrWhiteSpace(featureName))
|
||||||
@@ -63,21 +67,21 @@ public class FlagsmithService : IFlagsmithService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var flags = await GetIdentityFlagsAsync(identity);
|
var flags = await GetIdentityFlagsAsync(username);
|
||||||
return await flags.IsFeatureEnabled(featureName);
|
return await flags.IsFeatureEnabled(featureName);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error checking feature {FeatureName} for identity {Identity}", featureName, identity);
|
_logger.LogError(ex, "Error checking feature {FeatureName} for username {Username}", featureName, username);
|
||||||
return false; // Default to false on error
|
return false; // Default to false on error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string?> GetFeatureValueAsync(string identity, string featureName)
|
public async Task<string?> GetFeatureValueAsync(string username, string featureName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(identity))
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Identity cannot be null or empty", nameof(identity));
|
throw new ArgumentException("Username cannot be null or empty", nameof(username));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(featureName))
|
if (string.IsNullOrWhiteSpace(featureName))
|
||||||
@@ -87,15 +91,32 @@ public class FlagsmithService : IFlagsmithService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var flags = await GetIdentityFlagsAsync(identity);
|
var flags = await GetIdentityFlagsAsync(username);
|
||||||
return await flags.GetFeatureValue(featureName);
|
return await flags.GetFeatureValue(featureName);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error getting feature value {FeatureName} for identity {Identity}", featureName, identity);
|
_logger.LogError(ex, "Error getting feature value {FeatureName} for username {Username}", featureName, username);
|
||||||
return null; // Default to null on error
|
return null; // Default to null on error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hashes the username using SHA256 to create a privacy-preserving identity for Flagsmith
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username">The username to hash</param>
|
||||||
|
/// <returns>SHA256 hash of the username as a hexadecimal string</returns>
|
||||||
|
private static string HashUsername(string username)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Username cannot be null or empty", nameof(username));
|
||||||
|
}
|
||||||
|
|
||||||
|
using var sha256 = SHA256.Create();
|
||||||
|
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(username));
|
||||||
|
return Convert.ToHexString(hashBytes).ToLowerInvariant();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Domain.Bots
|
namespace Managing.Domain.Bots
|
||||||
@@ -9,6 +9,7 @@ namespace Managing.Domain.Bots
|
|||||||
public Guid Identifier { get; set; }
|
public Guid Identifier { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public Ticker Ticker { get; set; }
|
public Ticker Ticker { get; set; }
|
||||||
|
public TradingType TradingType { get; set; }
|
||||||
public BotStatus Status { get; set; }
|
public BotStatus Status { get; set; }
|
||||||
public DateTime StartupTime { get; set; }
|
public DateTime StartupTime { get; set; }
|
||||||
public DateTime CreateDate { get; set; }
|
public DateTime CreateDate { get; set; }
|
||||||
|
|||||||
1752
src/Managing.Infrastructure.Database/Migrations/20251211151923_AddTradingTypeToBacktestsAndBots.Designer.cs
generated
Normal file
1752
src/Managing.Infrastructure.Database/Migrations/20251211151923_AddTradingTypeToBacktestsAndBots.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Managing.Infrastructure.Databases.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddTradingTypeToBacktestsAndBots : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "Ticker",
|
||||||
|
table: "Bots",
|
||||||
|
type: "text",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "integer");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "TradingType",
|
||||||
|
table: "Bots",
|
||||||
|
type: "text",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "Spot");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "TradingType",
|
||||||
|
table: "Backtests",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "TradingType",
|
||||||
|
table: "Bots");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "TradingType",
|
||||||
|
table: "Backtests");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "Ticker",
|
||||||
|
table: "Bots",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "text");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -259,6 +259,9 @@ namespace Managing.Infrastructure.Databases.Migrations
|
|||||||
b.Property<int>("Timeframe")
|
b.Property<int>("Timeframe")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("TradingType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b.Property<DateTime>("UpdatedAt")
|
b.Property<DateTime>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
@@ -348,8 +351,9 @@ namespace Managing.Infrastructure.Databases.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<int>("Ticker")
|
b.Property<string>("Ticker")
|
||||||
.HasColumnType("integer");
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<int>("TradeLosses")
|
b.Property<int>("TradeLosses")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -357,6 +361,10 @@ namespace Managing.Infrastructure.Databases.Migrations
|
|||||||
b.Property<int>("TradeWins")
|
b.Property<int>("TradeWins")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("TradingType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<DateTime>("UpdatedAt")
|
b.Property<DateTime>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ public class BacktestEntity
|
|||||||
[Required]
|
[Required]
|
||||||
public int Timeframe { get; set; }
|
public int Timeframe { get; set; }
|
||||||
|
|
||||||
|
// Stored trading type as enum numeric value for direct filtering
|
||||||
|
[Required]
|
||||||
|
public int TradingType { get; set; }
|
||||||
|
|
||||||
// Comma-separated indicator types for filtering, e.g., "EMA_CROSS,MACD_CROSS"
|
// Comma-separated indicator types for filtering, e.g., "EMA_CROSS,MACD_CROSS"
|
||||||
[Column(TypeName = "text")]
|
[Column(TypeName = "text")]
|
||||||
public string IndicatorsCsv { get; set; } = string.Empty;
|
public string IndicatorsCsv { get; set; } = string.Empty;
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ public class BotEntity
|
|||||||
|
|
||||||
public Ticker Ticker { get; set; }
|
public Ticker Ticker { get; set; }
|
||||||
|
|
||||||
|
public TradingType TradingType { get; set; }
|
||||||
|
|
||||||
public int UserId { get; set; }
|
public int UserId { get; set; }
|
||||||
|
|
||||||
[ForeignKey("UserId")] public UserEntity User { get; set; }
|
[ForeignKey("UserId")] public UserEntity User { get; set; }
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ public class ManagingDbContext : DbContext
|
|||||||
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
|
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
|
||||||
entity.Property(e => e.Ticker).HasMaxLength(32);
|
entity.Property(e => e.Ticker).HasMaxLength(32);
|
||||||
entity.Property(e => e.Timeframe).IsRequired();
|
entity.Property(e => e.Timeframe).IsRequired();
|
||||||
|
entity.Property(e => e.TradingType).IsRequired();
|
||||||
entity.Property(e => e.IndicatorsCsv).HasColumnType("text");
|
entity.Property(e => e.IndicatorsCsv).HasColumnType("text");
|
||||||
entity.Property(e => e.IndicatorsCount).IsRequired();
|
entity.Property(e => e.IndicatorsCount).IsRequired();
|
||||||
entity.Property(e => e.PositionsJson).HasColumnType("jsonb");
|
entity.Property(e => e.PositionsJson).HasColumnType("jsonb");
|
||||||
@@ -522,6 +523,8 @@ public class ManagingDbContext : DbContext
|
|||||||
entity.HasKey(e => e.Identifier);
|
entity.HasKey(e => e.Identifier);
|
||||||
entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255);
|
entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255);
|
||||||
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
|
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
|
||||||
|
entity.Property(e => e.Ticker).IsRequired().HasConversion<string>();
|
||||||
|
entity.Property(e => e.TradingType).IsRequired().HasConversion<string>();
|
||||||
entity.Property(e => e.Status).IsRequired().HasConversion<string>();
|
entity.Property(e => e.Status).IsRequired().HasConversion<string>();
|
||||||
entity.Property(e => e.CreateDate).IsRequired();
|
entity.Property(e => e.CreateDate).IsRequired();
|
||||||
entity.Property(e => e.StartupTime).IsRequired();
|
entity.Property(e => e.StartupTime).IsRequired();
|
||||||
|
|||||||
@@ -438,6 +438,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
baseQuery = baseQuery.Where(b => b.Duration >= filter.DurationMin.Value);
|
baseQuery = baseQuery.Where(b => b.Duration >= filter.DurationMin.Value);
|
||||||
if (filter.DurationMax.HasValue)
|
if (filter.DurationMax.HasValue)
|
||||||
baseQuery = baseQuery.Where(b => b.Duration <= filter.DurationMax.Value);
|
baseQuery = baseQuery.Where(b => b.Duration <= filter.DurationMax.Value);
|
||||||
|
if (filter.TradingType.HasValue)
|
||||||
|
baseQuery = baseQuery.Where(b => b.TradingType == (int)filter.TradingType.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var entities = await baseQuery.ToListAsync().ConfigureAwait(false);
|
var entities = await baseQuery.ToListAsync().ConfigureAwait(false);
|
||||||
@@ -503,6 +505,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
baseQuery = baseQuery.Where(b => b.Duration >= filter.DurationMin.Value);
|
baseQuery = baseQuery.Where(b => b.Duration >= filter.DurationMin.Value);
|
||||||
if (filter.DurationMax.HasValue)
|
if (filter.DurationMax.HasValue)
|
||||||
baseQuery = baseQuery.Where(b => b.Duration <= filter.DurationMax.Value);
|
baseQuery = baseQuery.Where(b => b.Duration <= filter.DurationMax.Value);
|
||||||
|
if (filter.TradingType.HasValue)
|
||||||
|
baseQuery = baseQuery.Where(b => b.TradingType == (int)filter.TradingType.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var afterQueryMs = stopwatch.ElapsedMilliseconds;
|
var afterQueryMs = stopwatch.ElapsedMilliseconds;
|
||||||
@@ -642,6 +646,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
baseQuery = baseQuery.Where(b => b.Duration >= filter.DurationMin.Value);
|
baseQuery = baseQuery.Where(b => b.Duration >= filter.DurationMin.Value);
|
||||||
if (filter.DurationMax.HasValue)
|
if (filter.DurationMax.HasValue)
|
||||||
baseQuery = baseQuery.Where(b => b.Duration <= filter.DurationMax.Value);
|
baseQuery = baseQuery.Where(b => b.Duration <= filter.DurationMax.Value);
|
||||||
|
if (filter.TradingType.HasValue)
|
||||||
|
baseQuery = baseQuery.Where(b => b.TradingType == (int)filter.TradingType.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var afterQueryMs = stopwatch.ElapsedMilliseconds;
|
var afterQueryMs = stopwatch.ElapsedMilliseconds;
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ public static class PostgreSqlMappers
|
|||||||
Name = backtest.Config?.Name ?? string.Empty,
|
Name = backtest.Config?.Name ?? string.Empty,
|
||||||
Ticker = backtest.Config?.Ticker.ToString() ?? string.Empty,
|
Ticker = backtest.Config?.Ticker.ToString() ?? string.Empty,
|
||||||
Timeframe = (int)backtest.Config.Timeframe,
|
Timeframe = (int)backtest.Config.Timeframe,
|
||||||
|
TradingType = (int)backtest.Config.TradingType,
|
||||||
IndicatorsCsv = string.Join(',', backtest.Config.Scenario.Indicators.Select(i => i.Type.ToString())),
|
IndicatorsCsv = string.Join(',', backtest.Config.Scenario.Indicators.Select(i => i.Type.ToString())),
|
||||||
IndicatorsCount = backtest.Config.Scenario.Indicators.Count,
|
IndicatorsCount = backtest.Config.Scenario.Indicators.Count,
|
||||||
PositionsJson = JsonConvert.SerializeObject(backtest.Positions.Values.ToList(), jsonSettings),
|
PositionsJson = JsonConvert.SerializeObject(backtest.Positions.Values.ToList(), jsonSettings),
|
||||||
@@ -750,6 +751,7 @@ public static class PostgreSqlMappers
|
|||||||
CreateDate = entity.CreateDate,
|
CreateDate = entity.CreateDate,
|
||||||
Name = entity.Name,
|
Name = entity.Name,
|
||||||
Ticker = entity.Ticker,
|
Ticker = entity.Ticker,
|
||||||
|
TradingType = entity.TradingType,
|
||||||
StartupTime = entity.StartupTime,
|
StartupTime = entity.StartupTime,
|
||||||
LastStartTime = entity.LastStartTime,
|
LastStartTime = entity.LastStartTime,
|
||||||
LastStopTime = entity.LastStopTime,
|
LastStopTime = entity.LastStopTime,
|
||||||
@@ -782,6 +784,7 @@ public static class PostgreSqlMappers
|
|||||||
CreateDate = bot.CreateDate,
|
CreateDate = bot.CreateDate,
|
||||||
Name = bot.Name,
|
Name = bot.Name,
|
||||||
Ticker = bot.Ticker,
|
Ticker = bot.Ticker,
|
||||||
|
TradingType = bot.TradingType,
|
||||||
StartupTime = bot.StartupTime,
|
StartupTime = bot.StartupTime,
|
||||||
LastStartTime = bot.LastStartTime,
|
LastStartTime = bot.LastStartTime,
|
||||||
LastStopTime = bot.LastStopTime,
|
LastStopTime = bot.LastStopTime,
|
||||||
|
|||||||
@@ -987,6 +987,7 @@ export interface TradingBotResponse {
|
|||||||
startupTime: Date;
|
startupTime: Date;
|
||||||
name: string;
|
name: string;
|
||||||
ticker: Ticker;
|
ticker: Ticker;
|
||||||
|
tradingType: TradingType;
|
||||||
masterAgentName?: string | null;
|
masterAgentName?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {useExpanded, useFilters, usePagination, useSortBy, useTable,} from 'reac
|
|||||||
import useApiUrlStore from '../../../app/store/apiStore'
|
import useApiUrlStore from '../../../app/store/apiStore'
|
||||||
import useBacktestStore from '../../../app/store/backtestStore'
|
import useBacktestStore from '../../../app/store/backtestStore'
|
||||||
import type {Backtest, LightBacktestResponse} from '../../../generated/ManagingApi'
|
import type {Backtest, LightBacktestResponse} from '../../../generated/ManagingApi'
|
||||||
import {BacktestClient, BacktestSortableColumn, IndicatorType} from '../../../generated/ManagingApi'
|
import {BacktestClient, BacktestSortableColumn, IndicatorType, TradingType} from '../../../generated/ManagingApi'
|
||||||
import {ConfigDisplayModal, IndicatorsDisplay, SelectColumnFilter} from '../../mollecules'
|
import {ConfigDisplayModal, IndicatorsDisplay, SelectColumnFilter} from '../../mollecules'
|
||||||
import {UnifiedTradingModal} from '../index'
|
import {UnifiedTradingModal} from '../index'
|
||||||
import Toast from '../../mollecules/Toast/Toast'
|
import Toast from '../../mollecules/Toast/Toast'
|
||||||
@@ -148,6 +148,7 @@ interface BacktestTableProps {
|
|||||||
indicators?: string[] | null
|
indicators?: string[] | null
|
||||||
durationMinDays?: number | null
|
durationMinDays?: number | null
|
||||||
durationMaxDays?: number | null
|
durationMaxDays?: number | null
|
||||||
|
tradingType?: TradingType | null
|
||||||
}) => void
|
}) => void
|
||||||
filters?: {
|
filters?: {
|
||||||
nameContains?: string | null
|
nameContains?: string | null
|
||||||
@@ -160,6 +161,7 @@ interface BacktestTableProps {
|
|||||||
indicators?: string[] | null
|
indicators?: string[] | null
|
||||||
durationMinDays?: number | null
|
durationMinDays?: number | null
|
||||||
durationMaxDays?: number | null
|
durationMaxDays?: number | null
|
||||||
|
tradingType?: TradingType | null
|
||||||
}
|
}
|
||||||
openFiltersTrigger?: number // When this changes, open the filter sidebar
|
openFiltersTrigger?: number // When this changes, open the filter sidebar
|
||||||
}
|
}
|
||||||
@@ -196,6 +198,7 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
|
|||||||
const [selectedIndicators, setSelectedIndicators] = useState<string[]>([])
|
const [selectedIndicators, setSelectedIndicators] = useState<string[]>([])
|
||||||
const [durationMinDays, setDurationMinDays] = useState<number | null>(null)
|
const [durationMinDays, setDurationMinDays] = useState<number | null>(null)
|
||||||
const [durationMaxDays, setDurationMaxDays] = useState<number | null>(null)
|
const [durationMaxDays, setDurationMaxDays] = useState<number | null>(null)
|
||||||
|
const [selectedTradingType, setSelectedTradingType] = useState<TradingType | null>(null)
|
||||||
|
|
||||||
// Delete confirmation state
|
// Delete confirmation state
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
||||||
@@ -227,6 +230,7 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
|
|||||||
setSelectedIndicators([])
|
setSelectedIndicators([])
|
||||||
setDurationMinDays(null)
|
setDurationMinDays(null)
|
||||||
setDurationMaxDays(null)
|
setDurationMaxDays(null)
|
||||||
|
setSelectedTradingType(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh data function
|
// Refresh data function
|
||||||
@@ -249,6 +253,7 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
|
|||||||
indicators: selectedIndicators.length ? selectedIndicators : null,
|
indicators: selectedIndicators.length ? selectedIndicators : null,
|
||||||
durationMinDays,
|
durationMinDays,
|
||||||
durationMaxDays,
|
durationMaxDays,
|
||||||
|
tradingType: selectedTradingType,
|
||||||
})
|
})
|
||||||
setIsFilterOpen(false)
|
setIsFilterOpen(false)
|
||||||
}
|
}
|
||||||
@@ -270,7 +275,8 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
|
|||||||
filters.indicators?.join(',') || undefined,
|
filters.indicators?.join(',') || undefined,
|
||||||
filters.durationMinDays || undefined,
|
filters.durationMinDays || undefined,
|
||||||
filters.durationMaxDays || undefined,
|
filters.durationMaxDays || undefined,
|
||||||
filters.nameContains || undefined
|
filters.nameContains || undefined,
|
||||||
|
filters.tradingType || undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parse the response to get the deleted count
|
// Parse the response to get the deleted count
|
||||||
@@ -305,7 +311,8 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
|
|||||||
filters.tickers?.length ||
|
filters.tickers?.length ||
|
||||||
filters.indicators?.length ||
|
filters.indicators?.length ||
|
||||||
filters.durationMinDays !== null ||
|
filters.durationMinDays !== null ||
|
||||||
filters.durationMaxDays !== null
|
filters.durationMaxDays !== null ||
|
||||||
|
filters.tradingType !== null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,6 +342,7 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
|
|||||||
setSelectedIndicators(filters.indicators ? [...filters.indicators] : [])
|
setSelectedIndicators(filters.indicators ? [...filters.indicators] : [])
|
||||||
setDurationMinDays(filters.durationMinDays ?? null)
|
setDurationMinDays(filters.durationMinDays ?? null)
|
||||||
setDurationMaxDays(filters.durationMaxDays ?? null)
|
setDurationMaxDays(filters.durationMaxDays ?? null)
|
||||||
|
setSelectedTradingType(filters.tradingType ?? null)
|
||||||
}, [filters])
|
}, [filters])
|
||||||
|
|
||||||
// Handle external trigger to open filters
|
// Handle external trigger to open filters
|
||||||
@@ -507,6 +515,16 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
|
|||||||
accessor: 'config.timeframe',
|
accessor: 'config.timeframe',
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Filter: SelectColumnFilter,
|
||||||
|
Header: 'Trading Type',
|
||||||
|
accessor: 'tradingType',
|
||||||
|
Cell: ({cell}: any) => {
|
||||||
|
const tradingType = cell.value as TradingType;
|
||||||
|
return <span className="badge badge-outline">{tradingType}</span>;
|
||||||
|
},
|
||||||
|
disableSortBy: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Header: 'Indicators',
|
Header: 'Indicators',
|
||||||
accessor: 'config.scenario.indicators',
|
accessor: 'config.scenario.indicators',
|
||||||
@@ -798,6 +816,25 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Trading Type */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="mb-2 font-medium">Trading Type</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{Object.values(TradingType).map((type) => (
|
||||||
|
<button
|
||||||
|
key={type}
|
||||||
|
className={`btn btn-xs ${selectedTradingType === type ? 'btn-primary' : 'btn-outline'}`}
|
||||||
|
onClick={() => setSelectedTradingType(selectedTradingType === type ? null : type)}
|
||||||
|
>
|
||||||
|
{type}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
{selectedTradingType && (
|
||||||
|
<button className="btn btn-xs btn-ghost" onClick={() => setSelectedTradingType(null)}>Clear</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 justify-end">
|
<div className="flex gap-2 justify-end">
|
||||||
<button className="btn btn-ghost btn-sm" onClick={() => setIsFilterOpen(false)}>Cancel</button>
|
<button className="btn btn-ghost btn-sm" onClick={() => setIsFilterOpen(false)}>Cancel</button>
|
||||||
<button className="btn btn-primary btn-sm" onClick={applyFilters}>Apply</button>
|
<button className="btn btn-primary btn-sm" onClick={applyFilters}>Apply</button>
|
||||||
|
|||||||
@@ -797,7 +797,7 @@ export class BacktestClient extends AuthorizedApiBase {
|
|||||||
return Promise.resolve<FileResponse>(null as any);
|
return Promise.resolve<FileResponse>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
backtest_DeleteBacktestsByFilters(scoreMin: number | null | undefined, scoreMax: number | null | undefined, winrateMin: number | null | undefined, winrateMax: number | null | undefined, maxDrawdownMax: number | null | undefined, tickers: string | null | undefined, indicators: string | null | undefined, durationMinDays: number | null | undefined, durationMaxDays: number | null | undefined, name: string | null | undefined): Promise<FileResponse> {
|
backtest_DeleteBacktestsByFilters(scoreMin: number | null | undefined, scoreMax: number | null | undefined, winrateMin: number | null | undefined, winrateMax: number | null | undefined, maxDrawdownMax: number | null | undefined, tickers: string | null | undefined, indicators: string | null | undefined, durationMinDays: number | null | undefined, durationMaxDays: number | null | undefined, name: string | null | undefined, tradingType: TradingType | null | undefined): Promise<FileResponse> {
|
||||||
let url_ = this.baseUrl + "/Backtest/ByFilters?";
|
let url_ = this.baseUrl + "/Backtest/ByFilters?";
|
||||||
if (scoreMin !== undefined && scoreMin !== null)
|
if (scoreMin !== undefined && scoreMin !== null)
|
||||||
url_ += "scoreMin=" + encodeURIComponent("" + scoreMin) + "&";
|
url_ += "scoreMin=" + encodeURIComponent("" + scoreMin) + "&";
|
||||||
@@ -819,6 +819,8 @@ export class BacktestClient extends AuthorizedApiBase {
|
|||||||
url_ += "durationMaxDays=" + encodeURIComponent("" + durationMaxDays) + "&";
|
url_ += "durationMaxDays=" + encodeURIComponent("" + durationMaxDays) + "&";
|
||||||
if (name !== undefined && name !== null)
|
if (name !== undefined && name !== null)
|
||||||
url_ += "name=" + encodeURIComponent("" + name) + "&";
|
url_ += "name=" + encodeURIComponent("" + name) + "&";
|
||||||
|
if (tradingType !== undefined && tradingType !== null)
|
||||||
|
url_ += "tradingType=" + encodeURIComponent("" + tradingType) + "&";
|
||||||
url_ = url_.replace(/[?&]$/, "");
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
let options_: RequestInit = {
|
let options_: RequestInit = {
|
||||||
@@ -945,7 +947,7 @@ export class BacktestClient extends AuthorizedApiBase {
|
|||||||
return Promise.resolve<PaginatedBacktestsResponse>(null as any);
|
return Promise.resolve<PaginatedBacktestsResponse>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
backtest_GetBacktestsPaginated(page: number | undefined, pageSize: number | undefined, sortBy: BacktestSortableColumn | undefined, sortOrder: string | null | undefined, scoreMin: number | null | undefined, scoreMax: number | null | undefined, winrateMin: number | null | undefined, winrateMax: number | null | undefined, maxDrawdownMax: number | null | undefined, tickers: string | null | undefined, indicators: string | null | undefined, durationMinDays: number | null | undefined, durationMaxDays: number | null | undefined, name: string | null | undefined): Promise<PaginatedBacktestsResponse> {
|
backtest_GetBacktestsPaginated(page: number | undefined, pageSize: number | undefined, sortBy: BacktestSortableColumn | undefined, sortOrder: string | null | undefined, scoreMin: number | null | undefined, scoreMax: number | null | undefined, winrateMin: number | null | undefined, winrateMax: number | null | undefined, maxDrawdownMax: number | null | undefined, tickers: string | null | undefined, indicators: string | null | undefined, durationMinDays: number | null | undefined, durationMaxDays: number | null | undefined, name: string | null | undefined, tradingType: TradingType | null | undefined): Promise<PaginatedBacktestsResponse> {
|
||||||
let url_ = this.baseUrl + "/Backtest/Paginated?";
|
let url_ = this.baseUrl + "/Backtest/Paginated?";
|
||||||
if (page === null)
|
if (page === null)
|
||||||
throw new Error("The parameter 'page' cannot be null.");
|
throw new Error("The parameter 'page' cannot be null.");
|
||||||
@@ -981,6 +983,8 @@ export class BacktestClient extends AuthorizedApiBase {
|
|||||||
url_ += "durationMaxDays=" + encodeURIComponent("" + durationMaxDays) + "&";
|
url_ += "durationMaxDays=" + encodeURIComponent("" + durationMaxDays) + "&";
|
||||||
if (name !== undefined && name !== null)
|
if (name !== undefined && name !== null)
|
||||||
url_ += "name=" + encodeURIComponent("" + name) + "&";
|
url_ += "name=" + encodeURIComponent("" + name) + "&";
|
||||||
|
if (tradingType !== undefined && tradingType !== null)
|
||||||
|
url_ += "tradingType=" + encodeURIComponent("" + tradingType) + "&";
|
||||||
url_ = url_.replace(/[?&]$/, "");
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
let options_: RequestInit = {
|
let options_: RequestInit = {
|
||||||
@@ -5482,6 +5486,7 @@ export interface TradingBotResponse {
|
|||||||
startupTime: Date;
|
startupTime: Date;
|
||||||
name: string;
|
name: string;
|
||||||
ticker: Ticker;
|
ticker: Ticker;
|
||||||
|
tradingType: TradingType;
|
||||||
masterAgentName?: string | null;
|
masterAgentName?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -987,6 +987,7 @@ export interface TradingBotResponse {
|
|||||||
startupTime: Date;
|
startupTime: Date;
|
||||||
name: string;
|
name: string;
|
||||||
ticker: Ticker;
|
ticker: Ticker;
|
||||||
|
tradingType: TradingType;
|
||||||
masterAgentName?: string | null;
|
masterAgentName?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {Loader, Slider} from '../../components/atoms'
|
|||||||
import {BottomMenuBar, Modal, Toast} from '../../components/mollecules'
|
import {BottomMenuBar, Modal, Toast} from '../../components/mollecules'
|
||||||
import {BacktestTable, UnifiedTradingModal} from '../../components/organism'
|
import {BacktestTable, UnifiedTradingModal} from '../../components/organism'
|
||||||
import type {LightBacktestResponse} from '../../generated/ManagingApi'
|
import type {LightBacktestResponse} from '../../generated/ManagingApi'
|
||||||
import {BacktestClient, BacktestSortableColumn} from '../../generated/ManagingApi'
|
import {BacktestClient, BacktestSortableColumn, TradingType} from '../../generated/ManagingApi'
|
||||||
|
|
||||||
const PAGE_SIZE = 50
|
const PAGE_SIZE = 50
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ const BacktestScanner: React.FC = () => {
|
|||||||
indicators?: string[] | null
|
indicators?: string[] | null
|
||||||
durationMinDays?: number | null
|
durationMinDays?: number | null
|
||||||
durationMaxDays?: number | null
|
durationMaxDays?: number | null
|
||||||
|
tradingType?: TradingType | null
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
const { apiUrl } = useApiUrlStore()
|
const { apiUrl } = useApiUrlStore()
|
||||||
@@ -69,6 +70,7 @@ const BacktestScanner: React.FC = () => {
|
|||||||
filters.durationMinDays ?? null,
|
filters.durationMinDays ?? null,
|
||||||
filters.durationMaxDays ?? null,
|
filters.durationMaxDays ?? null,
|
||||||
filters.nameContains ?? null,
|
filters.nameContains ?? null,
|
||||||
|
filters.tradingType ?? null,
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
backtests: (response.backtests as LightBacktestResponse[]) || [],
|
backtests: (response.backtests as LightBacktestResponse[]) || [],
|
||||||
@@ -217,6 +219,7 @@ const BacktestScanner: React.FC = () => {
|
|||||||
indicators?: string[] | null
|
indicators?: string[] | null
|
||||||
durationMinDays?: number | null
|
durationMinDays?: number | null
|
||||||
durationMaxDays?: number | null
|
durationMaxDays?: number | null
|
||||||
|
tradingType?: TradingType | null
|
||||||
}) => {
|
}) => {
|
||||||
setFilters(newFilters)
|
setFilters(newFilters)
|
||||||
setCurrentPage(1)
|
setCurrentPage(1)
|
||||||
@@ -246,7 +249,8 @@ const BacktestScanner: React.FC = () => {
|
|||||||
(filters.tickers && filters.tickers.length) ||
|
(filters.tickers && filters.tickers.length) ||
|
||||||
(filters.indicators && filters.indicators.length) ||
|
(filters.indicators && filters.indicators.length) ||
|
||||||
(filters.durationMinDays !== undefined && filters.durationMinDays !== null) ||
|
(filters.durationMinDays !== undefined && filters.durationMinDays !== null) ||
|
||||||
(filters.durationMaxDays !== undefined && filters.durationMaxDays !== null)
|
(filters.durationMaxDays !== undefined && filters.durationMaxDays !== null) ||
|
||||||
|
(filters.tradingType !== undefined && filters.tradingType !== null)
|
||||||
) ? (
|
) ? (
|
||||||
<div className="flex flex-wrap gap-2 items-center">
|
<div className="flex flex-wrap gap-2 items-center">
|
||||||
<span className="text-sm opacity-70 mr-1">Active filters:</span>
|
<span className="text-sm opacity-70 mr-1">Active filters:</span>
|
||||||
@@ -274,6 +278,9 @@ const BacktestScanner: React.FC = () => {
|
|||||||
{(filters.durationMinDays !== undefined && filters.durationMinDays !== null) || (filters.durationMaxDays !== undefined && filters.durationMaxDays !== null) ? (
|
{(filters.durationMinDays !== undefined && filters.durationMinDays !== null) || (filters.durationMaxDays !== undefined && filters.durationMaxDays !== null) ? (
|
||||||
<div className="badge badge-outline">Duration: {filters.durationMinDays ?? 0}–{filters.durationMaxDays ?? '∞'} days</div>
|
<div className="badge badge-outline">Duration: {filters.durationMinDays ?? 0}–{filters.durationMaxDays ?? '∞'} days</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{filters.tradingType !== undefined && filters.tradingType !== null && (
|
||||||
|
<div className="badge badge-outline">Trading Type: {filters.tradingType}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user