Refactor user settings management to remove IsGmxEnabled and DefaultExchange from updatable fields, introducing GmxSlippage instead. Update UserController, UserService, and related DTOs to reflect these changes, ensuring proper handling of user settings. Adjust database schema and migrations to accommodate the new GmxSlippage property, enhancing user customization options for trading configurations.

This commit is contained in:
2025-12-30 07:19:08 +07:00
parent aa3b06bbe4
commit 21d87efeee
23 changed files with 1908 additions and 32 deletions

View File

@@ -157,6 +157,7 @@ public class UserController : BaseController
{
var user = await GetUser();
// Map API request to DTO
// Note: IsGmxEnabled and DefaultExchange are not updatable via settings endpoint
var settingsDto = new Managing.Application.Abstractions.Models.UserSettingsDto
{
LowEthAmountAlert = settings.LowEthAmountAlert,
@@ -164,12 +165,11 @@ public class UserController : BaseController
AutoswapAmount = settings.AutoswapAmount,
MaxWaitingTimeForPositionToGetFilledSeconds = settings.MaxWaitingTimeForPositionToGetFilledSeconds,
MaxTxnGasFeePerPosition = settings.MaxTxnGasFeePerPosition,
IsGmxEnabled = settings.IsGmxEnabled,
GmxSlippage = settings.GmxSlippage,
MinimumConfidence = settings.MinimumConfidence,
TrendStrongAgreementThreshold = settings.TrendStrongAgreementThreshold,
SignalAgreementThreshold = settings.SignalAgreementThreshold,
AllowSignalTrendOverride = settings.AllowSignalTrendOverride,
DefaultExchange = settings.DefaultExchange
AllowSignalTrendOverride = settings.AllowSignalTrendOverride
};
var updatedUser = await _userService.UpdateUserSettings(user, settingsDto);
return Ok(updatedUser);

View File

@@ -11,13 +11,14 @@ public class UpdateUserSettingsRequest
public decimal? AutoswapAmount { get; set; }
public int? MaxWaitingTimeForPositionToGetFilledSeconds { get; set; }
public decimal? MaxTxnGasFeePerPosition { get; set; }
public bool? IsGmxEnabled { get; set; }
// Note: IsGmxEnabled is not updatable via settings endpoint
public decimal? GmxSlippage { get; set; }
// Indicator Combo Configuration
public Confidence? MinimumConfidence { get; set; }
public decimal? TrendStrongAgreementThreshold { get; set; }
public decimal? SignalAgreementThreshold { get; set; }
public bool? AllowSignalTrendOverride { get; set; }
public TradingExchanges? DefaultExchange { get; set; }
// Note: DefaultExchange is not updatable via settings endpoint
}

View File

@@ -10,13 +10,14 @@ public class UserSettingsDto
public decimal? AutoswapAmount { get; set; }
public int? MaxWaitingTimeForPositionToGetFilledSeconds { get; set; }
public decimal? MaxTxnGasFeePerPosition { get; set; }
public bool? IsGmxEnabled { get; set; }
// Note: IsGmxEnabled is not updatable via settings endpoint
public decimal? GmxSlippage { get; set; }
// Indicator Combo Configuration
public Confidence? MinimumConfidence { get; set; }
public decimal? TrendStrongAgreementThreshold { get; set; }
public decimal? SignalAgreementThreshold { get; set; }
public bool? AllowSignalTrendOverride { get; set; }
public TradingExchanges? DefaultExchange { get; set; }
// Note: DefaultExchange is not updatable via settings endpoint
}

View File

@@ -38,7 +38,8 @@ public interface IEvmManager
Task<Trade> IncreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price,
decimal quantity, decimal? leverage = 1,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null);
decimal? takeProfitPrice = null,
decimal? allowedSlippage = null);
Task<Trade> GetTrade(Account account, string chainName, Ticker ticker);

View File

@@ -21,7 +21,8 @@ public interface IExchangeService
DateTime? currentDate = null,
bool ioc = true,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null);
decimal? takeProfitPrice = null,
decimal? allowedSlippage = null);
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
Task<Balance?> GetBalance(Account account, Ticker ticker);

View File

@@ -58,7 +58,7 @@ public interface ITradingService
Task<Scenario?> GetScenarioByNameUserAsync(string scenarioName, User user);
Task<IEnumerable<Position>> GetPositionByUserIdAsync(int userId);
Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker,
double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5);
double amount, string orderType = "market", double? triggerRatio = null, double? allowedSlippage = null);
/// <summary>
/// Calculates indicator values and generates signals for a given ticker, timeframe, and date range with selected indicators.

