Enhance user settings management by adding new properties and updating related functionality

This commit introduces additional user settings properties, including TrendStrongAgreementThreshold, SignalAgreementThreshold, AllowSignalTrendOverride, and DefaultExchange, to the User entity and associated DTOs. The UserController and UserService are updated to handle these new settings, allowing users to customize their trading configurations more effectively. Database migrations are also included to ensure proper schema updates for the new fields.
This commit is contained in:
2025-12-30 06:48:08 +07:00
parent 79d8a381d9
commit aa3b06bbe4
26 changed files with 5909 additions and 57 deletions

View File

@@ -165,7 +165,11 @@ public class UserController : BaseController
MaxWaitingTimeForPositionToGetFilledSeconds = settings.MaxWaitingTimeForPositionToGetFilledSeconds,
MaxTxnGasFeePerPosition = settings.MaxTxnGasFeePerPosition,
IsGmxEnabled = settings.IsGmxEnabled,
MinimumConfidence = settings.MinimumConfidence
MinimumConfidence = settings.MinimumConfidence,
TrendStrongAgreementThreshold = settings.TrendStrongAgreementThreshold,
SignalAgreementThreshold = settings.SignalAgreementThreshold,
AllowSignalTrendOverride = settings.AllowSignalTrendOverride,
DefaultExchange = settings.DefaultExchange
};
var updatedUser = await _userService.UpdateUserSettings(user, settingsDto);
return Ok(updatedUser);

View File

@@ -5,12 +5,19 @@ namespace Managing.Api.Models.Requests;
public class UpdateUserSettingsRequest
{
// Trading Configuration
public decimal? LowEthAmountAlert { get; set; }
public bool? EnableAutoswap { get; set; }
public decimal? AutoswapAmount { get; set; }
public int? MaxWaitingTimeForPositionToGetFilledSeconds { get; set; }
public decimal? MaxTxnGasFeePerPosition { get; set; }
public bool? IsGmxEnabled { 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; }
}

View File

@@ -4,12 +4,19 @@ namespace Managing.Application.Abstractions.Models;
public class UserSettingsDto
{
// Trading Configuration
public decimal? LowEthAmountAlert { get; set; }
public bool? EnableAutoswap { get; set; }
public decimal? AutoswapAmount { get; set; }
public int? MaxWaitingTimeForPositionToGetFilledSeconds { get; set; }
public decimal? MaxTxnGasFeePerPosition { get; set; }
public bool? IsGmxEnabled { 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; }
}

View File

@@ -2,6 +2,7 @@ using Managing.Common;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Indicators;
using Managing.Domain.Shared.Helpers;
namespace Managing.Application.Abstractions.Grains;
@@ -17,8 +18,11 @@ public interface IScenarioRunnerGrain : IGrainWithGuidKey
/// <param name="config">The trading bot configuration</param>
/// <param name="previousSignals">Previous signals to consider</param>
/// <param name="tradingExchange">Trading Exchange</param>
/// <param name="lastCandle">The last candle</param>
/// <param name="indicatorComboConfig">Optional indicator combo configuration (for user settings)</param>
/// <returns>The generated signal or null if no signal</returns>
Task<LightSignal> GetSignals(TradingBotConfig config, Dictionary<string, LightSignal> previousSignals,
Enums.TradingExchanges tradingExchange,
Candle lastCandle);
Candle lastCandle,
IndicatorComboConfig indicatorComboConfig = null);
}

View File

@@ -714,7 +714,11 @@ public class FuturesBot : TradingBotBase
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(_scopeFactory, async grainFactory =>
{
var scenarioRunnerGrain = grainFactory.GetGrain<IScenarioRunnerGrain>(Guid.NewGuid());
var signal = await scenarioRunnerGrain.GetSignals(Config, Signals, Account.Exchange, LastCandle);
// Create indicator combo config from user settings
var indicatorComboConfig = TradingBox.CreateConfigFromUserSettings(Account.User);
var signal = await scenarioRunnerGrain.GetSignals(Config, Signals, Account.Exchange, LastCandle, indicatorComboConfig);
if (signal == null) return;
await AddSignal(signal);
});

View File

