Files
managing-apps/src/Managing.Application/Trading/TradingService.cs
Oda 65bdb8e34f 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
2025-01-30 23:06:22 +07:00

362 lines
13 KiB
C#

using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts;
using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Statistics;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
namespace Managing.Application.Trading;
public class TradingService : ITradingService
{
private readonly ITradingRepository _tradingRepository;
private readonly IExchangeService _exchangeService;
private readonly IAccountService _accountService;
private readonly ICacheService _cacheService;
private readonly IMessengerService _messengerService;
private readonly IStatisticRepository _statisticRepository;
private readonly ILogger<TradingService> _logger;
public TradingService(
ITradingRepository tradingRepository,
IExchangeService exchangeService,
ILogger<TradingService> logger,
IAccountService accountService,
ICacheService cacheService,
IMessengerService messengerService,
IStatisticRepository statisticRepository)
{
_tradingRepository = tradingRepository;
_exchangeService = exchangeService;
_logger = logger;
_accountService = accountService;
_cacheService = cacheService;
_messengerService = messengerService;
_statisticRepository = statisticRepository;
}
public void DeleteScenario(string name)
{
_tradingRepository.DeleteScenario(name);
}
public void DeleteScenarios()
{
_tradingRepository.DeleteScenarios();
}
public void DeleteStrategies()
{
_tradingRepository.DeleteStrategies();
}
public void DeleteStrategy(string name)
{
_tradingRepository.DeleteStrategy(name);
}
public Position GetPositionByIdentifier(string identifier)
{
return _tradingRepository.GetPositionByIdentifier(identifier);
}
public IEnumerable<Position> GetPositions(PositionInitiator positionInitiator)
{
return _tradingRepository.GetPositions(positionInitiator);
}
public IEnumerable<Position> GetPositionsByStatus(PositionStatus postionStatus)
{
return _tradingRepository.GetPositionsByStatus(postionStatus);
}
public Scenario GetScenarioByName(string scenario)
{
return _tradingRepository.GetScenarioByName(scenario);
}
public IEnumerable<Scenario> GetScenarios()
{
return _tradingRepository.GetScenarios();
}
public IEnumerable<Strategy> GetStrategies()
{
return _tradingRepository.GetStrategies();
}
public Strategy GetStrategyByName(string strategy)
{
return _tradingRepository.GetStrategyByName(strategy);
}
public void InsertPosition(Position position)
{
_tradingRepository.InsertPosition(position);
}
public void InsertScenario(Scenario scenario)
{
_tradingRepository.InsertScenario(scenario);
}
public void InsertSignal(Signal signal)
{
_tradingRepository.InsertSignal(signal);
}
public void InsertStrategy(Strategy strategy)
{
_tradingRepository.InsertStrategy(strategy);
}
public async Task<Position> ManagePosition(Account account, Position position)
{
var lastPrice = _exchangeService.GetPrice(account, position.Ticker, DateTime.UtcNow);
var quantityInPosition = await _exchangeService.GetQuantityInPosition(account, position.Ticker);
var orders = await _exchangeService.GetOpenOrders(account, position.Ticker);
if (quantityInPosition > 0)
{
// Position still open
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}");
}
else
{
// No quantity in position = SL/TP hit
if (orders.All(o => o.TradeType != TradeType.StopLoss))
{
// 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.Open.Leverage);
_ = _exchangeService.CancelOrder(account, position.Ticker);
}
else if (orders.All(o => o.TradeType != TradeType.TakeProfit))
{
// TP Hit
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, 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, 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.");
position.Status = PositionStatus.Finished;
if (orders.Any()) await _exchangeService.CancelOrder(account, position.Ticker);
}
}
return position;
}
public void UpdateFee(TradingExchanges exchange)
{
var lastFee = _tradingRepository.GetFee(exchange);
var account = _accountService.GetAccounts(false, false).FirstOrDefault(a => a.Exchange == exchange);
if (lastFee != null)
{
if (DateTime.UtcNow.AddHours(-6) >= lastFee.LastUpdate)
{
lastFee.Cost = _exchangeService.GetFee(account);
lastFee.LastUpdate = DateTime.UtcNow;
_tradingRepository.UpdateFee(lastFee);
}
}
else
{
lastFee = new Fee
{
Cost = _exchangeService.GetFee(account),
Exchange = exchange,
LastUpdate = DateTime.UtcNow
};
_tradingRepository.InsertFee(lastFee);
}
}
public decimal GetFee(Account account, bool isForPaperTrading = false)
{
if (isForPaperTrading && account.Exchange != TradingExchanges.Evm)
{
return 0.000665M;
}
return _cacheService.GetOrSave($"Fee-{account.Exchange}",
() => { return _tradingRepository.GetFee(TradingExchanges.Evm)?.Cost ?? 0m; }, TimeSpan.FromHours(2));
}
public void UpdatePosition(Position position)
{
_tradingRepository.UpdatePosition(position);
}
public IEnumerable<Position> GetPositions()
{
var positions = new List<Position>();
positions.AddRange(GetPositionsByStatus(PositionStatus.New));
positions.AddRange(GetPositionsByStatus(PositionStatus.Filled));
positions.AddRange(GetPositionsByStatus(PositionStatus.PartiallyFilled));
return positions;
}
public async Task WatchTrader()
{
var availableTickers = new List<Ticker> { Ticker.BTC, Ticker.ETH, Ticker.UNI, Ticker.LINK };
var watchAccount = GetTradersWatch();
var key = $"AccountsQuantityInPosition";
var aqip = _cacheService.GetValue<List<TraderFollowup>>(key);
if (aqip == null)
{
aqip = GetAccountsQuantityInPosition(watchAccount);
}
else
{
foreach (var a in watchAccount.Where(w => !aqip.Any(a => a.Account.Address == w.Address)))
{
var newAccount = SetupFollowUp(a);
aqip.Add(newAccount);
}
foreach (var a in aqip)
{
await ManageTrader(a, availableTickers);
}
}
_cacheService.SaveValue(key, aqip, TimeSpan.FromMinutes(10));
}
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))));
return watchAccount;
}
public void UpdateDeltaNeutralOpportunities()
{
var fundingRates = _exchangeService.GetFundingRates();
}
private async Task ManageTrader(TraderFollowup a, List<Ticker> tickers)
{
var shortAddress = a.Account.Address.Substring(0, 6);
foreach (var ticker in tickers)
{
try
{
var newTrade = await _exchangeService.GetTrade(a.Account.Address, "", ticker);
var oldTrade = a.Trades.SingleOrDefault(t => t.Ticker == ticker);
if (newTrade == null)
{
if (oldTrade != null)
{
_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.");
var index = a.Trades.IndexOf(oldTrade);
if (index != -1)
{
a.Trades[index] = newTrade;
}
else
{
a.Trades.Add(newTrade);
}
// Open position
await _messengerService.SendIncreasePosition(a.Account.Address, newTrade, "Test6", oldTrade);
// Save position to cache
}
else if (newTrade.Quantity < oldTrade.Quantity && newTrade.Quantity > 0)
{
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}");
await _messengerService.SendDecreasePosition(a.Account.Address, newTrade, decreaseAmount);
}
else
{
_logger.LogInformation(
$"[{shortAddress}][{ticker}] No change - Quantity still {newTrade.Quantity}");
}
}
catch (Exception ex)
{
_logger.LogError($"[{shortAddress}][{ticker}] Impossible to fetch trader");
}
}
}
private List<TraderFollowup> GetAccountsQuantityInPosition(IEnumerable<Trader> watchAccount)
{
var result = new List<TraderFollowup>();
foreach (var account in watchAccount)
{
var trader = SetupFollowUp(account);
result.Add(trader);
}
return result;
}
private static TraderFollowup SetupFollowUp(Trader account)
{
var trader = new TraderFollowup
{
Account = account,
Trades = new List<Trade>(),
PositionIdentifiers = new List<string>()
};
return trader;
}
public class TraderFollowup
{
public Trader Account { get; set; }
public List<Trade> Trades { get; set; }
public List<string> PositionIdentifiers { get; set; }
}
}