View File

@@ -73,6 +73,10 @@ namespace Managing.Application.Trading.Handlers
var stopLossPrice = RiskHelpers.GetStopLossPrice(request.Direction, openPrice, request.MoneyManagement);
var takeProfitPrice = RiskHelpers.GetTakeProfitPrice(request.Direction, openPrice, request.MoneyManagement);
// Get user's slippage setting from IndicatorComboConfig
var config = TradingBox.CreateConfigFromUserSettings(request.User);
var allowedSlippage = request.User.GmxSlippage ?? config.GmxSlippage;
var trade = await exchangeService.OpenTrade(
account,
request.Ticker,
@@ -84,7 +88,8 @@ namespace Managing.Application.Trading.Handlers
isForPaperTrading: request.IsForPaperTrading,
currentDate: request.Date,
stopLossPrice: stopLossPrice,
takeProfitPrice: takeProfitPrice);
takeProfitPrice: takeProfitPrice,
allowedSlippage: allowedSlippage);
position.Open = trade;

View File

@@ -455,7 +455,7 @@ public class TradingService : ITradingService
}
public async Task<SwapInfos> SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker,
double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5)
double amount, string orderType = "market", double? triggerRatio = null, double? allowedSlippage = null)
{
// Get the account for the user
var account = await _accountService.GetAccountByUser(user, accountName, true, false);
@@ -473,6 +473,14 @@ public class TradingService : ITradingService
try
{
// Get user's config to access default slippage value
var config = TradingBox.CreateConfigFromUserSettings(user);
// Use provided allowedSlippage if specified, otherwise use user's GmxSlippage setting, or default from IndicatorComboConfig
var slippageToUse = (double)(allowedSlippage.HasValue
? (decimal)allowedSlippage.Value
: (user.GmxSlippage ?? config.GmxSlippage));
// Call the Web3ProxyService to swap GMX tokens
var swapInfos = await _web3ProxyService.SwapGmxTokensAsync(
account.Key,
@@ -481,7 +489,7 @@ public class TradingService : ITradingService
amount,
orderType,
triggerRatio,
allowedSlippage
slippageToUse
);
return swapInfos;

View File

@@ -359,8 +359,10 @@ public class UserService : IUserService
if (settings.MaxTxnGasFeePerPosition.HasValue)
user.MaxTxnGasFeePerPosition = settings.MaxTxnGasFeePerPosition.Value;
if (settings.IsGmxEnabled.HasValue)
user.IsGmxEnabled = settings.IsGmxEnabled.Value;
// Note: IsGmxEnabled is not updatable via settings endpoint - managed internally
if (settings.GmxSlippage.HasValue)
user.GmxSlippage = settings.GmxSlippage.Value;
if (settings.MinimumConfidence.HasValue)
user.MinimumConfidence = settings.MinimumConfidence.Value;
@@ -374,8 +376,7 @@ public class UserService : IUserService
if (settings.AllowSignalTrendOverride.HasValue)
user.AllowSignalTrendOverride = settings.AllowSignalTrendOverride.Value;
if (settings.DefaultExchange.HasValue)
user.DefaultExchange = settings.DefaultExchange.Value;
// Note: DefaultExchange is not updatable via settings endpoint - managed internally
await _userRepository.SaveOrUpdateUserAsync(user);
return user;

View File

@@ -58,6 +58,12 @@ public class IndicatorComboConfig
/// </summary>
[Id(5)]
public TradingExchanges DefaultExchange { get; set; } = TradingExchanges.GmxV2;
/// <summary>
/// GMX slippage tolerance percentage for trades (default: 0.5%)
/// </summary>
[Id(6)]
public decimal GmxSlippage { get; set; } = 0.5m;
}
public static class TradingBox
@@ -80,6 +86,8 @@ public static class TradingBox
config.AllowSignalTrendOverride = user.AllowSignalTrendOverride ?? true;
// DefaultExchange defaults to GMX if not set
config.DefaultExchange = user.DefaultExchange ?? TradingExchanges.GmxV2;
// GmxSlippage defaults to 0.5% if not set
config.GmxSlippage = user.GmxSlippage ?? 0.5m;
return config;
}

View File

