GMX v2 - Trading (#7)

* Move PrivateKeys.cs

* Update gitignore

* Update gitignore

* updt

* Extract GmxServiceTests.cs

* Refact

* update todo

* Update code

* Fix hashdata

* Replace static token hashed datas

* Set allowance

* Add get orders

* Add get orders tests

* Add ignore

* add close orders

* revert

* Add get gas limit

* Start increasePosition. Todo: Finish GetExecutionFee and estimateGas

* little refact

* Update gitignore

* Fix namespaces and clean repo

* Add tests samples

* Add execution fee

* Add increase position

* Handle backtest on the frontend

* Add tests

* Update increase

* Test increase

* fix increase

* Fix size

* Start get position

* Update get positions

* Fix get position

* Update rpc and trade mappers

* Finish close position

* Fix leverage
This commit is contained in:
Oda
2025-01-30 23:06:22 +07:00
committed by GitHub
parent ecaa89c67b
commit 65bdb8e34f
156 changed files with 11253 additions and 4073 deletions

View File

@@ -7,49 +7,43 @@ using static Managing.Common.Enums;
namespace Managing.Application.Trading;
public class ClosePositionCommandHandler : ICommandHandler<ClosePositionCommand, Position>
public class ClosePositionCommandHandler(
IExchangeService exchangeService,
IAccountService accountService,
ITradingService tradingService)
: ICommandHandler<ClosePositionCommand, Position>
{
private readonly IExchangeService _exchangeService;
private readonly IAccountService _accountService;
private readonly ITradingService _tradingService;
public ClosePositionCommandHandler(
IExchangeService exchangeService,
IAccountService accountService,
ITradingService tradingService)
{
_exchangeService = exchangeService;
_accountService = accountService;
_tradingService = tradingService;
}
public async Task<Position> Handle(ClosePositionCommand request)
{
// Get Trade
var account = await _accountService.GetAccount(request.Position.AccountName, false, false);
var account = await accountService.GetAccount(request.Position.AccountName, false, false);
if (request.Position == null)
{
_ = _exchangeService.CancelOrder(account, request.Position.Ticker).Result;
_ = exchangeService.CancelOrder(account, request.Position.Ticker).Result;
return request.Position;
}
var isForPaperTrading = request.Position.Initiator == PositionInitiator.PaperTrading;
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading ?
request.ExecutionPrice.GetValueOrDefault() :
_exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
? request.ExecutionPrice.GetValueOrDefault()
: exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
// Close market
var closedPosition = _exchangeService.ClosePosition(account, request.Position, lastPrice, isForPaperTrading).Result;
var closeRequestedOrders = isForPaperTrading ? true : _exchangeService.CancelOrder(account, request.Position.Ticker).Result;
var closedPosition =
await exchangeService.ClosePosition(account, request.Position, lastPrice, isForPaperTrading);
var closeRequestedOrders =
isForPaperTrading || (await exchangeService.CancelOrder(account, request.Position.Ticker));
if (closeRequestedOrders || closedPosition.Status == (TradeStatus.PendingOpen | TradeStatus.Filled))
{
request.Position.Status = PositionStatus.Finished;
request.Position.ProfitAndLoss = TradingBox.GetProfitAndLoss(request.Position, closedPosition.Quantity, lastPrice);
_tradingService.UpdatePosition(request.Position);
request.Position.ProfitAndLoss =
TradingBox.GetProfitAndLoss(request.Position, closedPosition.Quantity, lastPrice,
request.Position.Open.Leverage);
tradingService.UpdatePosition(request.Position);
}
return request.Position;
}
}
}

View File