@@ -313,6 +313,10 @@ public class AgentGrain : Grain, IAgentGrain
public async Task<BalanceCheckResult> CheckAndEnsureEthBalanceAsync(Guid requestingBotId, string accountName)
{
// Get user settings
var userId = (int)this.GetPrimaryKeyLong();
var user = await _userService.GetUserByIdAsync(userId);
// Check if a swap is already in progress
if (_state.State.IsSwapInProgress)
{
@@ -358,6 +362,15 @@ public class AgentGrain : Grain, IAgentGrain
};
}
// Check low ETH amount alert threshold
var lowEthAlertThreshold = user.LowEthAmountAlert ?? Constants.GMX.Config.MinimumTradeEthBalanceUsd;
if (balanceData.EthValueInUsd < lowEthAlertThreshold)
{
_logger.LogWarning(
"ETH balance below alert threshold for user {UserId} - ETH: {EthValue:F2} USD (threshold: {Threshold:F2} USD)",
userId, balanceData.EthValueInUsd, lowEthAlertThreshold);
}
_logger.LogInformation(
"Agent {UserId} balance check - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (cached: {IsCached})",
this.GetPrimaryKeyLong(), balanceData.EthValueInUsd, balanceData.UsdcValue,
@@ -402,18 +415,34 @@ public class AgentGrain : Grain, IAgentGrain
};
}
// Check if we have enough USDC for swap (need at least 5 USD for swap)
if (balanceData.UsdcValue <
(Constants.GMX.Config.MinimumPositionAmount + (decimal)Constants.GMX.Config.AutoSwapAmount))
// Check if autoswap is enabled for this user
if (!user.EnableAutoswap)
{
_logger.LogInformation("Autoswap is disabled for user {UserId}, skipping swap",
userId);
return new BalanceCheckResult
{
IsSuccessful = false,
FailureReason = BalanceCheckFailureReason.None,
Message = "Autoswap is disabled for this user",
ShouldStopBot = false
};
}
// Get autoswap amount from user settings or use default
var autoswapAmount = user.AutoswapAmount ?? (decimal)Constants.GMX.Config.AutoSwapAmount;
// Check if we have enough USDC for swap
if (balanceData.UsdcValue < (Constants.GMX.Config.MinimumPositionAmount + autoswapAmount))
{
_logger.LogWarning(
"Insufficient USDC balance for swap - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (need {AutoSwapAmount} USD for swap)",
balanceData.EthValueInUsd, balanceData.UsdcValue, Constants.GMX.Config.AutoSwapAmount);
balanceData.EthValueInUsd, balanceData.UsdcValue, autoswapAmount);
return new BalanceCheckResult
{
IsSuccessful = false,
FailureReason = BalanceCheckFailureReason.InsufficientUsdcForSwap,
Message = $"Insufficient USDC balance for swap (need {Constants.GMX.Config.AutoSwapAmount} USD)",
Message = $"Insufficient USDC balance for swap (need {autoswapAmount} USD)",
ShouldStopBot = true
};
}
@@ -440,22 +469,18 @@ public class AgentGrain : Grain, IAgentGrain
try
{
_logger.LogInformation("Initiating USDC to ETH swap for agent {UserId} - swapping 5 USDC",
this.GetPrimaryKeyLong());
_logger.LogInformation("Initiating USDC to ETH swap for agent {UserId} - swapping {Amount} USDC",
this.GetPrimaryKeyLong(), autoswapAmount);
// Get user for the swap
var userId = (int)this.GetPrimaryKeyLong();
var user = await _userService.GetUserByIdAsync(userId);
// Perform the swap
// Perform the swap using user's autoswap amount
var swapInfo = await _tradingService.SwapGmxTokensAsync(user, accountName,
Ticker.USDC, Ticker.ETH, Constants.GMX.Config.AutoSwapAmount);
Ticker.USDC, Ticker.ETH, (double)autoswapAmount);
if (swapInfo.Success)
{
_logger.LogInformation(
"Successfully swapped 5 USDC to ETH for agent {UserId}, transaction hash: {Hash}",
userId, swapInfo.Hash);
"Successfully swapped {Amount} USDC to ETH for agent {UserId}, transaction hash: {Hash}",
autoswapAmount, userId, swapInfo.Hash);
// Update last swap time and invalidate cache
_state.State.LastSwapTime = DateTime.UtcNow;

View File