@@ -32,6 +32,7 @@ public class User
[Id(12)] public int? MaxWaitingTimeForPositionToGetFilledSeconds { get; set; }
[Id(13)] public decimal? MaxTxnGasFeePerPosition { get; set; }
[Id(14)] public bool IsGmxEnabled { get; set; } = false;
[Id(20)] public decimal? GmxSlippage { get; set; }
// User Settings - Indicator Combo Configuration
[Id(15)] public Confidence? MinimumConfidence { get; set; }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class AddGmxSlippageSetting : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// Add column with default value
migrationBuilder.AddColumn<decimal>(
name: "GmxSlippage",
table: "Users",
type: "numeric(5,2)",
nullable: true,
defaultValue: 0.5m);
// Update existing NULL values to default
migrationBuilder.Sql(@"
UPDATE ""Users""
SET ""GmxSlippage"" = 0.5
WHERE ""GmxSlippage"" IS NULL;
");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GmxSlippage",
table: "Users");
}
}
}

View File

@@ -1441,6 +1441,9 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<bool>("EnableAutoswap")
.HasColumnType("boolean");
b.Property<decimal?>("GmxSlippage")
.HasColumnType("decimal(5,2)");
b.Property<bool>("IsAdmin")
.HasColumnType("boolean");

View File

@@ -26,6 +26,7 @@ public class UserEntity
public int? MaxWaitingTimeForPositionToGetFilledSeconds { get; set; } = 600; // Default: 10 minutes (600 seconds)
[Column(TypeName = "decimal(18,8)")] public decimal? MaxTxnGasFeePerPosition { get; set; } = 1.5m; // Default: MaximumGasFeeUsd
public bool IsGmxEnabled { get; set; } = false;
[Column(TypeName = "decimal(5,2)")] public decimal? GmxSlippage { get; set; } = 0.5m; // Default: 0.5% slippage for GMX trades
// User Settings - Indicator Combo Configuration
public Confidence? MinimumConfidence { get; set; } = Confidence.Medium; // Default: Medium confidence for context indicators

View File

@@ -140,6 +140,7 @@ public static class PostgreSqlMappers
MaxWaitingTimeForPositionToGetFilledSeconds = entity.MaxWaitingTimeForPositionToGetFilledSeconds,
MaxTxnGasFeePerPosition = entity.MaxTxnGasFeePerPosition,
IsGmxEnabled = entity.IsGmxEnabled,
GmxSlippage = entity.GmxSlippage,
MinimumConfidence = entity.MinimumConfidence,
TrendStrongAgreementThreshold = entity.TrendStrongAgreementThreshold,
SignalAgreementThreshold = entity.SignalAgreementThreshold,
@@ -187,6 +188,7 @@ public static class PostgreSqlMappers
MaxWaitingTimeForPositionToGetFilledSeconds = user.MaxWaitingTimeForPositionToGetFilledSeconds,
MaxTxnGasFeePerPosition = user.MaxTxnGasFeePerPosition,
IsGmxEnabled = user.IsGmxEnabled,
GmxSlippage = user.GmxSlippage,
MinimumConfidence = user.MinimumConfidence,
TrendStrongAgreementThreshold = user.TrendStrongAgreementThreshold,
SignalAgreementThreshold = user.SignalAgreementThreshold,

View File

@@ -263,6 +263,7 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito
existingUser.MaxWaitingTimeForPositionToGetFilledSeconds = user.MaxWaitingTimeForPositionToGetFilledSeconds;
existingUser.MaxTxnGasFeePerPosition = user.MaxTxnGasFeePerPosition;
existingUser.IsGmxEnabled = user.IsGmxEnabled;
existingUser.GmxSlippage = user.GmxSlippage;
existingUser.MinimumConfidence = user.MinimumConfidence;
existingUser.TrendStrongAgreementThreshold = user.TrendStrongAgreementThreshold;
existingUser.SignalAgreementThreshold = user.SignalAgreementThreshold;

View File

@@ -23,7 +23,8 @@ public interface IExchangeProcessor
DateTime? currentDate = null,
bool ioc = true,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null);
decimal? takeProfitPrice = null,
decimal? allowedSlippage = null);
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date);

View File

@@ -46,10 +46,11 @@ namespace Managing.Infrastructure.Exchanges
DateTime? currentDate = null,
bool ioc = true,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null)
decimal? takeProfitPrice = null,
decimal? allowedSlippage = null)
{
_logger.LogDebug(
$"OpenMarketTrade - {ticker} - Type: {tradeType} - {direction} - Price: {price} - Quantity: {quantity} - Leverage: {leverage} - SL: {stopLossPrice} - TP: {takeProfitPrice}");
$"OpenMarketTrade - {ticker} - Type: {tradeType} - {direction} - Price: {price} - Quantity: {quantity} - Leverage: {leverage} - SL: {stopLossPrice} - TP: {takeProfitPrice} - Slippage: {allowedSlippage}");
if (isForPaperTrading)
{
@@ -60,7 +61,7 @@ namespace Managing.Infrastructure.Exchanges
var processor = GetProcessor(account);
return await processor.OpenTrade(account, ticker, direction, price, quantity, leverage, tradeType,
reduceOnly, isForPaperTrading, currentDate, ioc, stopLossPrice, takeProfitPrice);
reduceOnly, isForPaperTrading, currentDate, ioc, stopLossPrice, takeProfitPrice, allowedSlippage);
}
private IExchangeProcessor GetProcessor(Account account)

