Price reminder and init approval
* Start price reminder grain * Add config and init grain at startup * Save init wallet when already init
This commit is contained in:
@@ -333,6 +333,34 @@ public class AccountService : IAccountService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<ExchangeApprovalStatus>> GetExchangeApprovalStatusAsync(User user)
|
||||
{
|
||||
var accounts = await GetAccountsByUserAsync(user, hideSecrets: true, getBalance: false);
|
||||
|
||||
var exchangeStatuses = new List<ExchangeApprovalStatus>();
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
exchangeStatuses.Add(new ExchangeApprovalStatus
|
||||
{
|
||||
Exchange = TradingExchanges.GmxV2,
|
||||
IsApproved = account.IsGmxInitialized
|
||||
});
|
||||
}
|
||||
|
||||
// Future: Add other exchanges here when supported
|
||||
// e.g.:
|
||||
// var hasEvmInitialized = accounts.Any(account =>
|
||||
// account.Exchange == TradingExchanges.Evm && account.IsGmxInitialized);
|
||||
// exchangeStatuses.Add(new ExchangeApprovalStatus
|
||||
// {
|
||||
// Exchange = TradingExchanges.Evm,
|
||||
// IsApproved = hasEvmInitialized
|
||||
// });
|
||||
|
||||
return exchangeStatuses;
|
||||
}
|
||||
|
||||
private async Task ManagePropertiesAsync(bool hideSecrets, bool getBalance, Account account)
|
||||
{
|
||||
if (account != null)
|
||||
|
||||
@@ -210,7 +210,7 @@ public class TradingBotBase : ITradingBot
|
||||
Low = position.Open.Price,
|
||||
Volume = 0,
|
||||
Exchange = TradingExchanges.Evm,
|
||||
Ticker = Config.Ticker.ToString(),
|
||||
Ticker = Config.Ticker,
|
||||
Timeframe = Config.Timeframe
|
||||
};
|
||||
|
||||
|
||||
215
src/Managing.Application/Grains/CandleStoreGrain.cs
Normal file
215
src/Managing.Application/Grains/CandleStoreGrain.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using Managing.Application.Abstractions.Grains;
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Domain.Candles;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Orleans.Streams;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Grains;
|
||||
|
||||
/// <summary>
|
||||
/// Grain for managing in-memory historical candle data with Orleans state persistence.
|
||||
/// Subscribes to price streams and maintains a rolling window of 500 candles.
|
||||
/// </summary>
|
||||
public class CandleStoreGrain : Grain, ICandleStoreGrain, IAsyncObserver<Candle>
|
||||
{
|
||||
private readonly IPersistentState<CandleStoreGrainState> _state;
|
||||
private readonly ILogger<CandleStoreGrain> _logger;
|
||||
private readonly ICandleRepository _candleRepository;
|
||||
|
||||
private const int MaxCandleCount = 500;
|
||||
private IAsyncStream<Candle> _priceStream;
|
||||
private StreamSubscriptionHandle<Candle> _streamSubscription;
|
||||
|
||||
public CandleStoreGrain(
|
||||
[PersistentState("candle-store-state", "candle-store")]
|
||||
IPersistentState<CandleStoreGrainState> state,
|
||||
ILogger<CandleStoreGrain> logger,
|
||||
ICandleRepository candleRepository)
|
||||
{
|
||||
_state = state;
|
||||
_logger = logger;
|
||||
_candleRepository = candleRepository;
|
||||
}
|
||||
|
||||
public override async Task OnActivateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var grainKey = this.GetPrimaryKeyString();
|
||||
_logger.LogInformation("CandleStoreGrain activated for key: {GrainKey}", grainKey);
|
||||
|
||||
// Parse the grain key to extract exchange, ticker, and timeframe
|
||||
var parts = grainKey.Split('-');
|
||||
if (parts.Length != 3)
|
||||
{
|
||||
_logger.LogError("Invalid grain key format: {GrainKey}. Expected format: Exchange-Ticker-Timeframe", grainKey);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Enum.TryParse<TradingExchanges>(parts[0], out var exchange) ||
|
||||
!Enum.TryParse<Ticker>(parts[1], out var ticker) ||
|
||||
!Enum.TryParse<Timeframe>(parts[2], out var timeframe))
|
||||
{
|
||||
_logger.LogError("Failed to parse grain key components: {GrainKey}", grainKey);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize state if empty
|
||||
if (_state.State.Candles == null || _state.State.Candles.Count == 0)
|
||||
{
|
||||
await LoadInitialCandlesAsync(exchange, ticker, timeframe);
|
||||
}
|
||||
|
||||
// Subscribe to the price stream
|
||||
await SubscribeToPriceStreamAsync(grainKey);
|
||||
|
||||
await base.OnActivateAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public override async Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken)
|
||||
{
|
||||
// Unsubscribe from the stream
|
||||
if (_streamSubscription != null)
|
||||
{
|
||||
await _streamSubscription.UnsubscribeAsync();
|
||||
_streamSubscription = null;
|
||||
}
|
||||
|
||||
await base.OnDeactivateAsync(reason, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<List<Candle>> GetCandlesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Task.FromResult(_state.State.Candles?.ToList() ?? new List<Candle>());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving candles for grain {GrainKey}", this.GetPrimaryKeyString());
|
||||
return Task.FromResult(new List<Candle>());
|
||||
}
|
||||
}
|
||||
|
||||
// Stream observer implementation
|
||||
public async Task OnNextAsync(Candle candle, StreamSequenceToken token = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Received new candle for {GrainKey} at {Date}",
|
||||
this.GetPrimaryKeyString(), candle.Date);
|
||||
|
||||
// Initialize state if needed
|
||||
if (_state.State.Candles == null)
|
||||
{
|
||||
_state.State.Candles = new List<Candle>();
|
||||
}
|
||||
|
||||
// Add the new candle
|
||||
_state.State.Candles.Add(candle);
|
||||
|
||||
// Maintain rolling window of 500 candles
|
||||
if (_state.State.Candles.Count > MaxCandleCount)
|
||||
{
|
||||
// Sort by date and keep the most recent 500
|
||||
_state.State.Candles = _state.State.Candles
|
||||
.OrderBy(c => c.Date)
|
||||
.TakeLast(MaxCandleCount)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Persist the updated state
|
||||
await _state.WriteStateAsync();
|
||||
|
||||
_logger.LogTrace("Updated candle store for {GrainKey}, total candles: {Count}",
|
||||
this.GetPrimaryKeyString(), _state.State.Candles.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing new candle for grain {GrainKey}", this.GetPrimaryKeyString());
|
||||
}
|
||||
}
|
||||
|
||||
public Task OnCompletedAsync()
|
||||
{
|
||||
_logger.LogInformation("Stream completed for grain {GrainKey}", this.GetPrimaryKeyString());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnErrorAsync(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Stream error for grain {GrainKey}", this.GetPrimaryKeyString());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task LoadInitialCandlesAsync(TradingExchanges exchange, Ticker ticker, Timeframe timeframe)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Loading initial candles for {Exchange}-{Ticker}-{Timeframe}",
|
||||
exchange, ticker, timeframe);
|
||||
|
||||
// Load the last 500 candles from the database
|
||||
var endDate = DateTime.UtcNow;
|
||||
var startDate = endDate.AddDays(-30); // Look back 30 days to ensure we get enough data
|
||||
|
||||
var candles = await _candleRepository.GetCandles(exchange, ticker, timeframe, startDate, endDate, MaxCandleCount);
|
||||
|
||||
if (candles?.Any() == true)
|
||||
{
|
||||
_state.State.Candles = candles
|
||||
.OrderBy(c => c.Date)
|
||||
.TakeLast(MaxCandleCount)
|
||||
.ToList();
|
||||
|
||||
await _state.WriteStateAsync();
|
||||
|
||||
_logger.LogInformation("Loaded {Count} initial candles for {Exchange}-{Ticker}-{Timeframe}",
|
||||
_state.State.Candles.Count, exchange, ticker, timeframe);
|
||||
}
|
||||
else
|
||||
{
|
||||
_state.State.Candles = new List<Candle>();
|
||||
await _state.WriteStateAsync();
|
||||
|
||||
_logger.LogWarning("No initial candles found for {Exchange}-{Ticker}-{Timeframe}",
|
||||
exchange, ticker, timeframe);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading initial candles for {Exchange}-{Ticker}-{Timeframe}",
|
||||
exchange, ticker, timeframe);
|
||||
|
||||
// Initialize empty state on error
|
||||
_state.State.Candles = new List<Candle>();
|
||||
await _state.WriteStateAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SubscribeToPriceStreamAsync(string streamKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
var streamProvider = this.GetStreamProvider("DefaultStreamProvider");
|
||||
_priceStream = streamProvider.GetStream<Candle>(streamKey);
|
||||
|
||||
_streamSubscription = await _priceStream.SubscribeAsync(this);
|
||||
|
||||
_logger.LogInformation("Subscribed to price stream for {StreamKey}", streamKey);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error subscribing to price stream for {StreamKey}", streamKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State object for CandleStoreGrain containing the rolling window of candles
|
||||
/// </summary>
|
||||
[GenerateSerializer]
|
||||
public class CandleStoreGrainState
|
||||
{
|
||||
[Id(0)]
|
||||
public List<Candle> Candles { get; set; } = new();
|
||||
}
|
||||
172
src/Managing.Application/Grains/PriceFetcher5MinGrain.cs
Normal file
172
src/Managing.Application/Grains/PriceFetcher5MinGrain.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using Managing.Application.Abstractions.Grains;
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Candles;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Orleans.Concurrency;
|
||||
using Orleans.Streams;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Grains;
|
||||
|
||||
/// <summary>
|
||||
/// StatelessWorker grain for fetching 5-minute price data from external APIs and publishing to Orleans streams.
|
||||
/// This grain runs every 5 minutes and processes all exchange/ticker combinations for the 5-minute timeframe.
|
||||
/// </summary>
|
||||
[StatelessWorker]
|
||||
public class PriceFetcher5MinGrain : Grain, IPriceFetcher5MinGrain, IRemindable
|
||||
{
|
||||
private readonly ILogger<PriceFetcher5MinGrain> _logger;
|
||||
private readonly IExchangeService _exchangeService;
|
||||
private readonly ICandleRepository _candleRepository;
|
||||
private readonly IGrainFactory _grainFactory;
|
||||
|
||||
private const string FetchPricesReminderName = "FetchPricesReminder";
|
||||
|
||||
// Predefined lists of trading parameters to fetch
|
||||
private static readonly TradingExchanges[] SupportedExchanges =
|
||||
{
|
||||
TradingExchanges.GmxV2
|
||||
};
|
||||
|
||||
private static readonly Ticker[] SupportedTickers = Constants.GMX.Config.SupportedTickers;
|
||||
|
||||
private static readonly Timeframe TargetTimeframe = Timeframe.FiveMinutes;
|
||||
|
||||
public PriceFetcher5MinGrain(
|
||||
ILogger<PriceFetcher5MinGrain> logger,
|
||||
IExchangeService exchangeService,
|
||||
ICandleRepository candleRepository,
|
||||
IGrainFactory grainFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_exchangeService = exchangeService;
|
||||
_candleRepository = candleRepository;
|
||||
_grainFactory = grainFactory;
|
||||
}
|
||||
|
||||
public override async Task OnActivateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("PriceFetcher5MinGrain activated");
|
||||
|
||||
// Register a reminder to fetch prices every 5 minutes
|
||||
await this.RegisterOrUpdateReminder(
|
||||
FetchPricesReminderName,
|
||||
TimeSpan.FromMinutes(5),
|
||||
TimeSpan.FromMinutes(5));
|
||||
|
||||
await base.OnActivateAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> FetchAndPublishPricesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Starting 5-minute price fetch cycle");
|
||||
|
||||
var fetchTasks = new List<Task>();
|
||||
|
||||
// Create fetch tasks for all exchange/ticker combinations for 5-minute timeframe
|
||||
foreach (var exchange in SupportedExchanges)
|
||||
{
|
||||
foreach (var ticker in SupportedTickers)
|
||||
{
|
||||
fetchTasks.Add(FetchAndPublish(exchange, ticker, TargetTimeframe));
|
||||
}
|
||||
}
|
||||
|
||||
// Execute all fetch operations in parallel
|
||||
await Task.WhenAll(fetchTasks);
|
||||
|
||||
_logger.LogInformation("Completed 5-minute price fetch cycle for {TotalCombinations} combinations",
|
||||
fetchTasks.Count);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during price fetch cycle");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FetchAndPublish(TradingExchanges exchange, Ticker ticker, Timeframe timeframe)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create a dummy account for API calls (this may need to be adjusted based on your implementation)
|
||||
var account = new Account
|
||||
{
|
||||
Name = "PriceFetcher",
|
||||
Exchange = exchange,
|
||||
Type = AccountType.Watch
|
||||
};
|
||||
|
||||
// Get the last candle date from database
|
||||
var existingCandles = await _candleRepository.GetCandles(exchange, ticker, timeframe,
|
||||
DateTime.UtcNow.AddDays(-7), 1);
|
||||
|
||||
var startDate = existingCandles.Any()
|
||||
? existingCandles.Max(c => c.Date).AddMinutes(GetTimeframeMinutes(timeframe))
|
||||
: DateTime.UtcNow.AddDays(-1);
|
||||
|
||||
// Fetch new candles from external API
|
||||
var newCandles = await _exchangeService.GetCandles(account, ticker, startDate, timeframe, true);
|
||||
|
||||
if (newCandles?.Any() == true)
|
||||
{
|
||||
var streamProvider = this.GetStreamProvider("DefaultStreamProvider");
|
||||
var streamKey = $"{exchange}-{ticker}-{timeframe}";
|
||||
var stream = streamProvider.GetStream<Candle>(streamKey);
|
||||
|
||||
_logger.LogDebug("Fetched {CandleCount} new candles for {StreamKey}",
|
||||
newCandles.Count, streamKey);
|
||||
|
||||
// Process each new candle
|
||||
foreach (var candle in newCandles.OrderBy(c => c.Date))
|
||||
{
|
||||
// Ensure candle has correct metadata
|
||||
candle.Exchange = exchange;
|
||||
candle.Ticker = ticker;
|
||||
candle.Timeframe = timeframe;
|
||||
|
||||
// Save to database
|
||||
await _candleRepository.InsertCandle(candle);
|
||||
|
||||
// Publish to stream
|
||||
await stream.OnNextAsync(candle);
|
||||
|
||||
_logger.LogTrace("Published candle for {StreamKey} at {Date}",
|
||||
streamKey, candle.Date);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching prices for {Exchange}-{Ticker}-{Timeframe}",
|
||||
exchange, ticker, timeframe);
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetTimeframeMinutes(Timeframe timeframe) => timeframe switch
|
||||
{
|
||||
Timeframe.OneMinute => 1,
|
||||
Timeframe.FiveMinutes => 5,
|
||||
Timeframe.FifteenMinutes => 15,
|
||||
Timeframe.ThirtyMinutes => 30,
|
||||
Timeframe.OneHour => 60,
|
||||
Timeframe.FourHour => 240,
|
||||
Timeframe.OneDay => 1440,
|
||||
_ => 1
|
||||
};
|
||||
|
||||
public async Task ReceiveReminder(string reminderName, TickStatus status)
|
||||
{
|
||||
if (reminderName == FetchPricesReminderName)
|
||||
{
|
||||
await FetchAndPublishPricesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/Managing.Application/Grains/PriceFetcherInitializer.cs
Normal file
25
src/Managing.Application/Grains/PriceFetcherInitializer.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Managing.Application.Abstractions.Grains;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Managing.Application.Grains;
|
||||
|
||||
public class PriceFetcherInitializer : IHostedService
|
||||
{
|
||||
private readonly IClusterClient _clusterClient;
|
||||
|
||||
public PriceFetcherInitializer(IClusterClient clusterClient)
|
||||
{
|
||||
_clusterClient = clusterClient;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_clusterClient.GetGrain<IPriceFetcher5MinGrain>(0);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Managing.Application.Hubs;
|
||||
|
||||
public class CandleHub : Hub
|
||||
{
|
||||
private int ConnectionCount = 0;
|
||||
private readonly IStreamService _streamService;
|
||||
|
||||
public CandleHub(IStreamService streamService)
|
||||
{
|
||||
_streamService = streamService;
|
||||
}
|
||||
|
||||
public async override Task OnConnectedAsync()
|
||||
{
|
||||
ConnectionCount++;
|
||||
|
||||
await Clients.Caller.SendAsync("Message", $"Connected successfully on candle hub. ConnectionId : {Context.ConnectionId}");
|
||||
|
||||
//await _streamService.SubscribeCandle(async (candle) => {
|
||||
// await Clients.All.SendAsync("Candle", candle);
|
||||
//});
|
||||
await _streamService.SubscribeCandle();
|
||||
await base.OnConnectedAsync();
|
||||
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception ex)
|
||||
{
|
||||
await Clients.Caller.SendAsync("Message", $"Shuting down candle hub. ConnectionId : {Context.ConnectionId}");
|
||||
|
||||
ConnectionCount--;
|
||||
if(ConnectionCount == 0)
|
||||
{
|
||||
await _streamService.UnSubscribeCandle();
|
||||
}
|
||||
await base.OnDisconnectedAsync(ex);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="9.2.1"/>
|
||||
<PackageReference Include="Microsoft.Orleans.Reminders" Version="9.2.1"/>
|
||||
<PackageReference Include="Microsoft.Orleans.Runtime" Version="9.2.1"/>
|
||||
<PackageReference Include="Microsoft.Orleans.Streaming" Version="9.2.1"/>
|
||||
<PackageReference Include="Polly" Version="8.4.0"/>
|
||||
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Hubs;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Managing.Application.Shared;
|
||||
|
||||
public class StreamService : IStreamService
|
||||
{
|
||||
private readonly IExchangeStream _exchangeStream;
|
||||
private readonly IHubContext<CandleHub> _hubContext;
|
||||
|
||||
|
||||
public StreamService(IExchangeStream exchangeStream, IHubContext<CandleHub> hubContext)
|
||||
{
|
||||
_exchangeStream = exchangeStream;
|
||||
_hubContext = hubContext;
|
||||
}
|
||||
|
||||
public async Task SubscribeCandle()
|
||||
{
|
||||
await _exchangeStream.StartBinanceWorker(Common.Enums.Ticker.BTC, async (candle) => {
|
||||
await _hubContext.Clients.All.SendAsync(candle.Ticker, candle);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task UnSubscribeCandle()
|
||||
{
|
||||
await _exchangeStream.StopBinanceWorker();
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ public class TradingService : ITradingService
|
||||
private readonly ITradingRepository _tradingRepository;
|
||||
private readonly IExchangeService _exchangeService;
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly IAccountRepository _accountRepository;
|
||||
private readonly ICacheService _cacheService;
|
||||
private readonly IMessengerService _messengerService;
|
||||
private readonly IStatisticRepository _statisticRepository;
|
||||
@@ -35,6 +36,7 @@ public class TradingService : ITradingService
|
||||
IExchangeService exchangeService,
|
||||
ILogger<TradingService> logger,
|
||||
IAccountService accountService,
|
||||
IAccountRepository accountRepository,
|
||||
ICacheService cacheService,
|
||||
IMessengerService messengerService,
|
||||
IStatisticRepository statisticRepository,
|
||||
@@ -45,6 +47,7 @@ public class TradingService : ITradingService
|
||||
_exchangeService = exchangeService;
|
||||
_logger = logger;
|
||||
_accountService = accountService;
|
||||
_accountRepository = accountRepository;
|
||||
_cacheService = cacheService;
|
||||
_messengerService = messengerService;
|
||||
_statisticRepository = statisticRepository;
|
||||
@@ -319,7 +322,7 @@ public class TradingService : ITradingService
|
||||
$"[{shortAddress}][{ticker}] No change - Quantity still {newTrade.Quantity}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.LogError($"[{shortAddress}][{ticker}] Impossible to fetch trader");
|
||||
}
|
||||
@@ -357,7 +360,7 @@ public class TradingService : ITradingService
|
||||
public List<string> PositionIdentifiers { get; set; }
|
||||
}
|
||||
|
||||
public async Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress)
|
||||
public async Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress, TradingExchanges tradingExchange)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -368,7 +371,39 @@ public class TradingService : ITradingService
|
||||
{ Success = false, Error = "Public address cannot be null or empty" };
|
||||
}
|
||||
|
||||
return await _evmManager.InitAddress(publicAddress);
|
||||
// Check if the account is already initialized
|
||||
var account = await _accountRepository.GetAccountByKeyAsync(publicAddress);
|
||||
if (account != null && account.IsGmxInitialized)
|
||||
{
|
||||
_logger.LogInformation("Account with address {PublicAddress} is already initialized for GMX", publicAddress);
|
||||
return new PrivyInitAddressResponse
|
||||
{
|
||||
Success = true,
|
||||
Address = publicAddress,
|
||||
IsAlreadyInitialized = true
|
||||
};
|
||||
}
|
||||
|
||||
PrivyInitAddressResponse initResult;
|
||||
switch (tradingExchange)
|
||||
{
|
||||
case TradingExchanges.GmxV2:
|
||||
initResult = await _evmManager.InitAddressForGMX(publicAddress);
|
||||
break;
|
||||
default:
|
||||
initResult = await _evmManager.InitAddressForGMX(publicAddress);
|
||||
break;
|
||||
}
|
||||
|
||||
// If initialization was successful, update the account's initialization status
|
||||
if (initResult.Success && account != null)
|
||||
{
|
||||
account.IsGmxInitialized = true;
|
||||
await _accountRepository.UpdateAccountAsync(account);
|
||||
_logger.LogInformation("Updated account {AccountName} GMX initialization status to true", account.Name);
|
||||
}
|
||||
|
||||
return initResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user