@@ -860,7 +860,11 @@ public class SpotBot : TradingBotBase
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(_scopeFactory, async grainFactory =>
{
var scenarioRunnerGrain = grainFactory.GetGrain<IScenarioRunnerGrain>(Guid.NewGuid());
var signal = await scenarioRunnerGrain.GetSignals(Config, Signals, Account.Exchange, LastCandle);
// Create indicator combo config from user settings
var indicatorComboConfig = TradingBox.CreateConfigFromUserSettings(Account.User);
var signal = await scenarioRunnerGrain.GetSignals(Config, Signals, Account.Exchange, LastCandle, indicatorComboConfig);
if (signal == null) return;
await AddSignal(signal);
});

View File

@@ -56,7 +56,7 @@ public class ScenarioRunnerGrain : Grain, IScenarioRunnerGrain
}
public async Task<LightSignal> GetSignals(TradingBotConfig config, Dictionary<string, LightSignal> previousSignals,
TradingExchanges tradingExchanges, Candle candle)
TradingExchanges tradingExchanges, Candle candle, IndicatorComboConfig indicatorComboConfig = null)
{
try
{
@@ -84,11 +84,16 @@ public class ScenarioRunnerGrain : Grain, IScenarioRunnerGrain
_logger.LogInformation($"Fetched {candlesList.Count} candles for {config.Ticker} for {config.Name}");
// Use provided config or default
var comboConfig = indicatorComboConfig ?? new IndicatorComboConfig();
var signal = TradingBox.GetSignal(
candlesList,
config.Scenario,
previousSignals,
config.Scenario?.LookbackPeriod ?? 1);
comboConfig,
config.Scenario?.LookbackPeriod ?? 1,
null);
if (signal != null && signal.Date > candle.Date)
{

View File

@@ -365,6 +365,18 @@ public class UserService : IUserService
if (settings.MinimumConfidence.HasValue)
user.MinimumConfidence = settings.MinimumConfidence.Value;
if (settings.TrendStrongAgreementThreshold.HasValue)
user.TrendStrongAgreementThreshold = settings.TrendStrongAgreementThreshold.Value;
if (settings.SignalAgreementThreshold.HasValue)
user.SignalAgreementThreshold = settings.SignalAgreementThreshold.Value;
if (settings.AllowSignalTrendOverride.HasValue)
user.AllowSignalTrendOverride = settings.AllowSignalTrendOverride.Value;
if (settings.DefaultExchange.HasValue)
user.DefaultExchange = settings.DefaultExchange.Value;
await _userRepository.SaveOrUpdateUserAsync(user);
return user;
}

View File

@@ -9,6 +9,8 @@ using Managing.Domain.Statistics;
using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Trades;
using Managing.Domain.Users;
using Orleans;
using static Managing.Common.Enums;
namespace Managing.Domain.Shared.Helpers;
@@ -16,45 +18,72 @@ namespace Managing.Domain.Shared.Helpers;
/// <summary>
/// Configuration for strategy combination logic
/// </summary>
[GenerateSerializer]
public class IndicatorComboConfig
{
/// <summary>
/// Minimum percentage of trend strategies that must agree for strong trend (default: 66%)
/// </summary>
[Id(0)]
public decimal TrendStrongAgreementThreshold { get; set; } = 0.66m;
/// <summary>
/// Minimum percentage of signal strategies that must agree (default: 50%)
/// </summary>
[Id(1)]
public decimal SignalAgreementThreshold { get; set; } = 0.5m;
/// <summary>
/// Whether to allow signal strategies to override conflicting trends (default: true)
/// This is useful for trend reversal signals
/// </summary>
[Id(2)]
public bool AllowSignalTrendOverride { get; set; } = true;
/// <summary>
/// Minimum confidence level to return a signal (default: Low)
/// </summary>
[Id(3)]
public Confidence MinimumConfidence { get; set; } = Confidence.Low;
/// <summary>
/// Minimum confidence level required from context strategies (default: Medium)
/// Context strategies evaluate market conditions - higher requirements mean more conservative trading
/// </summary>
[Id(4)]
public Confidence MinimumContextConfidence { get; set; } = Confidence.Medium;
/// <summary>
/// Default exchange to use when signals don't specify one
/// Default exchange to use when signals don't specify one (defaults to GMX)
/// </summary>
public TradingExchanges DefaultExchange { get; set; } = TradingExchanges.Binance;
[Id(5)]
public TradingExchanges DefaultExchange { get; set; } = TradingExchanges.GmxV2;
}
public static class TradingBox
{
private static readonly IndicatorComboConfig _defaultConfig = new();
/// <summary>
/// Creates an IndicatorComboConfig from user settings, using defaults for any null values
/// </summary>
/// <param name="user">The user with settings to apply</param>
/// <returns>IndicatorComboConfig with user settings applied</returns>
public static IndicatorComboConfig CreateConfigFromUserSettings(User user)
{
var config = new IndicatorComboConfig();
// Apply user's indicator combo settings, using defaults if not set
config.MinimumContextConfidence = user.MinimumConfidence ?? Confidence.Medium;
config.TrendStrongAgreementThreshold = user.TrendStrongAgreementThreshold ?? 0.66m;
config.SignalAgreementThreshold = user.SignalAgreementThreshold ?? 0.5m;
config.AllowSignalTrendOverride = user.AllowSignalTrendOverride ?? true;
// DefaultExchange defaults to GMX if not set
config.DefaultExchange = user.DefaultExchange ?? TradingExchanges.GmxV2;
return config;
}
public static LightSignal GetSignal(IReadOnlyList<Candle> newCandles, LightScenario scenario,
Dictionary<string, LightSignal> previousSignal, int? loopbackPeriod = 1)
{

View File

@@ -25,12 +25,18 @@ public class User
[Id(8)] public DateTimeOffset? LastConnectionDate { get; set; }
// User Settings
// User Settings - Trading Configuration
[Id(9)] public decimal? LowEthAmountAlert { get; set; }
[Id(10)] public bool EnableAutoswap { get; set; } = false;
[Id(11)] public decimal? AutoswapAmount { get; set; }
[Id(12)] public int? MaxWaitingTimeForPositionToGetFilledSeconds { get; set; }
[Id(13)] public decimal? MaxTxnGasFeePerPosition { get; set; }
[Id(14)] public bool IsGmxEnabled { get; set; } = false;
// User Settings - Indicator Combo Configuration
[Id(15)] public Confidence? MinimumConfidence { get; set; }
[Id(16)] public decimal? TrendStrongAgreementThreshold { get; set; }
[Id(17)] public decimal? SignalAgreementThreshold { get; set; }
[Id(18)] public bool? AllowSignalTrendOverride { get; set; }
[Id(19)] public TradingExchanges? DefaultExchange { get; set; }
}

View File

@@ -14,7 +14,8 @@ namespace Managing.Infrastructure.Databases.Migrations
name: "AutoswapAmount",
table: "Users",
type: "numeric(18,8)",
nullable: true);
nullable: true,
defaultValue: 3m);
migrationBuilder.AddColumn<bool>(
name: "EnableAutoswap",
@@ -34,25 +35,29 @@ namespace Managing.Infrastructure.Databases.Migrations
name: "LowEthAmountAlert",
table: "Users",
type: "numeric(18,8)",
nullable: true);
nullable: true,
defaultValue: 1.5m);
migrationBuilder.AddColumn<decimal>(
name: "MaxTxnGasFeePerPosition",
table: "Users",
type: "numeric(18,8)",
nullable: true);
nullable: true,
defaultValue: 1.5m);
migrationBuilder.AddColumn<int>(
name: "MaxWaitingTimeForPositionToGetFilledSeconds",
table: "Users",
type: "integer",
nullable: true);
nullable: true,
defaultValue: 600);
migrationBuilder.AddColumn<string>(
name: "MinimumConfidence",
table: "Users",
type: "text",
nullable: true);
nullable: true,
defaultValue: "Medium");
}
/// <inheritdoc />