View File

@@ -35,7 +35,8 @@ namespace Managing.Infrastructure.Exchanges.Exchanges
DateTime? currentDate = null,
bool ioc = true,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null);
decimal? takeProfitPrice = null,
decimal? allowedSlippage = null);
public abstract Orderbook GetOrderbook(Account account, Ticker ticker);
public abstract Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
public abstract Task<List<Trade>> GetOrders(Account account, Ticker ticker);

View File

@@ -174,7 +174,8 @@ public class EvmProcessor : BaseProcessor
DateTime? currentDate = null,
bool ioc = true,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null)
decimal? takeProfitPrice = null,
decimal? allowedSlippage = null)
{
Trade trade;
if (reduceOnly)
@@ -197,7 +198,7 @@ public class EvmProcessor : BaseProcessor
else
{
trade = await _evmManager.IncreasePosition(account, ticker, direction, price, quantity, leverage,
stopLossPrice, takeProfitPrice);
stopLossPrice, takeProfitPrice, allowedSlippage);
}
return trade;

View File

@@ -641,7 +641,8 @@ public class EvmManager : IEvmManager
decimal quantity,
decimal? leverage,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null)
decimal? takeProfitPrice = null,
decimal? allowedSlippage = null)
{
Trade trade = null;
@@ -664,7 +665,8 @@ public class EvmManager : IEvmManager
quantity,
leverage = leverage ?? 1.0m,
stopLossPrice = stopLossPrice,
takeProfitPrice = takeProfitPrice
takeProfitPrice = takeProfitPrice,
allowedSlippage = allowedSlippage.HasValue ? (double)allowedSlippage.Value : (double?)null
});
// Create a trade object using the returned hash

View File

@@ -643,7 +643,8 @@ const openPositionSchema = z.object({
direction: z.enum(Object.keys(TradeDirection) as [string, ...string[]]),
price: z.number().positive().optional(),
quantity: z.number().positive(),
leverage: z.number().positive()
leverage: z.number().positive(),
allowedSlippage: z.number().min(0).max(100).optional()
});
// Schema for cancel-orders request
@@ -774,7 +775,8 @@ export const openGmxPositionImpl = async (
leverage: number,
price?: number,
stopLossPrice?: number,
takeProfitPrice?: number
takeProfitPrice?: number,
allowedSlippage?: number
): Promise<string> => {
return executeWithFallback(
async (sdk, retryCount) => {
@@ -852,12 +854,18 @@ export const openGmxPositionImpl = async (
const leverageBps = BigInt((leverage || 1) * 10000);
const limitPrice = price ? numberToBigint(price, 30) : undefined;
// Use provided slippage or default to 0.5% (50 basis points)
// Convert percentage to basis points (e.g., 0.5% = 50 bps)
const slippageBps = allowedSlippage != null
? Math.floor(allowedSlippage * 100) // Convert percentage to basis points
: 50; // Default 0.5% = 50 basis points
const params: PositionIncreaseParams = {
payAmount: collateralAmount,
marketAddress: marketInfo.marketTokenAddress,
payTokenAddress: collateralToken.address,
collateralTokenAddress: collateralToken.address,
allowedSlippageBps: 55, // 0.55% slippage
allowedSlippageBps: slippageBps,
leverage: leverageBps,
skipSimulation: true,
referralCodeForTxn: encodeReferralCode("kaigen_ai"),
@@ -971,7 +979,8 @@ export async function openGmxPosition(
quantity?: number,
leverage?: number,
stopLossPrice?: number,
takeProfitPrice?: number
takeProfitPrice?: number,
allowedSlippage?: number
) {
try {
// Validate the request parameters
@@ -984,7 +993,8 @@ export async function openGmxPosition(
quantity,
leverage,
stopLossPrice,
takeProfitPrice
takeProfitPrice,
allowedSlippage
});
// Get client for the address
@@ -999,7 +1009,8 @@ export async function openGmxPosition(
leverage,
price,
stopLossPrice,
takeProfitPrice
takeProfitPrice,
allowedSlippage
);
return {