@@ -7,33 +7,30 @@ using static Managing.Common.Enums;
namespace Managing.Application.Trading
{
public class OpenPositionCommandHandler : ICommandHandler<OpenPositionRequest, Position>
public class OpenPositionCommandHandler(
IExchangeService exchangeService,
IAccountService accountService,
ITradingService tradingService)
: ICommandHandler<OpenPositionRequest, Position>
{
private readonly IExchangeService _exchangeService;
private readonly IAccountService _accountService;
private readonly ITradingService _tradingService;
public OpenPositionCommandHandler(
IExchangeService exchangeService,
IAccountService accountService,
ITradingService tradingService)
public async Task<Position> Handle(OpenPositionRequest request)
{
_exchangeService = exchangeService;
_accountService = accountService;
_tradingService = tradingService;
}
public Task<Position> Handle(OpenPositionRequest request)
{
var account = _accountService.GetAccount(request.AccountName, hideSecrets: false, getBalance: false).Result;
if (!request.IsForPaperTrading && !_exchangeService.CancelOrder(account, request.Ticker).Result)
var account = await accountService.GetAccount(request.AccountName, hideSecrets: false, getBalance: false);
if (!request.IsForPaperTrading)
{
throw new Exception($"Not able to close all orders for {request.Ticker}");
var cancelOrderResult = await exchangeService.CancelOrder(account, request.Ticker);
if (!cancelOrderResult)
{
throw new Exception($"Not able to close all orders for {request.Ticker}");
}
}
var initiator = request.IsForPaperTrading ? PositionInitiator.PaperTrading : request.Initiator;
var position = new Position(request.AccountName, request.Direction, request.Ticker, request.MoneyManagement, initiator, request.Date);
var balance = request.IsForPaperTrading ? request.Balance.GetValueOrDefault() : _exchangeService.GetBalance(account, request.IsForPaperTrading).Result;
var position = new Position(request.AccountName, request.Direction, request.Ticker, request.MoneyManagement,
initiator, request.Date);
var balance = request.IsForPaperTrading
? request.Balance.GetValueOrDefault()
: exchangeService.GetBalance(account, request.IsForPaperTrading).Result;
var balanceAtRisk = RiskHelpers.GetBalanceAtRisk(balance, request.MoneyManagement);
if (balanceAtRisk < 13)
@@ -41,11 +38,13 @@ namespace Managing.Application.Trading
throw new Exception($"Try to risk {balanceAtRisk} $ but inferior to minimum to trade");
}
var price = request.IsForPaperTrading && request.Price.HasValue ?
request.Price.Value :
_exchangeService.GetPrice(account, request.Ticker, DateTime.Now);
var price = request.IsForPaperTrading && request.Price.HasValue
? request.Price.Value
: exchangeService.GetPrice(account, request.Ticker, DateTime.Now);
var quantity = balanceAtRisk / price;
var fee = request.IsForPaperTrading ? request.Fee.GetValueOrDefault() : _tradingService.GetFee(account, request.IsForPaperTrading);
var fee = request.IsForPaperTrading
? request.Fee.GetValueOrDefault()
: tradingService.GetFee(account, request.IsForPaperTrading);
var expectedStatus = GetExpectedStatus(request);
position.Open = TradingPolicies.OpenPosition(expectedStatus).Execute(
@@ -53,18 +52,18 @@ namespace Managing.Application.Trading
{
var openPrice = request.IsForPaperTrading || request.Price.HasValue
? request.Price.Value
: _exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction);
: exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction);
var trade = _exchangeService.OpenTrade(
account,
request.Ticker,
request.Direction,
openPrice,
quantity,
request.MoneyManagement.Leverage,
TradeType.Limit,
isForPaperTrading: request.IsForPaperTrading,
currentDate: request.Date).Result;
var trade = exchangeService.OpenTrade(
account,
request.Ticker,
request.Direction,
openPrice,
quantity,
request.MoneyManagement.Leverage,
TradeType.Limit,
isForPaperTrading: request.IsForPaperTrading,
currentDate: request.Date).Result;
trade.Fee = TradingHelpers.GetFeeAmount(fee, openPrice * quantity, account.Exchange);
return trade;
@@ -73,11 +72,12 @@ namespace Managing.Application.Trading
if (IsOpenTradeHandled(position.Open.Status, account.Exchange) && !request.IgnoreSLTP.GetValueOrDefault())
{
var closeDirection = request.Direction == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long;
var closeDirection = request.Direction == TradeDirection.Long
? TradeDirection.Short
: TradeDirection.Long;
// Stop loss
position.StopLoss = _exchangeService.BuildEmptyTrade(
position.StopLoss = exchangeService.BuildEmptyTrade(
request.Ticker,
RiskHelpers.GetStopLossPrice(request.Direction, position.Open.Price, request.MoneyManagement),
position.Open.Quantity,
@@ -87,10 +87,11 @@ namespace Managing.Application.Trading
request.Date,
TradeStatus.PendingOpen);
position.StopLoss.Fee = TradingHelpers.GetFeeAmount(fee, position.StopLoss.Price * position.StopLoss.Quantity, account.Exchange);
position.StopLoss.Fee = TradingHelpers.GetFeeAmount(fee,
position.StopLoss.Price * position.StopLoss.Quantity, account.Exchange);
// Take profit
position.TakeProfit1 = _exchangeService.BuildEmptyTrade(
position.TakeProfit1 = exchangeService.BuildEmptyTrade(
request.Ticker,
RiskHelpers.GetTakeProfitPrice(request.Direction, position.Open.Price, request.MoneyManagement),
quantity,
@@ -100,13 +101,16 @@ namespace Managing.Application.Trading
request.Date,
TradeStatus.PendingOpen);
position.TakeProfit1.Fee = TradingHelpers.GetFeeAmount(fee, position.TakeProfit1.Price * position.TakeProfit1.Quantity, account.Exchange);
position.TakeProfit1.Fee = TradingHelpers.GetFeeAmount(fee,
position.TakeProfit1.Price * position.TakeProfit1.Quantity, account.Exchange);
}
position.Status = IsOpenTradeHandled(position.Open.Status, account.Exchange) ? position.Status : PositionStatus.Rejected;
_tradingService.InsertPosition(position);
position.Status = IsOpenTradeHandled(position.Open.Status, account.Exchange)
? position.Status
: PositionStatus.Rejected;
tradingService.InsertPosition(position);
return Task.FromResult(position);
return position;
}
private static TradeStatus GetExpectedStatus(OpenPositionRequest request)
@@ -122,7 +126,7 @@ namespace Managing.Application.Trading
private static bool IsOpenTradeHandled(TradeStatus tradeStatus, TradingExchanges exchange)
{
return tradeStatus == TradeStatus.Filled
|| (exchange == TradingExchanges.Evm && tradeStatus == TradeStatus.Requested);
|| (exchange == TradingExchanges.Evm && tradeStatus == TradeStatus.Requested);
}
}
}
}