View File

@@ -0,0 +1,116 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class SetUserSettingsDefaults : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// Set default values for existing NULL values
migrationBuilder.Sql(@"
UPDATE ""Users""
SET ""LowEthAmountAlert"" = 1.5
WHERE ""LowEthAmountAlert"" IS NULL;
");
migrationBuilder.Sql(@"
UPDATE ""Users""
SET ""AutoswapAmount"" = 3
WHERE ""AutoswapAmount"" IS NULL;
");
migrationBuilder.Sql(@"
UPDATE ""Users""
SET ""MaxWaitingTimeForPositionToGetFilledSeconds"" = 600
WHERE ""MaxWaitingTimeForPositionToGetFilledSeconds"" IS NULL;
");
migrationBuilder.Sql(@"
UPDATE ""Users""
SET ""MaxTxnGasFeePerPosition"" = 1.5
WHERE ""MaxTxnGasFeePerPosition"" IS NULL;
");
migrationBuilder.Sql(@"
UPDATE ""Users""
SET ""MinimumConfidence"" = 'Medium'
WHERE ""MinimumConfidence"" IS NULL;
");
// Alter columns to set default values for future inserts
migrationBuilder.AlterColumn<decimal>(
name: "LowEthAmountAlert",
table: "Users",
type: "numeric(18,8)",
nullable: true,
defaultValue: 1.5m);
migrationBuilder.AlterColumn<decimal>(
name: "AutoswapAmount",
table: "Users",
type: "numeric(18,8)",
nullable: true,
defaultValue: 3m);
migrationBuilder.AlterColumn<int>(
name: "MaxWaitingTimeForPositionToGetFilledSeconds",
table: "Users",
type: "integer",
nullable: true,
defaultValue: 600);
migrationBuilder.AlterColumn<decimal>(
name: "MaxTxnGasFeePerPosition",
table: "Users",
type: "numeric(18,8)",
nullable: true,
defaultValue: 1.5m);
migrationBuilder.AlterColumn<string>(
name: "MinimumConfidence",
table: "Users",
type: "text",
nullable: true,
defaultValue: "Medium");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
// Remove default values (revert to nullable without defaults)
migrationBuilder.AlterColumn<decimal>(
name: "LowEthAmountAlert",
table: "Users",
type: "numeric(18,8)",
nullable: true);
migrationBuilder.AlterColumn<decimal>(
name: "AutoswapAmount",
table: "Users",
type: "numeric(18,8)",
nullable: true);
migrationBuilder.AlterColumn<int>(
name: "MaxWaitingTimeForPositionToGetFilledSeconds",
table: "Users",
type: "integer",
nullable: true);
migrationBuilder.AlterColumn<decimal>(
name: "MaxTxnGasFeePerPosition",
table: "Users",
type: "numeric(18,8)",
nullable: true);
migrationBuilder.AlterColumn<string>(
name: "MinimumConfidence",
table: "Users",
type: "text",
nullable: true);
}
}
}

View File

@@ -0,0 +1,88 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class AddIndicatorComboConfigSettings : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// Add columns with defaults
migrationBuilder.AddColumn<decimal>(
name: "TrendStrongAgreementThreshold",
table: "Users",
type: "numeric(5,4)",
nullable: true,
defaultValue: 0.66m);
migrationBuilder.AddColumn<decimal>(
name: "SignalAgreementThreshold",
table: "Users",
type: "numeric(5,4)",
nullable: true,
defaultValue: 0.5m);
migrationBuilder.AddColumn<bool>(
name: "AllowSignalTrendOverride",
table: "Users",
type: "boolean",
nullable: true,
defaultValue: true);
migrationBuilder.AddColumn<string>(
name: "DefaultExchange",
table: "Users",
type: "text",
nullable: true,
defaultValue: "GmxV2");
// Update existing NULL values to defaults
migrationBuilder.Sql(@"
UPDATE ""Users""
SET ""TrendStrongAgreementThreshold"" = 0.66
WHERE ""TrendStrongAgreementThreshold"" IS NULL;
");
migrationBuilder.Sql(@"
UPDATE ""Users""
SET ""SignalAgreementThreshold"" = 0.5
WHERE ""SignalAgreementThreshold"" IS NULL;
");
migrationBuilder.Sql(@"
UPDATE ""Users""
SET ""AllowSignalTrendOverride"" = true
WHERE ""AllowSignalTrendOverride"" IS NULL;
");
migrationBuilder.Sql(@"
UPDATE ""Users""
SET ""DefaultExchange"" = 'GmxV2'
WHERE ""DefaultExchange"" IS NULL;
");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TrendStrongAgreementThreshold",
table: "Users");
migrationBuilder.DropColumn(
name: "SignalAgreementThreshold",
table: "Users");
migrationBuilder.DropColumn(
name: "AllowSignalTrendOverride",
table: "Users");
migrationBuilder.DropColumn(
name: "DefaultExchange",
table: "Users");
}
}
}

View File

@@ -0,0 +1,47 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class UpdateDefaultExchangeToGmxV2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// Update existing Binance values to GmxV2
migrationBuilder.Sql(@"
UPDATE ""Users""
SET ""DefaultExchange"" = 'GmxV2'
WHERE ""DefaultExchange"" = 'Binance' OR ""DefaultExchange"" IS NULL;
");
// Update the default value for future inserts
migrationBuilder.AlterColumn<string>(
name: "DefaultExchange",
table: "Users",
type: "text",
nullable: true,
defaultValue: "GmxV2");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
// Revert to Binance
migrationBuilder.Sql(@"
UPDATE ""Users""
SET ""DefaultExchange"" = 'Binance'
WHERE ""DefaultExchange"" = 'GmxV2';
");
migrationBuilder.AlterColumn<string>(
name: "DefaultExchange",
table: "Users",
type: "text",
nullable: true,
defaultValue: "Binance");
}
}
}

View File

@@ -1425,6 +1425,9 @@ namespace Managing.Infrastructure.Databases.Migrations
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<bool?>("AllowSignalTrendOverride")
.HasColumnType("boolean");
b.Property<decimal?>("AutoswapAmount")
.HasColumnType("decimal(18,8)");
@@ -1432,6 +1435,9 @@ namespace Managing.Infrastructure.Databases.Migrations
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("DefaultExchange")
.HasColumnType("text");
b.Property<bool>("EnableAutoswap")
.HasColumnType("boolean");
@@ -1464,10 +1470,16 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<string>("OwnerWalletAddress")
.HasColumnType("text");
b.Property<decimal?>("SignalAgreementThreshold")
.HasColumnType("decimal(5,4)");
b.Property<string>("TelegramChannel")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<decimal?>("TrendStrongAgreementThreshold")
.HasColumnType("decimal(5,4)");
b.HasKey("Id");
b.HasIndex("AgentName");

View File

@@ -19,14 +19,20 @@ public class UserEntity
public DateTimeOffset? LastConnectionDate { get; set; }
public bool IsAdmin { get; set; }
// User Settings
[Column(TypeName = "decimal(18,8)")] public decimal? LowEthAmountAlert { get; set; }
// User Settings - Trading Configuration
[Column(TypeName = "decimal(18,8)")] public decimal? LowEthAmountAlert { get; set; } = 1.5m; // Default: MinimumTradeEthBalanceUsd
public bool EnableAutoswap { get; set; } = false;
[Column(TypeName = "decimal(18,8)")] public decimal? AutoswapAmount { get; set; }
public int? MaxWaitingTimeForPositionToGetFilledSeconds { get; set; }
[Column(TypeName = "decimal(18,8)")] public decimal? MaxTxnGasFeePerPosition { get; set; }
[Column(TypeName = "decimal(18,8)")] public decimal? AutoswapAmount { get; set; } = 3m; // Default: AutoSwapAmount
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;
public Confidence? MinimumConfidence { get; set; }
// User Settings - Indicator Combo Configuration
public Confidence? MinimumConfidence { get; set; } = Confidence.Medium; // Default: Medium confidence for context indicators
[Column(TypeName = "decimal(5,4)")] public decimal? TrendStrongAgreementThreshold { get; set; } = 0.66m; // Default: 66% agreement required
[Column(TypeName = "decimal(5,4)")] public decimal? SignalAgreementThreshold { get; set; } = 0.5m; // Default: 50% agreement required
public bool? AllowSignalTrendOverride { get; set; } = true; // Default: Allow signal strategies to override trends
public TradingExchanges? DefaultExchange { get; set; } = TradingExchanges.GmxV2; // Default exchange
// Navigation properties
public virtual ICollection<AccountEntity> Accounts { get; set; } = new List<AccountEntity>();

View File

@@ -103,6 +103,8 @@ public class ManagingDbContext : DbContext
entity.Property(e => e.TelegramChannel).HasMaxLength(255);
entity.Property(e => e.MinimumConfidence)
.HasConversion<string>(); // Store enum as string
entity.Property(e => e.DefaultExchange)
.HasConversion<string>(); // Store enum as string
// Create indexes for performance
entity.HasIndex(e => e.Name).IsUnique();

View File

@@ -141,6 +141,10 @@ public static class PostgreSqlMappers
MaxTxnGasFeePerPosition = entity.MaxTxnGasFeePerPosition,
IsGmxEnabled = entity.IsGmxEnabled,
MinimumConfidence = entity.MinimumConfidence,
TrendStrongAgreementThreshold = entity.TrendStrongAgreementThreshold,
SignalAgreementThreshold = entity.SignalAgreementThreshold,
AllowSignalTrendOverride = entity.AllowSignalTrendOverride,
DefaultExchange = entity.DefaultExchange,
Accounts = entity.Accounts?.Select(MapAccountWithoutUser).ToList() ?? new List<Account>()
};
}
@@ -183,7 +187,11 @@ public static class PostgreSqlMappers
MaxWaitingTimeForPositionToGetFilledSeconds = user.MaxWaitingTimeForPositionToGetFilledSeconds,
MaxTxnGasFeePerPosition = user.MaxTxnGasFeePerPosition,
IsGmxEnabled = user.IsGmxEnabled,
MinimumConfidence = user.MinimumConfidence
MinimumConfidence = user.MinimumConfidence,
TrendStrongAgreementThreshold = user.TrendStrongAgreementThreshold,
SignalAgreementThreshold = user.SignalAgreementThreshold,
AllowSignalTrendOverride = user.AllowSignalTrendOverride,
DefaultExchange = user.DefaultExchange
};
}

