Add synthApi (#27)

* Add synthApi

* Put confidence for Synth proba

* Update the code

* Update readme

* Fix bootstraping

* fix github build

* Update the endpoints for scenario

* Add scenario and update backtest modal

* Update bot modal

* Update interfaces for synth

* add synth to backtest

* Add Kelly criterion and better signal

* Update signal confidence

* update doc

* save leaderboard and prediction

* Update nswag to generate ApiClient in the correct path

* Unify the trading modal

* Save miner and prediction

* Update messaging and block new signal until position not close when flipping off

* Rename strategies to indicators

* Update doc

* Update chart + add signal name

* Fix signal direction

* Update docker webui

* remove crypto npm

* Clean
This commit is contained in:
Oda
2025-07-03 00:13:42 +07:00
committed by GitHub
parent 453806356d
commit a547c4a040
103 changed files with 9916 additions and 810 deletions

View File

@@ -1,4 +1,4 @@
using System.ComponentModel.DataAnnotations;
using Managing.Api.Models.Requests;
using Managing.Api.Models.Responses;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
@@ -7,6 +7,8 @@ using Managing.Application.ManageBot.Commands;
using Managing.Common;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using MediatR;
using Microsoft.AspNetCore.Authorization;
@@ -117,24 +119,37 @@ public class BotController : BaseController
return Forbid("You don't have permission to start a bot with this account");
}
// Trigger error if money management is not provided
if (string.IsNullOrEmpty(request.MoneyManagementName) && request.Config.MoneyManagement == null)
// Validate that either money management name or object is provided
if (string.IsNullOrEmpty(request.Config.MoneyManagementName) && request.Config.MoneyManagement == null)
{
return BadRequest("Money management name or money management object is required");
return BadRequest("Either money management name or money management object is required");
}
var user = await GetUser();
// Get money management if name is provided
MoneyManagement moneyManagement = request.Config.MoneyManagement;
if (!string.IsNullOrEmpty(request.MoneyManagementName))
// Get money management - either by name lookup or use provided object
MoneyManagement moneyManagement;
if (!string.IsNullOrEmpty(request.Config.MoneyManagementName))
{
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
moneyManagement =
await _moneyManagementService.GetMoneyMangement(user, request.Config.MoneyManagementName);
if (moneyManagement == null)
{
return BadRequest("Money management not found");
}
}
else
{
moneyManagement = Map(request.Config.MoneyManagement);
// Format percentage values if using custom money management
moneyManagement?.FormatPercentage();
// Ensure user is set for custom money management
if (moneyManagement != null)
{
moneyManagement.User = user;
}
}
// Validate initialTradingBalance
if (request.Config.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
@@ -167,13 +182,45 @@ public class BotController : BaseController
return BadRequest("CloseEarlyWhenProfitable can only be enabled when MaxPositionTimeHours is set");
}
// Update the config with final money management
// Handle scenario - either from ScenarioRequest or ScenarioName
Scenario scenario = null;
if (request.Config.Scenario != null)
{
// Convert ScenarioRequest to Scenario domain object
scenario = new Scenario(request.Config.Scenario.Name, request.Config.Scenario.LoopbackPeriod)
{
User = user
};
// Convert IndicatorRequest objects to Indicator domain objects
foreach (var indicatorRequest in request.Config.Scenario.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,
User = user
};
scenario.AddIndicator(indicator);
}
}
// Map the request to the full TradingBotConfig
var config = new TradingBotConfig
{
AccountName = request.Config.AccountName,
MoneyManagement = moneyManagement,
Ticker = request.Config.Ticker,
ScenarioName = request.Config.ScenarioName,
Scenario = scenario, // Use the converted scenario object
ScenarioName = request.Config.ScenarioName, // Fallback to scenario name if scenario object not provided
Timeframe = request.Config.Timeframe,
IsForWatchingOnly = request.Config.IsForWatchingOnly,
BotTradingBalance = request.Config.BotTradingBalance,
@@ -182,10 +229,15 @@ public class BotController : BaseController
MaxLossStreak = request.Config.MaxLossStreak,
MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable,
UseSynthApi = request.Config.UseSynthApi,
UseForPositionSizing = request.Config.UseForPositionSizing,
UseForSignalFiltering = request.Config.UseForSignalFiltering,
UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss,
// Set computed/default properties
IsForBacktest = false,
FlipPosition = request.Config.BotType == BotType.FlippingBot,
Name = request.Config.Name,
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable
Name = request.Config.Name
};
var result = await _mediator.Send(new StartBotCommand(config, request.Config.Name, user));
@@ -200,6 +252,7 @@ public class BotController : BaseController
}
}
/// <summary>
/// Stops a bot specified by type and name.
/// </summary>
@@ -614,7 +667,7 @@ public class BotController : BaseController
// Get the existing bot to ensure it exists and get current config
var bots = _botService.GetActiveBots();
var existingBot = bots.FirstOrDefault(b => b.Identifier == request.Identifier);
if (existingBot == null)
{
return NotFound($"Bot with identifier '{request.Identifier}' not found");
@@ -630,9 +683,9 @@ public class BotController : BaseController
}
// If the bot name is being changed, check for conflicts
var isNameChanging = !string.IsNullOrEmpty(request.Config.Name) &&
var isNameChanging = !string.IsNullOrEmpty(request.Config.Name) &&
request.Config.Name != request.Identifier;
if (isNameChanging)
{
// Check if new name already exists
@@ -643,31 +696,36 @@ public class BotController : BaseController
}
}
// Validate the money management if provided
if (request.Config.MoneyManagement != null)
// Validate and get the money management
MoneyManagement moneyManagement = null;
if (!string.IsNullOrEmpty(request.MoneyManagementName))
{
// Check if the money management belongs to the user
var userMoneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.Config.MoneyManagement.Name);
if (userMoneyManagement != null && userMoneyManagement.User?.Name != user.Name)
{
return Forbid("You don't have permission to use this money management");
}
}
else if (!string.IsNullOrEmpty(request.MoneyManagementName))
{
// If MoneyManagement is null but MoneyManagementName is provided, load it
var moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
// Load money management by name
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
if (moneyManagement == null)
{
return BadRequest($"Money management '{request.MoneyManagementName}' not found");
}
if (moneyManagement.User?.Name != user.Name)
{
return Forbid("You don't have permission to use this money management");
}
request.Config.MoneyManagement = moneyManagement;
}
else if (request.MoneyManagement != null)
{
// Use provided money management object
moneyManagement = request.MoneyManagement;
// Format percentage values if using custom money management
moneyManagement.FormatPercentage();
// Ensure user is set for custom money management
moneyManagement.User = user;
}
else
{
// Use existing bot's money management if no new one is provided
moneyManagement = existingBot.Config.MoneyManagement;
}
// Validate CloseEarlyWhenProfitable requires MaxPositionTimeHours
@@ -676,27 +734,85 @@ public class BotController : BaseController
return BadRequest("CloseEarlyWhenProfitable requires MaxPositionTimeHours to be set");
}
// Handle scenario - either from ScenarioRequest or ScenarioName
Scenario scenarioForUpdate = null;
if (request.Config.Scenario != null)
{
// Convert ScenarioRequest to Scenario domain object
scenarioForUpdate = new Scenario(request.Config.Scenario.Name, request.Config.Scenario.LoopbackPeriod)
{
User = user
};
// Convert IndicatorRequest objects to Indicator domain objects
foreach (var indicatorRequest in request.Config.Scenario.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,
User = user
};
scenarioForUpdate.AddIndicator(indicator);
}
}
// Map the request to the full TradingBotConfig
var updatedConfig = new TradingBotConfig
{
AccountName = request.Config.AccountName,
MoneyManagement = moneyManagement,
Ticker = request.Config.Ticker,
Scenario = scenarioForUpdate, // Use the converted scenario object
ScenarioName = request.Config.ScenarioName, // Fallback to scenario name if scenario object not provided
Timeframe = request.Config.Timeframe,
IsForWatchingOnly = request.Config.IsForWatchingOnly,
BotTradingBalance = request.Config.BotTradingBalance,
BotType = request.Config.BotType,
CooldownPeriod = request.Config.CooldownPeriod,
MaxLossStreak = request.Config.MaxLossStreak,
MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable,
UseSynthApi = request.Config.UseSynthApi,
UseForPositionSizing = request.Config.UseForPositionSizing,
UseForSignalFiltering = request.Config.UseForSignalFiltering,
UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss,
// Set computed/default properties
IsForBacktest = false,
FlipPosition = request.Config.BotType == BotType.FlippingBot,
Name = request.Config.Name
};
// Update the bot configuration using the enhanced method
var success = await _botService.UpdateBotConfiguration(request.Identifier, request.Config);
var success = await _botService.UpdateBotConfiguration(request.Identifier, updatedConfig);
if (success)
{
var finalBotName = isNameChanging ? request.Config.Name : request.Identifier;
await _hubContext.Clients.All.SendAsync("SendNotification",
$"Bot {finalBotName} configuration updated successfully by {user.Name}." +
(isNameChanging ? $" (renamed from {request.Identifier})" : ""), "Info");
await NotifyBotSubscriberAsync();
return Ok(isNameChanging
return Ok(isNameChanging
? $"Bot configuration updated successfully and renamed to '{request.Config.Name}'"
: "Bot configuration updated successfully");
}
else
{
return BadRequest("Failed to update bot configuration. " +
(isNameChanging ? "The new name might already be in use." : ""));
(isNameChanging ? "The new name might already be in use." : ""));
}
}
catch (Exception ex)
@@ -705,6 +821,18 @@ public class BotController : BaseController
return StatusCode(500, $"Error updating bot configuration: {ex.Message}");
}
}
public MoneyManagement Map(MoneyManagementRequest moneyManagementRequest)
{
return new MoneyManagement
{
Name = moneyManagementRequest.Name,
StopLoss = moneyManagementRequest.StopLoss,
TakeProfit = moneyManagementRequest.TakeProfit,
Leverage = moneyManagementRequest.Leverage,
Timeframe = moneyManagementRequest.Timeframe
};
}
}
/// <summary>
@@ -745,35 +873,7 @@ public class ClosePositionRequest
public class StartBotRequest
{
/// <summary>
/// The trading bot configuration
/// The trading bot configuration request with primary properties
/// </summary>
public TradingBotConfig Config { get; set; }
/// <summary>
/// Optional money management name (if not included in Config.MoneyManagement)
/// </summary>
public string? MoneyManagementName { get; set; }
}
/// <summary>
/// Request model for updating bot configuration
/// </summary>
public class UpdateBotConfigRequest
{
/// <summary>
/// The unique identifier of the bot to update
/// </summary>
[Required]
public string Identifier { get; set; }
/// <summary>
/// The new trading bot configuration
/// </summary>
[Required]
public TradingBotConfig Config { get; set; }
/// <summary>
/// Optional: Money management name to load if Config.MoneyManagement is null
/// </summary>
public string? MoneyManagementName { get; set; }
public TradingBotConfigRequest Config { get; set; }
}