View File

@@ -76,7 +76,6 @@ public class TradingService : ITradingService
}
public Scenario GetScenarioByName(string scenario)
{
return _tradingRepository.GetScenarioByName(scenario);
@@ -126,7 +125,8 @@ public class TradingService : ITradingService
if (quantityInPosition > 0)
{
// Position still open
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.Open.Quantity, lastPrice);
position.ProfitAndLoss =
TradingBox.GetProfitAndLoss(position, position.Open.Quantity, lastPrice, position.Open.Leverage);
_logger.LogInformation($"Position is still open - PNL : {position.ProfitAndLoss.Realized} $");
_logger.LogInformation($"Requested trades : {orders.Count}");
}
@@ -138,7 +138,8 @@ public class TradingService : ITradingService
// SL hit
_logger.LogInformation($"Stop loss is filled on exchange.");
position.StopLoss.SetStatus(TradeStatus.Filled);
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.StopLoss.Quantity, position.StopLoss.Price);
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.StopLoss.Quantity,
position.StopLoss.Price, position.Open.Leverage);
_ = _exchangeService.CancelOrder(account, position.Ticker);
}
else if (orders.All(o => o.TradeType != TradeType.TakeProfit))
@@ -147,19 +148,22 @@ public class TradingService : ITradingService
if (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit2 != null)
{
position.TakeProfit2.SetStatus(TradeStatus.Filled);
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.TakeProfit2.Quantity, position.TakeProfit2.Price);
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.TakeProfit2.Quantity,
position.TakeProfit2.Price, 1);
_logger.LogInformation($"TakeProfit 2 is filled on exchange.");
}
else
{
position.TakeProfit1.SetStatus(TradeStatus.Filled);
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.TakeProfit1.Quantity, position.TakeProfit1.Price);
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(position, position.TakeProfit1.Quantity,
position.TakeProfit1.Price, 1);
_logger.LogInformation($"TakeProfit 1 is filled on exchange.");
}
}
else
{
_logger.LogInformation($"Position closed manually or forced close by exchange because quantity in position is below 0.");
_logger.LogInformation(
$"Position closed manually or forced close by exchange because quantity in position is below 0.");
position.Status = PositionStatus.Finished;
if (orders.Any()) await _exchangeService.CancelOrder(account, position.Ticker);
@@ -202,10 +206,8 @@ public class TradingService : ITradingService
return 0.000665M;
}
return _cacheService.GetOrSave($"Fee-{account.Exchange}", () =>
{
return _tradingRepository.GetFee(TradingExchanges.Evm)?.Cost ?? 0m;
}, TimeSpan.FromHours(2));
return _cacheService.GetOrSave($"Fee-{account.Exchange}",
() => { return _tradingRepository.GetFee(TradingExchanges.Evm)?.Cost ?? 0m; }, TimeSpan.FromHours(2));
}
public void UpdatePosition(Position position)
@@ -246,7 +248,6 @@ public class TradingService : ITradingService
{
await ManageTrader(a, availableTickers);
}
}
_cacheService.SaveValue(key, aqip, TimeSpan.FromMinutes(10));
@@ -255,8 +256,10 @@ public class TradingService : ITradingService
public IEnumerable<Trader> GetTradersWatch()
{
var watchAccount = _statisticRepository.GetBestTraders();
var customWatchAccount = _accountService.GetAccounts(true, false).Where(a => a.Type == AccountType.Watch).ToList().MapToTraders();
watchAccount.AddRange(customWatchAccount.Where(a => !watchAccount.Any(w => w.Address.Equals(a.Address, StringComparison.InvariantCultureIgnoreCase))));
var customWatchAccount = _accountService.GetAccounts(true, false).Where(a => a.Type == AccountType.Watch)
.ToList().MapToTraders();
watchAccount.AddRange(customWatchAccount.Where(a =>
!watchAccount.Any(w => w.Address.Equals(a.Address, StringComparison.InvariantCultureIgnoreCase))));
return watchAccount;
}
@@ -279,14 +282,16 @@ public class TradingService : ITradingService
{
if (oldTrade != null)
{
_logger.LogInformation($"[{shortAddress}][{ticker}] Trader previously got a position open but the position was close by trader");
_logger.LogInformation(
$"[{shortAddress}][{ticker}] Trader previously got a position open but the position was close by trader");
await _messengerService.SendClosedPosition(a.Account.Address, oldTrade);
a.Trades.Remove(oldTrade);
}
}
else if ((newTrade != null && oldTrade == null) || (newTrade.Quantity > oldTrade.Quantity))
{
_logger.LogInformation($"[{shortAddress}][{ticker}] Trader increase {newTrade.Direction} by {newTrade.Quantity - (oldTrade?.Quantity ?? 0)} with leverage {newTrade.Leverage} at {newTrade.Price} leverage.");
_logger.LogInformation(
$"[{shortAddress}][{ticker}] Trader increase {newTrade.Direction} by {newTrade.Quantity - (oldTrade?.Quantity ?? 0)} with leverage {newTrade.Leverage} at {newTrade.Price} leverage.");
var index = a.Trades.IndexOf(oldTrade);
if (index != -1)
@@ -307,12 +312,14 @@ public class TradingService : ITradingService
var decreaseAmount = oldTrade.Quantity - newTrade.Quantity;
var index = a.Trades.IndexOf(oldTrade);
a.Trades[index] = newTrade;
_logger.LogInformation($"[{a.Account.Address.Substring(0, 6)}][{ticker}] Trader decrease position but didnt close it {decreaseAmount}");
_logger.LogInformation(
$"[{a.Account.Address.Substring(0, 6)}][{ticker}] Trader decrease position but didnt close it {decreaseAmount}");
await _messengerService.SendDecreasePosition(a.Account.Address, newTrade, decreaseAmount);
}
else
{
_logger.LogInformation($"[{shortAddress}][{ticker}] No change - Quantity still {newTrade.Quantity}");
_logger.LogInformation(
$"[{shortAddress}][{ticker}] No change - Quantity still {newTrade.Quantity}");
}
}
catch (Exception ex)
@@ -324,7 +331,7 @@ public class TradingService : ITradingService
private List<TraderFollowup> GetAccountsQuantityInPosition(IEnumerable<Trader> watchAccount)
{
var result = new List<TraderFollowup> ();
var result = new List<TraderFollowup>();
foreach (var account in watchAccount)
{
var trader = SetupFollowUp(account);
@@ -352,4 +359,4 @@ public class TradingService : ITradingService
public List<Trade> Trades { get; set; }
public List<string> PositionIdentifiers { get; set; }
}
}
}