View File

@@ -264,6 +264,10 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito
existingUser.MaxTxnGasFeePerPosition = user.MaxTxnGasFeePerPosition;
existingUser.IsGmxEnabled = user.IsGmxEnabled;
existingUser.MinimumConfidence = user.MinimumConfidence;
existingUser.TrendStrongAgreementThreshold = user.TrendStrongAgreementThreshold;
existingUser.SignalAgreementThreshold = user.SignalAgreementThreshold;
existingUser.AllowSignalTrendOverride = user.AllowSignalTrendOverride;
existingUser.DefaultExchange = user.DefaultExchange;
_context.Users.Update(existingUser);

View File

@@ -49,6 +49,24 @@ export interface User {
ownerWalletAddress?: string | null;
isAdmin?: boolean;
lastConnectionDate?: Date | null;
lowEthAmountAlert?: number | null;
enableAutoswap?: boolean;
autoswapAmount?: number | null;
maxWaitingTimeForPositionToGetFilledSeconds?: number | null;
maxTxnGasFeePerPosition?: number | null;
isGmxEnabled?: boolean;
minimumConfidence?: Confidence | null;
trendStrongAgreementThreshold?: number | null;
signalAgreementThreshold?: number | null;
allowSignalTrendOverride?: boolean | null;
defaultExchange?: TradingExchanges | null;
}
export enum Confidence {
Low = "Low",
Medium = "Medium",
High = "High",
None = "None",
}
export interface Balance {
@@ -586,13 +604,6 @@ export enum SignalStatus {
Expired = "Expired",
}
export enum Confidence {
Low = "Low",
Medium = "Medium",
High = "High",
None = "None",
}
export interface Candle {
exchange: TradingExchanges;
ticker: Ticker;
@@ -1490,6 +1501,20 @@ export interface LoginRequest {
ownerWalletAddress?: string | null;
}
export interface UpdateUserSettingsRequest {
lowEthAmountAlert?: number | null;
enableAutoswap?: boolean | null;
autoswapAmount?: number | null;
maxWaitingTimeForPositionToGetFilledSeconds?: number | null;
maxTxnGasFeePerPosition?: number | null;
isGmxEnabled?: boolean | null;
minimumConfidence?: Confidence | null;
trendStrongAgreementThreshold?: number | null;
signalAgreementThreshold?: number | null;
allowSignalTrendOverride?: boolean | null;
defaultExchange?: TradingExchanges | null;
}
export interface PaginatedWhitelistAccountsResponse {
accounts?: WhitelistAccount[] | null;
totalCount?: number;

View File

@@ -4414,6 +4414,45 @@ export class UserClient extends AuthorizedApiBase {
}
return Promise.resolve<string>(null as any);
}
user_UpdateUserSettings(settings: UpdateUserSettingsRequest): Promise<User> {
let url_ = this.baseUrl + "/User/settings";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(settings);
let options_: RequestInit = {
body: content_,
method: "PUT",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processUser_UpdateUserSettings(_response);
});
}
protected processUser_UpdateUserSettings(response: Response): Promise<User> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as User;
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<User>(null as any);
}
}
export class WhitelistClient extends AuthorizedApiBase {
@@ -4591,6 +4630,24 @@ export interface User {
ownerWalletAddress?: string | null;
isAdmin?: boolean;
lastConnectionDate?: Date | null;
lowEthAmountAlert?: number | null;
enableAutoswap?: boolean;
autoswapAmount?: number | null;
maxWaitingTimeForPositionToGetFilledSeconds?: number | null;
maxTxnGasFeePerPosition?: number | null;
isGmxEnabled?: boolean;
minimumConfidence?: Confidence | null;
trendStrongAgreementThreshold?: number | null;
signalAgreementThreshold?: number | null;
allowSignalTrendOverride?: boolean | null;
defaultExchange?: TradingExchanges | null;
}
export enum Confidence {
Low = "Low",
Medium = "Medium",
High = "High",
None = "None",
}
export interface Balance {
@@ -5128,13 +5185,6 @@ export enum SignalStatus {
Expired = "Expired",
}
export enum Confidence {
Low = "Low",
Medium = "Medium",
High = "High",
None = "None",
}
export interface Candle {
exchange: TradingExchanges;
ticker: Ticker;
@@ -6032,6 +6082,20 @@ export interface LoginRequest {
ownerWalletAddress?: string | null;
}
export interface UpdateUserSettingsRequest {
lowEthAmountAlert?: number | null;
enableAutoswap?: boolean | null;
autoswapAmount?: number | null;
maxWaitingTimeForPositionToGetFilledSeconds?: number | null;
maxTxnGasFeePerPosition?: number | null;
isGmxEnabled?: boolean | null;
minimumConfidence?: Confidence | null;
trendStrongAgreementThreshold?: number | null;
signalAgreementThreshold?: number | null;
allowSignalTrendOverride?: boolean | null;
defaultExchange?: TradingExchanges | null;
}
export interface PaginatedWhitelistAccountsResponse {
accounts?: WhitelistAccount[] | null;
totalCount?: number;

View File

@@ -49,6 +49,24 @@ export interface User {
ownerWalletAddress?: string | null;
isAdmin?: boolean;
lastConnectionDate?: Date | null;
lowEthAmountAlert?: number | null;
enableAutoswap?: boolean;
autoswapAmount?: number | null;
maxWaitingTimeForPositionToGetFilledSeconds?: number | null;
maxTxnGasFeePerPosition?: number | null;
isGmxEnabled?: boolean;
minimumConfidence?: Confidence | null;
trendStrongAgreementThreshold?: number | null;
signalAgreementThreshold?: number | null;
allowSignalTrendOverride?: boolean | null;
defaultExchange?: TradingExchanges | null;
}
export enum Confidence {
Low = "Low",
Medium = "Medium",
High = "High",
None = "None",
}
export interface Balance {
@@ -586,13 +604,6 @@ export enum SignalStatus {
Expired = "Expired",
}
export enum Confidence {
Low = "Low",
Medium = "Medium",
High = "High",
None = "None",
}
export interface Candle {
exchange: TradingExchanges;
ticker: Ticker;
@@ -1490,6 +1501,20 @@ export interface LoginRequest {
ownerWalletAddress?: string | null;
}
export interface UpdateUserSettingsRequest {
lowEthAmountAlert?: number | null;
enableAutoswap?: boolean | null;
autoswapAmount?: number | null;
maxWaitingTimeForPositionToGetFilledSeconds?: number | null;
maxTxnGasFeePerPosition?: number | null;
isGmxEnabled?: boolean | null;
minimumConfidence?: Confidence | null;
trendStrongAgreementThreshold?: number | null;
signalAgreementThreshold?: number | null;
allowSignalTrendOverride?: boolean | null;
defaultExchange?: TradingExchanges | null;
}
export interface PaginatedWhitelistAccountsResponse {
accounts?: WhitelistAccount[] | null;
totalCount?: number;