Add funding rate watcher (#2)
* Add FundingRate interfaces and worker * Add build on PR * Remove zip * Specify the solution path * Add build for worker too * Set up StatisticService.cs for funding rate * Add Fundingrate alerts * Send alert when big funding rate change + add SlashCommands.cs for fundingrate * Remove fixtures * Refact names * Renames
This commit is contained in:
@@ -16,7 +16,7 @@ using Serilog.Sinks.Elasticsearch;
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Configuration.SetBasePath(System.AppContext.BaseDirectory);
|
||||
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json");
|
||||
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json");
|
||||
|
||||
builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
|
||||
{
|
||||
@@ -49,12 +49,12 @@ builder.Services.AddControllers().AddJsonOptions(options =>
|
||||
builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
|
||||
{
|
||||
builder
|
||||
.SetIsOriginAllowed((host) => true)
|
||||
.AllowAnyOrigin()
|
||||
.WithOrigins("http://localhost:3000/")
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials();
|
||||
.SetIsOriginAllowed((host) => true)
|
||||
.AllowAnyOrigin()
|
||||
.WithOrigins("http://localhost:3000/")
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials();
|
||||
}));
|
||||
builder.Services.AddSignalR().AddJsonProtocol();
|
||||
|
||||
@@ -85,15 +85,18 @@ builder.Services.AddSwaggerGen(options =>
|
||||
Scheme = "Bearer",
|
||||
BearerFormat = "JWT"
|
||||
});
|
||||
options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement{
|
||||
options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new Microsoft.OpenApi.Models.OpenApiSecurityScheme{
|
||||
Reference = new Microsoft.OpenApi.Models.OpenApiReference{
|
||||
new Microsoft.OpenApi.Models.OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new Microsoft.OpenApi.Models.OpenApiReference
|
||||
{
|
||||
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
new string[]{}
|
||||
new string[] { }
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -112,6 +115,7 @@ builder.Services.AddHostedService<SpotlightWorker>();
|
||||
builder.Services.AddHostedService<TraderWatcher>();
|
||||
builder.Services.AddHostedService<LeaderboardWorker>();
|
||||
builder.Services.AddHostedService<NoobiesboardWorker>();
|
||||
builder.Services.AddHostedService<FundingRatesWatcher>();
|
||||
|
||||
// App
|
||||
var app = builder.Build();
|
||||
@@ -145,4 +149,4 @@ app.UseEndpoints(endpoints =>
|
||||
endpoints.MapHub<PositionHub>("/positionhub");
|
||||
});
|
||||
|
||||
app.Run();
|
||||
app.Run();
|
||||
28
src/Managing.Api.Workers/Workers/FundingRatesWatcher.cs
Normal file
28
src/Managing.Api.Workers/Workers/FundingRatesWatcher.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Managing.Application.Workers;
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
using Managing.Common;
|
||||
|
||||
namespace Managing.Api.Workers.Workers;
|
||||
|
||||
public class FundingRatesWatcher : BaseWorker<FundingRatesWatcher>
|
||||
{
|
||||
private readonly IStatisticService _statisticService;
|
||||
|
||||
public FundingRatesWatcher(
|
||||
ILogger<FundingRatesWatcher> logger,
|
||||
IStatisticService statisticService,
|
||||
IWorkerService workerService) : base(
|
||||
Enums.WorkerType.FundingRatesWatcher,
|
||||
logger,
|
||||
TimeSpan.FromMinutes(30),
|
||||
workerService
|
||||
)
|
||||
{
|
||||
_statisticService = statisticService;
|
||||
}
|
||||
|
||||
protected override async Task Run(CancellationToken cancellationToken)
|
||||
{
|
||||
await _statisticService.UpdateFundingRates();
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
"TradesChannelId": 998374177763491851,
|
||||
"TroublesChannelId": 1015761955321040917,
|
||||
"CopyTradingChannelId": 1132022887012909126,
|
||||
"FundingRateChannelId": 1263566138709774336,
|
||||
"RequestsChannelId": 1018589494968078356,
|
||||
"ButtonExpirationMinutes": 10
|
||||
},
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
"Authentication": {
|
||||
"Schemes": {
|
||||
"Bearer": {
|
||||
"ValidAudiences": [ "http://localhost:3000/" ],
|
||||
"ValidAudiences": [
|
||||
"http://localhost:3000/"
|
||||
],
|
||||
"ValidIssuer": "Managing"
|
||||
}
|
||||
}
|
||||
@@ -42,6 +44,7 @@
|
||||
"TroublesChannelId": 1015761955321040917,
|
||||
"CopyTradingChannelId": 1132022887012909126,
|
||||
"RequestsChannelId": 1018589494968078356,
|
||||
"FundingRateChannelId": 1263566138709774336,
|
||||
"ButtonExpirationMinutes": 10
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Evm;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -33,4 +34,5 @@ public interface IEvmManager
|
||||
Task<decimal> GetFee(string chainName);
|
||||
Task<List<Trade>> GetOrders(Account account, Ticker ticker);
|
||||
Task<Trade> GetTrade(string reference, string arbitrum, Ticker ticker);
|
||||
Task<List<FundingRate>> GetFundingRates();
|
||||
}
|
||||
|
||||
@@ -17,4 +17,8 @@ public interface IStatisticRepository
|
||||
void UpdateBadTrader(Trader trader);
|
||||
Task InsertBadTrader(Trader trader);
|
||||
Task RemoveBadTrader(Trader trader);
|
||||
}
|
||||
List<FundingRate> GetFundingRates();
|
||||
Task RemoveFundingRate(FundingRate oldRate);
|
||||
Task InsertFundingRate(FundingRate newRate);
|
||||
void UpdateFundingRate(FundingRate oldRate, FundingRate newRate);
|
||||
}
|
||||
@@ -6,7 +6,9 @@ namespace Managing.Application.Abstractions.Services;
|
||||
|
||||
public interface IDiscordService
|
||||
{
|
||||
Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe);
|
||||
Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction,
|
||||
Timeframe timeframe);
|
||||
|
||||
Task SendPosition(Position position);
|
||||
Task SendClosingPosition(Position position);
|
||||
Task SendMessage(string v);
|
||||
@@ -16,4 +18,7 @@ public interface IDiscordService
|
||||
Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount);
|
||||
Task SendBestTraders(List<Trader> traders);
|
||||
Task SendBadTraders(List<Trader> traders);
|
||||
}
|
||||
Task SendDowngradedFundingRate(FundingRate oldRate);
|
||||
Task SendNewTopFundingRate(FundingRate newRate);
|
||||
Task SendFundingRateUpdate(FundingRate oldRate, FundingRate newRate);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using static Managing.Common.Enums;
|
||||
using Managing.Domain.Accounts;
|
||||
|
||||
namespace Managing.Application.Abstractions.Services;
|
||||
|
||||
@@ -19,16 +20,22 @@ public interface IExchangeService
|
||||
bool isForPaperTrading = false,
|
||||
DateTime? currentDate = null,
|
||||
bool ioc = true);
|
||||
|
||||
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
|
||||
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
||||
decimal GetPrice(Account account, Ticker ticker, DateTime date);
|
||||
Task<Trade> GetTrade(Account account, string order, Ticker ticker);
|
||||
Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval);
|
||||
|
||||
Task<Trade> OpenStopLoss(Account account, Ticker ticker, TradeDirection originalDirection, decimal stopLossPrice,
|
||||
decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null);
|
||||
|
||||
Task<List<Ticker>> GetTickers(Account account, Timeframe timeframe);
|
||||
Task<Trade> OpenTakeProfit(Account account, Ticker ticker, TradeDirection originalDirection, decimal takeProfitPrice,
|
||||
|
||||
Task<Trade> OpenTakeProfit(Account account, Ticker ticker, TradeDirection originalDirection,
|
||||
decimal takeProfitPrice,
|
||||
decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null);
|
||||
|
||||
Task<Trade> ClosePosition(Account account, Position position, decimal lastPrice, bool isForPaperTrading = false);
|
||||
decimal GetVolume(Account account, Ticker ticker);
|
||||
Task<List<Trade>> GetTrades(Account account, Ticker ticker);
|
||||
@@ -36,10 +43,17 @@ public interface IExchangeService
|
||||
decimal GetFee(Account account, bool isForPaperTrading = false);
|
||||
Candle GetCandle(Account account, Ticker ticker, DateTime date);
|
||||
Task<decimal> GetQuantityInPosition(Account account, Ticker ticker);
|
||||
Task<List<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate, Timeframe timeframe);
|
||||
|
||||
Task<List<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate,
|
||||
Timeframe timeframe);
|
||||
|
||||
decimal GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction);
|
||||
Orderbook GetOrderbook(Account account, Ticker ticker);
|
||||
Trade BuildEmptyTrade(Ticker ticker, decimal price, decimal quantity, TradeDirection direction, decimal? leverage, TradeType tradeType, DateTime dateTime, TradeStatus tradeStatus = TradeStatus.PendingOpen);
|
||||
|
||||
Trade BuildEmptyTrade(Ticker ticker, decimal price, decimal quantity, TradeDirection direction, decimal? leverage,
|
||||
TradeType tradeType, DateTime dateTime, TradeStatus tradeStatus = TradeStatus.PendingOpen);
|
||||
|
||||
Task<List<Trade>> GetOpenOrders(Account account, Ticker ticker);
|
||||
Task<Trade> GetTrade(string reference, string orderId, Ticker ticker);
|
||||
Task<List<FundingRate>> GetFundingRates();
|
||||
}
|
||||
@@ -6,7 +6,9 @@ namespace Managing.Application.Abstractions.Services;
|
||||
|
||||
public interface IMessengerService
|
||||
{
|
||||
Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe);
|
||||
Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction,
|
||||
Timeframe timeframe);
|
||||
|
||||
Task SendPosition(Position position);
|
||||
Task SendClosingPosition(Position position);
|
||||
Task SendMessage(string v);
|
||||
@@ -16,4 +18,7 @@ public interface IMessengerService
|
||||
Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount);
|
||||
Task SendBestTraders(List<Trader> traders);
|
||||
Task SendBadTraders(List<Trader> filteredTrader);
|
||||
}
|
||||
Task SendDowngradedFundingRate(FundingRate oldRate);
|
||||
Task SendNewTopFundingRate(FundingRate newRate);
|
||||
Task SendFundingRateUpdate(FundingRate oldRate, FundingRate newRate);
|
||||
}
|
||||
@@ -8,4 +8,5 @@ public interface ITradaoService
|
||||
Task<List<Trader>> GetBadTrader();
|
||||
Task<List<Trader>> GetBestTrader();
|
||||
Task<List<Trade>> GetTrades(string address);
|
||||
}
|
||||
Task<List<FundingRate>> GetFundingRates();
|
||||
}
|
||||
@@ -32,4 +32,5 @@ public interface ITradingService
|
||||
decimal GetFee(Account account, bool isForPaperTrading = false);
|
||||
Task WatchTrader();
|
||||
IEnumerable<Trader> GetTradersWatch();
|
||||
void UpdateDeltaNeutralOpportunities();
|
||||
}
|
||||
@@ -16,4 +16,6 @@ public interface IStatisticService
|
||||
Task UpdateNoobiesboard();
|
||||
Task UpdateSpotlight();
|
||||
Task UpdateTopVolumeTicker(Enums.TradingExchanges exchange, int top);
|
||||
}
|
||||
Task UpdateFundingRates();
|
||||
Task<List<FundingRate>> GetFundingRates();
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
@@ -40,7 +39,8 @@ public abstract class BaseWorker<T> : BackgroundService where T : class
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation($"[{_workerType}] Last run : {worker.LastRunTime} - Execution Count : {worker.ExecutionCount}");
|
||||
_logger.LogInformation(
|
||||
$"[{_workerType}] Last run : {worker.LastRunTime} - Execution Count : {worker.ExecutionCount}");
|
||||
_executionCount = worker.ExecutionCount;
|
||||
}
|
||||
|
||||
@@ -49,8 +49,7 @@ public abstract class BaseWorker<T> : BackgroundService where T : class
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
worker = await _workerService.GetWorker(_workerType);
|
||||
|
||||
//if (true)
|
||||
|
||||
if (worker.IsActive)
|
||||
{
|
||||
await Run(cancellationToken);
|
||||
@@ -60,11 +59,13 @@ public abstract class BaseWorker<T> : BackgroundService where T : class
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation($"[{_workerType}] Worker not active. Next run at : {DateTime.UtcNow.Add(_delay)}");
|
||||
_logger.LogInformation(
|
||||
$"[{_workerType}] Worker not active. Next run at : {DateTime.UtcNow.Add(_delay)}");
|
||||
}
|
||||
|
||||
await Task.Delay(_delay);
|
||||
}
|
||||
|
||||
_logger.LogInformation($"[{_workerType}] Stopped");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -74,4 +75,4 @@ public abstract class BaseWorker<T> : BackgroundService where T : class
|
||||
}
|
||||
|
||||
protected abstract Task Run(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,63 @@ public class StatisticService : IStatisticService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateFundingRates()
|
||||
{
|
||||
// Get fundingRate from database
|
||||
var previousFundingRate = await GetFundingRates();
|
||||
// var fundingRates = await .GetFundingRates();
|
||||
|
||||
var newFundingRates = await _tradaoService.GetFundingRates();
|
||||
var topRates = newFundingRates
|
||||
.OrderByDescending(fr => fr.Rate)
|
||||
.Take(3)
|
||||
.ToList();
|
||||
|
||||
// Old position not in the new top
|
||||
foreach (var oldRate in previousFundingRate)
|
||||
{
|
||||
if (topRates.All(tr => !SameFundingRate(tr, oldRate)))
|
||||
{
|
||||
// Close position
|
||||
await _messengerService.SendDowngradedFundingRate(oldRate);
|
||||
await _statisticRepository.RemoveFundingRate(oldRate);
|
||||
}
|
||||
}
|
||||
|
||||
// New position not in the old top
|
||||
foreach (var newRate in topRates)
|
||||
{
|
||||
if (previousFundingRate.All(tr => !SameFundingRate(tr, newRate)))
|
||||
{
|
||||
// Open position
|
||||
await _messengerService.SendNewTopFundingRate(newRate);
|
||||
await _statisticRepository.InsertFundingRate(newRate);
|
||||
}
|
||||
else if (previousFundingRate.Any(tr => SameFundingRate(tr, newRate)))
|
||||
{
|
||||
var oldRate = previousFundingRate.FirstOrDefault(tr => SameFundingRate(tr, newRate));
|
||||
if (oldRate != null && Math.Abs(oldRate.Rate - newRate.Rate) > 1m)
|
||||
{
|
||||
await _messengerService.SendFundingRateUpdate(oldRate, newRate);
|
||||
_statisticRepository.UpdateFundingRate(oldRate, newRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool SameFundingRate(FundingRate oldRate, FundingRate newRate)
|
||||
{
|
||||
return oldRate.Ticker == newRate.Ticker &&
|
||||
oldRate.Exchange == newRate.Exchange &&
|
||||
oldRate.Direction == newRate.Direction;
|
||||
}
|
||||
|
||||
public Task<List<FundingRate>> GetFundingRates()
|
||||
{
|
||||
var previousFundingRate = _statisticRepository.GetFundingRates();
|
||||
return Task.FromResult(previousFundingRate);
|
||||
}
|
||||
|
||||
public IList<TopVolumeTicker> GetLastTopVolumeTicker()
|
||||
{
|
||||
var from = DateTime.UtcNow.AddDays(-1);
|
||||
@@ -113,16 +170,17 @@ public class StatisticService : IStatisticService
|
||||
|
||||
if (overview != null)
|
||||
{
|
||||
if(overview.Spotlights.Count < overview.ScenarioCount)
|
||||
if (overview.Spotlights.Count < overview.ScenarioCount)
|
||||
{
|
||||
_logger.LogInformation($"Spotlights not up to date. {overview.Spotlights.Count}/{overview.ScenarioCount}");
|
||||
_logger.LogInformation(
|
||||
$"Spotlights not up to date. {overview.Spotlights.Count}/{overview.ScenarioCount}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No need to update spotlights");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
overview = new SpotlightOverview
|
||||
@@ -188,14 +246,14 @@ public class StatisticService : IStatisticService
|
||||
};
|
||||
|
||||
var backtest = _backtester.RunScalpingBotBacktest(
|
||||
account,
|
||||
moneyManagement,
|
||||
ticker,
|
||||
scenario,
|
||||
timeframe,
|
||||
CandleExtensions.GetMinimalDays(timeframe),
|
||||
1000,
|
||||
isForWatchingOnly: true);
|
||||
account,
|
||||
moneyManagement,
|
||||
ticker,
|
||||
scenario,
|
||||
timeframe,
|
||||
CandleExtensions.GetMinimalDays(timeframe),
|
||||
1000,
|
||||
isForWatchingOnly: true);
|
||||
|
||||
return backtest.Signals;
|
||||
}
|
||||
@@ -301,4 +359,4 @@ public class StatisticService : IStatisticService
|
||||
|
||||
await _messengerService.SendBadTraders(lastBadTrader);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,8 @@ public class MessengerService : IMessengerService
|
||||
await _discordService.SendPosition(position);
|
||||
}
|
||||
|
||||
public async Task SendSignal(string message, Enums.TradingExchanges exchange, Enums.Ticker ticker, Enums.TradeDirection direction, Enums.Timeframe timeframe)
|
||||
public async Task SendSignal(string message, Enums.TradingExchanges exchange, Enums.Ticker ticker,
|
||||
Enums.TradeDirection direction, Enums.Timeframe timeframe)
|
||||
{
|
||||
await _discordService.SendSignal(message, exchange, ticker, direction, timeframe);
|
||||
}
|
||||
@@ -63,4 +64,19 @@ public class MessengerService : IMessengerService
|
||||
{
|
||||
await _discordService.SendBadTraders(traders);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendDowngradedFundingRate(FundingRate oldRate)
|
||||
{
|
||||
await _discordService.SendDowngradedFundingRate(oldRate);
|
||||
}
|
||||
|
||||
public async Task SendNewTopFundingRate(FundingRate newRate)
|
||||
{
|
||||
await _discordService.SendNewTopFundingRate(newRate);
|
||||
}
|
||||
|
||||
public async Task SendFundingRateUpdate(FundingRate oldRate, FundingRate newRate)
|
||||
{
|
||||
await _discordService.SendFundingRateUpdate(oldRate, newRate);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ 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 Managing.Domain.Shared.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -260,6 +260,11 @@ public class TradingService : ITradingService
|
||||
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);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
public const string Leaderboard = "leaderboard";
|
||||
public const string Noobiesboard = "noobiesboard";
|
||||
public const string LeaderboardPosition = "leaderboardposition";
|
||||
public const string FundingRates = "fundingrates";
|
||||
}
|
||||
|
||||
public class DiscordButtonAction
|
||||
@@ -36,4 +37,4 @@
|
||||
public const string Usdt = "USDT";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,54 +113,67 @@ public static class Enums
|
||||
// Summary:
|
||||
// Limit order
|
||||
Limit = 0,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Symbol order
|
||||
Market = 1,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Stop market order
|
||||
StopMarket = 2,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Stop limit order
|
||||
StopLimit = 3,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Stop loss order
|
||||
StopLoss = 4,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Take profit order
|
||||
TakeProfit = 5,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Stop loss profit order
|
||||
StopLossProfit = 6,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Stop loss profit limit order
|
||||
StopLossProfitLimit = 7,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Stop loss limit order
|
||||
StopLossLimit = 8,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Take profit limit order
|
||||
TakeProfitLimit = 9,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Trailing stop order
|
||||
TrailingStop = 10,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Trailing stop limit order
|
||||
TrailingStopLimit = 11,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Stop loss and limit order
|
||||
StopLossAndLimit = 12,
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Settle position
|
||||
@@ -201,22 +214,27 @@ public static class Enums
|
||||
/// 5m
|
||||
/// </summary>
|
||||
FiveMinutes,
|
||||
|
||||
/// <summary>
|
||||
/// 15m
|
||||
/// </summary>
|
||||
FifteenMinutes,
|
||||
|
||||
/// <summary>
|
||||
/// 30m
|
||||
/// </summary>
|
||||
ThirtyMinutes,
|
||||
|
||||
/// <summary>
|
||||
/// 1h
|
||||
/// </summary>
|
||||
OneHour,
|
||||
|
||||
/// <summary>
|
||||
/// 4h
|
||||
/// </summary>
|
||||
FourHour,
|
||||
|
||||
/// <summary>
|
||||
/// 1d
|
||||
/// </summary>
|
||||
@@ -225,25 +243,20 @@ public static class Enums
|
||||
|
||||
public enum Ticker
|
||||
{
|
||||
AAVE,
|
||||
ADA,
|
||||
APE,
|
||||
ALICE,
|
||||
ALGO,
|
||||
ARB,
|
||||
ATOM,
|
||||
AVAX,
|
||||
AXS,
|
||||
BAT,
|
||||
BNB,
|
||||
BTC,
|
||||
BAL,
|
||||
C98,
|
||||
CHR,
|
||||
CHZ,
|
||||
COMP,
|
||||
CRO,
|
||||
CRV,
|
||||
CVC,
|
||||
DEFI,
|
||||
DOGE,
|
||||
DOT,
|
||||
DYDX,
|
||||
@@ -254,29 +267,22 @@ public static class Enums
|
||||
FLM,
|
||||
FTM,
|
||||
GALA,
|
||||
GMT,
|
||||
GMX,
|
||||
GRT,
|
||||
HNT,
|
||||
IMX,
|
||||
JASMY,
|
||||
KAVA,
|
||||
KSM,
|
||||
LDO,
|
||||
LINK,
|
||||
LOOKS,
|
||||
LRC,
|
||||
LTC,
|
||||
MANA,
|
||||
MATIC,
|
||||
MKR,
|
||||
NEAR,
|
||||
NEO,
|
||||
OMG,
|
||||
ONE,
|
||||
ONT,
|
||||
OP,
|
||||
PEPE,
|
||||
QTUM,
|
||||
REEF,
|
||||
REN,
|
||||
ROSE,
|
||||
RSR,
|
||||
@@ -284,23 +290,17 @@ public static class Enums
|
||||
SAND,
|
||||
SOL,
|
||||
SRM,
|
||||
STMX,
|
||||
SUSHI,
|
||||
SXP,
|
||||
THETA,
|
||||
UNI,
|
||||
USDC,
|
||||
USDT,
|
||||
VET,
|
||||
WAVES,
|
||||
WIF,
|
||||
XMR,
|
||||
XRP,
|
||||
XTZ,
|
||||
YFI,
|
||||
ZEC,
|
||||
ZIL
|
||||
}
|
||||
|
||||
|
||||
public enum WorkerType
|
||||
{
|
||||
PriceOneMinute,
|
||||
@@ -318,7 +318,8 @@ public static class Enums
|
||||
TraderWatcher,
|
||||
LeaderboardWorker,
|
||||
Noobiesboard,
|
||||
BotManager
|
||||
BotManager,
|
||||
FundingRatesWatcher
|
||||
}
|
||||
|
||||
public enum WorkflowUsage
|
||||
@@ -341,5 +342,4 @@ public static class Enums
|
||||
Position,
|
||||
MoneyManagement
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
13
src/Managing.Domain/Statistics/FundingRate.cs
Normal file
13
src/Managing.Domain/Statistics/FundingRate.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Managing.Common;
|
||||
|
||||
namespace Managing.Domain.Statistics;
|
||||
|
||||
public class FundingRate
|
||||
{
|
||||
public Enums.Ticker Ticker { get; set; }
|
||||
public Enums.TradingExchanges Exchange { get; set; }
|
||||
public decimal Rate { get; set; }
|
||||
public decimal OpenInterest { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public Enums.TradeDirection Direction { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Managing.Common;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Attributes;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Configurations;
|
||||
|
||||
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
|
||||
|
||||
[BsonCollection("FundingRates")]
|
||||
public class FundingRateDto : Document
|
||||
{
|
||||
public Enums.Ticker Ticker { get; set; }
|
||||
public decimal Rate { get; set; }
|
||||
public Enums.TradingExchanges Exchange { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public Enums.TradeDirection Direction { get; set; }
|
||||
}
|
||||
@@ -16,7 +16,8 @@ namespace Managing.Infrastructure.Databases.MongoDb;
|
||||
|
||||
public static class MongoMappers
|
||||
{
|
||||
#region Statistics
|
||||
#region Statistics
|
||||
|
||||
internal static TopVolumeTickerDto Map(TopVolumeTicker topVolumeTicker)
|
||||
{
|
||||
return new TopVolumeTickerDto
|
||||
@@ -44,6 +45,7 @@ public static class MongoMappers
|
||||
#endregion
|
||||
|
||||
#region Accounts
|
||||
|
||||
internal static AccountDto Map(Account request)
|
||||
{
|
||||
return new AccountDto
|
||||
@@ -84,9 +86,11 @@ public static class MongoMappers
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Workers
|
||||
|
||||
internal static WorkerDto Map(Worker worker)
|
||||
{
|
||||
return new WorkerDto
|
||||
@@ -118,6 +122,7 @@ public static class MongoMappers
|
||||
#endregion
|
||||
|
||||
#region Backtests
|
||||
|
||||
internal static Backtest Map(BacktestDto b)
|
||||
{
|
||||
return new Backtest(
|
||||
@@ -165,7 +170,8 @@ public static class MongoMappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region Candles
|
||||
#region Candles
|
||||
|
||||
public static Candle Map(CandleDto candle)
|
||||
{
|
||||
if (candle == null)
|
||||
@@ -213,10 +219,10 @@ public static class MongoMappers
|
||||
return candles.ConvertAll(candle => Map(candle));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Positions
|
||||
|
||||
public static PositionDto Map(Position position)
|
||||
{
|
||||
var p = new PositionDto
|
||||
@@ -267,9 +273,13 @@ public static class MongoMappers
|
||||
|
||||
public static Position Map(PositionDto dto)
|
||||
{
|
||||
var position = new Position(dto.AccountName, originDirection: dto.OriginDirection, dto.Ticker, Map(dto.MoneyManagement), dto.Initiator, dto.Date)
|
||||
var position = new Position(dto.AccountName, originDirection: dto.OriginDirection, dto.Ticker,
|
||||
Map(dto.MoneyManagement), dto.Initiator, dto.Date)
|
||||
{
|
||||
Open = new Trade(date: dto.Open.Date, direction: dto.Open.Direction, status: dto.Open.Status, tradeType: dto.Open.TradeType, ticker: dto.Open.Ticker, quantity: dto.Open.Quantity, price: dto.Open.Price, leverage: dto.Open.Leverage, exchangeOrderId: dto.Open.ExchangeOrderId, message: dto.Open.Message),
|
||||
Open = new Trade(date: dto.Open.Date, direction: dto.Open.Direction, status: dto.Open.Status,
|
||||
tradeType: dto.Open.TradeType, ticker: dto.Open.Ticker, quantity: dto.Open.Quantity,
|
||||
price: dto.Open.Price, leverage: dto.Open.Leverage, exchangeOrderId: dto.Open.ExchangeOrderId,
|
||||
message: dto.Open.Message),
|
||||
ProfitAndLoss = new ProfitAndLoss { Realized = dto.ProfitAndLoss },
|
||||
Status = dto.Status,
|
||||
SignalIdentifier = dto.SignalIdentifier,
|
||||
@@ -278,17 +288,26 @@ public static class MongoMappers
|
||||
|
||||
if (dto.StopLoss != null)
|
||||
{
|
||||
position.StopLoss = new Trade(date: dto.StopLoss.Date, direction: dto.StopLoss.Direction, status: dto.StopLoss.Status, tradeType: dto.StopLoss.TradeType, ticker: dto.StopLoss.Ticker, quantity: dto.StopLoss.Quantity, price: dto.StopLoss.Price, leverage: dto.StopLoss.Leverage, exchangeOrderId: dto.StopLoss.ExchangeOrderId, message: dto.StopLoss.Message);
|
||||
position.StopLoss = new Trade(date: dto.StopLoss.Date, direction: dto.StopLoss.Direction,
|
||||
status: dto.StopLoss.Status, tradeType: dto.StopLoss.TradeType, ticker: dto.StopLoss.Ticker,
|
||||
quantity: dto.StopLoss.Quantity, price: dto.StopLoss.Price, leverage: dto.StopLoss.Leverage,
|
||||
exchangeOrderId: dto.StopLoss.ExchangeOrderId, message: dto.StopLoss.Message);
|
||||
}
|
||||
|
||||
if (dto.TakeProfit1 != null)
|
||||
{
|
||||
position.TakeProfit1 = new Trade(date: dto.TakeProfit1.Date, direction: dto.TakeProfit1.Direction, status: dto.TakeProfit1.Status, tradeType: dto.TakeProfit1.TradeType, ticker: dto.TakeProfit1.Ticker, quantity: dto.TakeProfit1.Quantity, price: dto.TakeProfit1.Price, leverage: dto.TakeProfit1.Leverage, exchangeOrderId: dto.TakeProfit1.ExchangeOrderId, message: dto.TakeProfit1.Message);
|
||||
position.TakeProfit1 = new Trade(date: dto.TakeProfit1.Date, direction: dto.TakeProfit1.Direction,
|
||||
status: dto.TakeProfit1.Status, tradeType: dto.TakeProfit1.TradeType, ticker: dto.TakeProfit1.Ticker,
|
||||
quantity: dto.TakeProfit1.Quantity, price: dto.TakeProfit1.Price, leverage: dto.TakeProfit1.Leverage,
|
||||
exchangeOrderId: dto.TakeProfit1.ExchangeOrderId, message: dto.TakeProfit1.Message);
|
||||
}
|
||||
|
||||
if (dto.TakeProfit2 != null)
|
||||
{
|
||||
position.TakeProfit2 = new Trade(date: dto.TakeProfit2.Date, direction: dto.TakeProfit2.Direction, status: dto.TakeProfit2.Status, tradeType: dto.TakeProfit2.TradeType, ticker: dto.TakeProfit2.Ticker, quantity: dto.TakeProfit2.Quantity, price: dto.TakeProfit2.Price, leverage: dto.TakeProfit2.Leverage, exchangeOrderId: dto.TakeProfit2.ExchangeOrderId, message: dto.TakeProfit2.Message);
|
||||
position.TakeProfit2 = new Trade(date: dto.TakeProfit2.Date, direction: dto.TakeProfit2.Direction,
|
||||
status: dto.TakeProfit2.Status, tradeType: dto.TakeProfit2.TradeType, ticker: dto.TakeProfit2.Ticker,
|
||||
quantity: dto.TakeProfit2.Quantity, price: dto.TakeProfit2.Price, leverage: dto.TakeProfit2.Leverage,
|
||||
exchangeOrderId: dto.TakeProfit2.ExchangeOrderId, message: dto.TakeProfit2.Message);
|
||||
}
|
||||
|
||||
return position;
|
||||
@@ -302,6 +321,7 @@ public static class MongoMappers
|
||||
#endregion
|
||||
|
||||
#region Signals
|
||||
|
||||
public static SignalDto Map(Signal signal)
|
||||
{
|
||||
return new SignalDto
|
||||
@@ -330,7 +350,8 @@ public static class MongoMappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scenarios
|
||||
#region Scenarios
|
||||
|
||||
public static ScenarioDto Map(Scenario scenario)
|
||||
{
|
||||
return new ScenarioDto()
|
||||
@@ -373,6 +394,7 @@ public static class MongoMappers
|
||||
CyclePeriods = strategyDto.CyclePeriods
|
||||
};
|
||||
}
|
||||
|
||||
internal static StrategyDto Map(Strategy strategy)
|
||||
{
|
||||
var dto = new StrategyDto
|
||||
@@ -430,6 +452,7 @@ public static class MongoMappers
|
||||
#endregion
|
||||
|
||||
#region Money Management
|
||||
|
||||
public static MoneyManagementDto Map(MoneyManagement request)
|
||||
{
|
||||
if (request == null) return null;
|
||||
@@ -497,16 +520,25 @@ public static class MongoMappers
|
||||
Scenario = new ScenarioDto
|
||||
{
|
||||
Name = spotlight.Scenario.Name,
|
||||
Strategies = spotlight.Scenario.Strategies.ConvertAll(spotlightScenarioStrategy => Map(spotlightScenarioStrategy))
|
||||
Strategies =
|
||||
spotlight.Scenario.Strategies.ConvertAll(
|
||||
spotlightScenarioStrategy => Map(spotlightScenarioStrategy))
|
||||
},
|
||||
TickerSignals = spotlight.TickerSignals.ConvertAll(spotlightTickerSignal => new TickerSignalDto
|
||||
{
|
||||
Ticker = spotlightTickerSignal.Ticker,
|
||||
FiveMinutes = spotlightTickerSignal.FiveMinutes?.ConvertAll(spotlightTickerSignalFiveMinute => Map(spotlightTickerSignalFiveMinute)) ?? new List<SignalDto>(),
|
||||
FifteenMinutes = spotlightTickerSignal.FifteenMinutes?.ConvertAll(spotlightTickerSignalFifteenMinute => Map(spotlightTickerSignalFifteenMinute)) ?? new List<SignalDto>(),
|
||||
OneHour = spotlightTickerSignal.OneHour?.ConvertAll(spotlightTickerSignalOneHour => Map(spotlightTickerSignalOneHour)) ?? new List<SignalDto>(),
|
||||
FourHour = spotlightTickerSignal.FourHour?.ConvertAll(spotlightTickerSignalFourHour => Map(spotlightTickerSignalFourHour)) ?? new List<SignalDto>(),
|
||||
OneDay = spotlightTickerSignal.OneDay?.ConvertAll(spotlightTickerSignalOneDay => Map(spotlightTickerSignalOneDay)) ?? new List<SignalDto>()
|
||||
FiveMinutes =
|
||||
spotlightTickerSignal.FiveMinutes?.ConvertAll(spotlightTickerSignalFiveMinute =>
|
||||
Map(spotlightTickerSignalFiveMinute)) ?? new List<SignalDto>(),
|
||||
FifteenMinutes =
|
||||
spotlightTickerSignal.FifteenMinutes?.ConvertAll(spotlightTickerSignalFifteenMinute =>
|
||||
Map(spotlightTickerSignalFifteenMinute)) ?? new List<SignalDto>(),
|
||||
OneHour = spotlightTickerSignal.OneHour?.ConvertAll(spotlightTickerSignalOneHour =>
|
||||
Map(spotlightTickerSignalOneHour)) ?? new List<SignalDto>(),
|
||||
FourHour = spotlightTickerSignal.FourHour?.ConvertAll(spotlightTickerSignalFourHour =>
|
||||
Map(spotlightTickerSignalFourHour)) ?? new List<SignalDto>(),
|
||||
OneDay = spotlightTickerSignal.OneDay?.ConvertAll(spotlightTickerSignalOneDay =>
|
||||
Map(spotlightTickerSignalOneDay)) ?? new List<SignalDto>()
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -528,16 +560,23 @@ public static class MongoMappers
|
||||
{
|
||||
Scenario = new Scenario(name: spotlight.Scenario.Name)
|
||||
{
|
||||
Strategies = spotlight.Scenario.Strategies.ConvertAll(spotlightScenarioStrategy => Map(spotlightScenarioStrategy))
|
||||
Strategies =
|
||||
spotlight.Scenario.Strategies.ConvertAll(
|
||||
spotlightScenarioStrategy => Map(spotlightScenarioStrategy))
|
||||
},
|
||||
TickerSignals = spotlight.TickerSignals.ConvertAll(spotlightTickerSignal => new TickerSignal
|
||||
{
|
||||
Ticker = spotlightTickerSignal.Ticker,
|
||||
FiveMinutes = spotlightTickerSignal.FiveMinutes.ConvertAll(spotlightTickerSignalFiveMinute => Map(spotlightTickerSignalFiveMinute)),
|
||||
FifteenMinutes = spotlightTickerSignal.FifteenMinutes.ConvertAll(spotlightTickerSignalFifteenMinute => Map(spotlightTickerSignalFifteenMinute)),
|
||||
OneHour = spotlightTickerSignal.OneHour.ConvertAll(spotlightTickerSignalOneHour => Map(spotlightTickerSignalOneHour)),
|
||||
FourHour = spotlightTickerSignal.FourHour.ConvertAll(spotlightTickerSignalFourHour => Map(spotlightTickerSignalFourHour)),
|
||||
OneDay = spotlightTickerSignal.OneDay.ConvertAll(spotlightTickerSignalOneDay => Map(spotlightTickerSignalOneDay))
|
||||
FiveMinutes = spotlightTickerSignal.FiveMinutes.ConvertAll(spotlightTickerSignalFiveMinute =>
|
||||
Map(spotlightTickerSignalFiveMinute)),
|
||||
FifteenMinutes = spotlightTickerSignal.FifteenMinutes.ConvertAll(spotlightTickerSignalFifteenMinute =>
|
||||
Map(spotlightTickerSignalFifteenMinute)),
|
||||
OneHour = spotlightTickerSignal.OneHour.ConvertAll(spotlightTickerSignalOneHour =>
|
||||
Map(spotlightTickerSignalOneHour)),
|
||||
FourHour = spotlightTickerSignal.FourHour.ConvertAll(spotlightTickerSignalFourHour =>
|
||||
Map(spotlightTickerSignalFourHour)),
|
||||
OneDay = spotlightTickerSignal.OneDay.ConvertAll(spotlightTickerSignalOneDay =>
|
||||
Map(spotlightTickerSignalOneDay))
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -675,4 +714,31 @@ public static class MongoMappers
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static FundingRate Map(FundingRateDto fundingRate)
|
||||
{
|
||||
if (fundingRate == null)
|
||||
return null;
|
||||
|
||||
return new FundingRate
|
||||
{
|
||||
Exchange = fundingRate.Exchange,
|
||||
Rate = fundingRate.Rate,
|
||||
Ticker = fundingRate.Ticker,
|
||||
Date = fundingRate.Date,
|
||||
Direction = fundingRate.Direction
|
||||
};
|
||||
}
|
||||
|
||||
public static FundingRateDto Map(FundingRate fundingRate)
|
||||
{
|
||||
return new FundingRateDto
|
||||
{
|
||||
Exchange = fundingRate.Exchange,
|
||||
Rate = fundingRate.Rate,
|
||||
Ticker = fundingRate.Ticker,
|
||||
Date = fundingRate.Date,
|
||||
Direction = fundingRate.Direction
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -8,21 +8,24 @@ namespace Managing.Infrastructure.Databases;
|
||||
|
||||
public class StatisticRepository : IStatisticRepository
|
||||
{
|
||||
private readonly IMongoRepository<BestTraderDto> _bestTrader;
|
||||
private readonly IMongoRepository<BestTraderDto> _bestTrader;
|
||||
private readonly IMongoRepository<BadTraderDto> _badTrader;
|
||||
private readonly IMongoRepository<TopVolumeTickerDto> _topRepository;
|
||||
private readonly IMongoRepository<SpotlighOverviewDto> _spotlightRepository;
|
||||
private readonly IMongoRepository<FundingRateDto> _fundingRateRepository;
|
||||
|
||||
public StatisticRepository(
|
||||
IMongoRepository<TopVolumeTickerDto> topRepository,
|
||||
IMongoRepository<SpotlighOverviewDto> spotlightRepository,
|
||||
IMongoRepository<BestTraderDto> bestTrader,
|
||||
IMongoRepository<BadTraderDto> badTrader)
|
||||
IMongoRepository<BadTraderDto> badTrader,
|
||||
IMongoRepository<FundingRateDto> fundingRateRepository)
|
||||
{
|
||||
_topRepository = topRepository;
|
||||
_spotlightRepository = spotlightRepository;
|
||||
_bestTrader = bestTrader;
|
||||
_badTrader = badTrader;
|
||||
_fundingRateRepository = fundingRateRepository;
|
||||
}
|
||||
|
||||
public async Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker)
|
||||
@@ -62,7 +65,7 @@ public class StatisticRepository : IStatisticRepository
|
||||
|
||||
public void UpdateBestTrader(Trader trader)
|
||||
{
|
||||
var t = _bestTrader.FindOne(t => t.Address == trader.Address);
|
||||
var t = _bestTrader.FindOne(t => t.Address == trader.Address);
|
||||
var dto = MongoMappers.Map(trader);
|
||||
dto.Id = t.Id;
|
||||
_bestTrader.Update(dto);
|
||||
@@ -100,4 +103,28 @@ public class StatisticRepository : IStatisticRepository
|
||||
{
|
||||
await _badTrader.DeleteOneAsync(t => t.Address == trader.Address);
|
||||
}
|
||||
}
|
||||
|
||||
public List<FundingRate> GetFundingRates()
|
||||
{
|
||||
return _fundingRateRepository.FindAll().Select(MongoMappers.Map).ToList();
|
||||
}
|
||||
|
||||
public async Task RemoveFundingRate(FundingRate oldRate)
|
||||
{
|
||||
var rate = _fundingRateRepository.FindOne(r => r.Ticker == oldRate.Ticker && r.Exchange == oldRate.Exchange);
|
||||
await _fundingRateRepository.DeleteOneAsync(r => r.Id == rate.Id);
|
||||
}
|
||||
|
||||
public async Task InsertFundingRate(FundingRate newRate)
|
||||
{
|
||||
await _fundingRateRepository.InsertOneAsync(MongoMappers.Map(newRate));
|
||||
}
|
||||
|
||||
public void UpdateFundingRate(FundingRate oldRate, FundingRate newRate)
|
||||
{
|
||||
var f = _fundingRateRepository.FindOne(t => t.Ticker == oldRate.Ticker && t.Exchange == oldRate.Exchange);
|
||||
var dto = MongoMappers.Map(newRate);
|
||||
dto.Id = f.Id;
|
||||
_fundingRateRepository.Update(dto);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -36,4 +37,5 @@ public interface IExchangeProcessor
|
||||
Orderbook GetOrderbook(Account account, Ticker ticker);
|
||||
Task<List<Trade>> GetOrders(Account account, Ticker ticker);
|
||||
Task<Trade> GetTrade(string reference, string orderId, Ticker ticker);
|
||||
Task<List<FundingRate>> GetFundingRates();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Exchanges.Abstractions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Infrastructure.Exchanges.Abstractions;
|
||||
|
||||
namespace Managing.Infrastructure.Exchanges
|
||||
{
|
||||
@@ -145,6 +146,12 @@ namespace Managing.Infrastructure.Exchanges
|
||||
return await processor.GetTrade(reference, orderId, ticker);
|
||||
}
|
||||
|
||||
public Task<List<FundingRate>> GetFundingRates()
|
||||
{
|
||||
var processor = _exchangeProcessor.First(e => e.Exchange() == Common.Enums.TradingExchanges.Evm);
|
||||
return processor.GetFundingRates();
|
||||
}
|
||||
|
||||
public async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
|
||||
{
|
||||
var processor = GetProcessor(account);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Exchanges.Abstractions;
|
||||
using static Managing.Common.Enums;
|
||||
@@ -26,5 +27,6 @@ namespace Managing.Infrastructure.Exchanges.Exchanges
|
||||
public abstract Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
||||
public abstract Task<List<Trade>> GetOrders(Account account, Ticker ticker);
|
||||
public abstract Task<Trade> GetTrade(string reference, string orderId, Ticker ticker);
|
||||
public abstract Task<List<FundingRate>> GetFundingRates();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Managing.Common;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Exchanges.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -98,6 +99,11 @@ public class BinanceProcessor : BaseProcessor
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task<List<FundingRate>> GetFundingRates()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
|
||||
{
|
||||
var binanceOrder =
|
||||
|
||||
@@ -3,6 +3,7 @@ using Managing.Common;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Evm;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
@@ -96,6 +97,11 @@ public class EvmProcessor : BaseProcessor
|
||||
return await _evmManager.GetTrade(reference, Constants.Chains.Arbitrum, ticker);
|
||||
}
|
||||
|
||||
public override async Task<List<FundingRate>> GetFundingRates()
|
||||
{
|
||||
return await _evmManager.GetFundingRates();
|
||||
}
|
||||
|
||||
public override decimal GetVolume(Account account, Ticker ticker)
|
||||
{
|
||||
var volume = _evmManager.GetVolume(SubgraphProvider.ChainlinkPrice, ticker);
|
||||
|
||||
@@ -4,6 +4,7 @@ using FTX.Net.Objects;
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Exchanges.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -194,4 +195,9 @@ public class FtxProcessor : BaseProcessor
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task<List<FundingRate>> GetFundingRates()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Kraken.Net.Objects.Options;
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Exchanges.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -92,6 +93,11 @@ public class KrakenProcessor : BaseProcessor
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task<List<FundingRate>> GetFundingRates()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
|
||||
{
|
||||
LoadClient(account);
|
||||
|
||||
@@ -19,7 +19,8 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
|
||||
return new Trade(data.CreateTime,
|
||||
(data.Side == OrderSide.Buy) ? TradeDirection.Long : TradeDirection.Short,
|
||||
(TradeStatus)data.Status, (TradeType)data.OriginalType, MiscExtensions.ParseEnum<Ticker>(data.Symbol), data.Quantity, data.AveragePrice, 1,
|
||||
(TradeStatus)data.Status, (TradeType)data.OriginalType, MiscExtensions.ParseEnum<Ticker>(data.Symbol),
|
||||
data.Quantity, data.AveragePrice, 1,
|
||||
data.ClientOrderId, "");
|
||||
}
|
||||
|
||||
@@ -68,8 +69,8 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
}
|
||||
|
||||
return new Trade(DateTime.Now, TradeDirection.None,
|
||||
TradeStatus.Cancelled, TradeType.Market, Ticker.BTC, 0, 0, 0,
|
||||
"", result.Error?.Message);
|
||||
TradeStatus.Cancelled, TradeType.Market, Ticker.BTC, 0, 0, 0,
|
||||
"", result.Error?.Message);
|
||||
}
|
||||
|
||||
public static Candle Map(IBinanceKline binanceKline, Ticker ticker, Enums.TradingExchanges exchange)
|
||||
@@ -115,16 +116,12 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
return "ATOMUSDT";
|
||||
case Ticker.AVAX:
|
||||
return "AVAXUSDT";
|
||||
case Ticker.BAT:
|
||||
return "BATUSDT";
|
||||
case Ticker.BNB:
|
||||
return "BNBUSDT";
|
||||
case Ticker.BTC:
|
||||
return "BTCUSDT";
|
||||
case Ticker.CRV:
|
||||
return "CRVUSDT";
|
||||
case Ticker.DEFI:
|
||||
return "DEFIUSDT";
|
||||
case Ticker.DOGE:
|
||||
return "DOGEUSDT";
|
||||
case Ticker.DOT:
|
||||
@@ -143,8 +140,6 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
return "GRTUSDT";
|
||||
case Ticker.IMX:
|
||||
return "IMXUSDT";
|
||||
case Ticker.KAVA:
|
||||
return "KAVAUSDT";
|
||||
case Ticker.KSM:
|
||||
return "KSMUSDT";
|
||||
case Ticker.LINK:
|
||||
@@ -159,10 +154,6 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
return "MKRUSDT";
|
||||
case Ticker.NEAR:
|
||||
return "NEARUSDT";
|
||||
case Ticker.NEO:
|
||||
return "NEOUSDT";
|
||||
case Ticker.ONT:
|
||||
return "ONTUSDT";
|
||||
case Ticker.SAND:
|
||||
return "SANDUSDT";
|
||||
case Ticker.SOL:
|
||||
@@ -175,19 +166,16 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
return "THETAUSDT";
|
||||
case Ticker.UNI:
|
||||
return "UNIUSDT";
|
||||
case Ticker.WAVES:
|
||||
return "WAVESUSDT";
|
||||
case Ticker.XMR:
|
||||
return "XMRUSDT";
|
||||
case Ticker.XRP:
|
||||
return "XRPUSDT";
|
||||
case Ticker.XTZ:
|
||||
return "XTZUSDT";
|
||||
case Ticker.ZEC:
|
||||
return "ZECUSDT";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -200,4 +188,4 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
public class BinanceFuturesPlacedOrder
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,28 +19,18 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
return "ADA-PERP";
|
||||
case Ticker.APE:
|
||||
return "APE-PERP";
|
||||
case Ticker.ALICE:
|
||||
return "ALICE-PERP";
|
||||
case Ticker.ALGO:
|
||||
return "ALGO-PERP";
|
||||
case Ticker.ATOM:
|
||||
return "ATOM-PERP";
|
||||
case Ticker.AVAX:
|
||||
return "AVAX-PERP";
|
||||
case Ticker.AXS:
|
||||
return "AXS-PERP";
|
||||
case Ticker.BAT:
|
||||
return "BAT-PERP";
|
||||
case Ticker.BNB:
|
||||
return "BNB-PERP";
|
||||
case Ticker.BTC:
|
||||
return "BTC-PERP";
|
||||
case Ticker.BAL:
|
||||
return "BAL-PERP";
|
||||
case Ticker.C98:
|
||||
return "C98-PERP";
|
||||
case Ticker.CHR:
|
||||
return "CHR-PERP";
|
||||
case Ticker.CHZ:
|
||||
return "CHZ-PERP";
|
||||
case Ticker.COMP:
|
||||
@@ -49,10 +39,6 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
return "CRO-PERP";
|
||||
case Ticker.CRV:
|
||||
return "CRV-PERP";
|
||||
case Ticker.CVC:
|
||||
return "CVC-PERP";
|
||||
case Ticker.DEFI:
|
||||
return "DEFI-PERP";
|
||||
case Ticker.DOGE:
|
||||
return "DOGE-PERP";
|
||||
case Ticker.DOT:
|
||||
@@ -73,26 +59,14 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
return "FTM-PERP";
|
||||
case Ticker.GALA:
|
||||
return "GALA-PERP";
|
||||
case Ticker.GMT:
|
||||
return "GMT-PERP";
|
||||
case Ticker.GRT:
|
||||
return "GRT-PERP";
|
||||
case Ticker.HNT:
|
||||
return "HNT-PERP";
|
||||
case Ticker.IMX:
|
||||
return "IMX-PERP";
|
||||
case Ticker.JASMY:
|
||||
return "JASMY-PERP";
|
||||
case Ticker.KAVA:
|
||||
return "KAVA-PERP";
|
||||
case Ticker.KSM:
|
||||
return "KSM-PERP";
|
||||
case Ticker.LDO:
|
||||
return "LDO-PERP";
|
||||
case Ticker.LINK:
|
||||
return "LINK-PERP";
|
||||
case Ticker.LOOKS:
|
||||
return "LOOKS-PERP";
|
||||
case Ticker.LRC:
|
||||
return "LRC-PERP";
|
||||
case Ticker.LTC:
|
||||
@@ -105,18 +79,8 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
return "MKR-PERP";
|
||||
case Ticker.NEAR:
|
||||
return "NEAR-PERP";
|
||||
case Ticker.NEO:
|
||||
return "NEO-PERP";
|
||||
case Ticker.OMG:
|
||||
return "OMG-PERP";
|
||||
case Ticker.ONE:
|
||||
return "ONE-PERP";
|
||||
case Ticker.ONT:
|
||||
return "ONT-PERP";
|
||||
case Ticker.QTUM:
|
||||
return "QTUM-PERP";
|
||||
case Ticker.REEF:
|
||||
return "REEF-PERP";
|
||||
case Ticker.REN:
|
||||
return "REN-PERP";
|
||||
case Ticker.ROSE:
|
||||
@@ -131,35 +95,22 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
return "SOL-PERP";
|
||||
case Ticker.SRM:
|
||||
return "SRM-PERP";
|
||||
case Ticker.STMX:
|
||||
return "STMX-PERP";
|
||||
case Ticker.SUSHI:
|
||||
return "SUSHI-PERP";
|
||||
case Ticker.SXP:
|
||||
return "SXP-PERP";
|
||||
case Ticker.THETA:
|
||||
return "THETA-PERP";
|
||||
case Ticker.UNI:
|
||||
return "UNI-PERP";
|
||||
case Ticker.VET:
|
||||
return "VET-PERP";
|
||||
case Ticker.WAVES:
|
||||
return "WAVES-PERP";
|
||||
case Ticker.XMR:
|
||||
return "XMR-PERP";
|
||||
case Ticker.XRP:
|
||||
return "XRP-PERP";
|
||||
case Ticker.XTZ:
|
||||
return "XTZ-PERP";
|
||||
case Ticker.YFI:
|
||||
return "YFI-PERP";
|
||||
case Ticker.ZEC:
|
||||
return "ZEC-PERP";
|
||||
case Ticker.ZIL:
|
||||
return "ZIL-PERP";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -177,7 +128,8 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
|
||||
return new Trade(data.CreateTime,
|
||||
(data.Side == OrderSide.Buy) ? TradeDirection.Long : TradeDirection.Short,
|
||||
(TradeStatus)data.Status, (TradeType)data.Type, MiscExtensions.ParseEnum<Ticker>(data.Symbol), data.Quantity, data.AverageFillPrice ?? 0, leverage,
|
||||
(TradeStatus)data.Status, (TradeType)data.Type, MiscExtensions.ParseEnum<Ticker>(data.Symbol),
|
||||
data.Quantity, data.AverageFillPrice ?? 0, leverage,
|
||||
data.ClientOrderId, "");
|
||||
}
|
||||
|
||||
@@ -194,7 +146,8 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
|
||||
return new Trade(data.CreateTime,
|
||||
(data.Side == OrderSide.Buy) ? TradeDirection.Long : TradeDirection.Short,
|
||||
(TradeStatus)data.Status, (TradeType)data.Type, MiscExtensions.ParseEnum<Ticker>(data.Symbol), data.Quantity, data.TriggerPrice ?? 0, leverage,
|
||||
(TradeStatus)data.Status, (TradeType)data.Type, MiscExtensions.ParseEnum<Ticker>(data.Symbol),
|
||||
data.Quantity, data.TriggerPrice ?? 0, leverage,
|
||||
Guid.NewGuid().ToString(), "");
|
||||
}
|
||||
|
||||
@@ -217,7 +170,8 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
return null;
|
||||
|
||||
return new Trade(data.CreateTime, TradeDirection.None,
|
||||
(TradeStatus)data.Status, (TradeType)data.Type, MiscExtensions.ParseEnum<Ticker>(data.Symbol), data.Quantity, data.AverageFillPrice ?? 0, 0,
|
||||
(TradeStatus)data.Status, (TradeType)data.Type, MiscExtensions.ParseEnum<Ticker>(data.Symbol),
|
||||
data.Quantity, data.AverageFillPrice ?? 0, 0,
|
||||
data.ClientOrderId, "");
|
||||
}
|
||||
|
||||
@@ -280,7 +234,7 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
||||
|
||||
private static List<OrderBookEntry> Map(IEnumerable<FTXOrderBookEntry> entry)
|
||||
{
|
||||
return entry.Select(ask => new OrderBookEntry() { Price = ask.Price, Quantity = ask.Quantity}).ToList();
|
||||
return entry.Select(ask => new OrderBookEntry() { Price = ask.Price, Quantity = ask.Quantity }).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,8 @@ public static class DiscordHelpers
|
||||
fields.Add(new EmbedFieldBuilder
|
||||
{
|
||||
Name = $"{GetExplorerUrl(trader.Address)}",
|
||||
Value = $"Avg Win / Avg Loss / Winrate / ROI \n {trader.AverageWin:#.##}$ / {trader.AverageLoss:#.##}$ / {trader.Winrate}% / {Convert.ToDecimal(trader.Roi) * 100:#.##}%",
|
||||
Value =
|
||||
$"Avg Win / Avg Loss / Winrate / ROI \n {trader.AverageWin:#.##}$ / {trader.AverageLoss:#.##}$ / {trader.Winrate}% / {Convert.ToDecimal(trader.Roi) * 100:#.##}%",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,6 +32,48 @@ public static class DiscordHelpers
|
||||
return embed;
|
||||
}
|
||||
|
||||
public static Embed GetFundingRateEmbed(FundingRate fundingRate, string title, FundingRate? oldRate = null)
|
||||
{
|
||||
var fields = new List<EmbedFieldBuilder>();
|
||||
|
||||
decimal ratePerYear = fundingRate.Rate; // Rate per year
|
||||
decimal ratePerDay = ratePerYear / 365; // Rate per day
|
||||
decimal ratePerMonth = ratePerYear / 12; // Rate per month
|
||||
decimal ratePerHour = ratePerDay / 24; // Rate per hour
|
||||
|
||||
if (oldRate != null)
|
||||
{
|
||||
var oldRatePerYear = oldRate.Rate; // Rate per year
|
||||
var oldRatePerDay = oldRatePerYear / 365; // Rate per day
|
||||
var oldRatePerMonth = oldRatePerYear / 12; // Rate per month
|
||||
var oldRatePerHour = oldRatePerDay / 24; // Rate per hour
|
||||
|
||||
fields.Add(new EmbedFieldBuilder
|
||||
{
|
||||
Name = $"{fundingRate.Direction} - Previous Rate",
|
||||
Value =
|
||||
$"Hour: {oldRatePerHour:#.##}% / Day: {oldRatePerDay:#.##}% / Month: {oldRatePerMonth:#.##}% / Year: {oldRatePerYear:#.##}%",
|
||||
});
|
||||
}
|
||||
|
||||
fields.Add(new EmbedFieldBuilder
|
||||
{
|
||||
Name = $"{fundingRate.Direction} - Current rate",
|
||||
Value =
|
||||
$"Hour: {ratePerHour:#.##}% / Day: {ratePerDay:#.##}% / Month: {ratePerMonth:#.##}% / Year: {ratePerYear:#.##}%",
|
||||
});
|
||||
|
||||
var embed = new EmbedBuilder
|
||||
{
|
||||
Author = new EmbedAuthorBuilder() { Name = "GMX" },
|
||||
Title = $"{title} {DateTime.UtcNow:d}",
|
||||
Color = Color.DarkGreen,
|
||||
Fields = fields,
|
||||
}.Build();
|
||||
|
||||
return embed;
|
||||
}
|
||||
|
||||
public static Embed GetEmbed(string address, string title, List<EmbedFieldBuilder> fields, Color color)
|
||||
{
|
||||
return new EmbedBuilder
|
||||
@@ -57,14 +100,16 @@ public static class DiscordHelpers
|
||||
fields.Add(new EmbedFieldBuilder
|
||||
{
|
||||
Name = $"{GetExplorerUrl(trade.ExchangeOrderId)}",
|
||||
Value = $"Side / Ticker / Open / Qty / Leverage / LiqPrice \n {trade.Direction} / {trade.Ticker} / {trade.Price:#.##}$ / {trade.Quantity:#.##}$ / x{trade.Leverage:#.##} / {Convert.ToDecimal(trade.Message):#.##}$",
|
||||
Value =
|
||||
$"Side / Ticker / Open / Qty / Leverage / LiqPrice \n {trade.Direction} / {trade.Ticker} / {trade.Price:#.##}$ / {trade.Quantity:#.##}$ / x{trade.Leverage:#.##} / {Convert.ToDecimal(trade.Message):#.##}$",
|
||||
});
|
||||
}
|
||||
|
||||
fields.Add(new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Summary",
|
||||
Value = $"Long / Short / \n {trades.Count(t => t.Direction == Common.Enums.TradeDirection.Long)} / {trades.Count(t => t.Direction == Common.Enums.TradeDirection.Short)}"
|
||||
Value =
|
||||
$"Long / Short / \n {trades.Count(t => t.Direction == Common.Enums.TradeDirection.Long)} / {trades.Count(t => t.Direction == Common.Enums.TradeDirection.Short)}"
|
||||
});
|
||||
|
||||
var embed = new EmbedBuilder
|
||||
@@ -77,4 +122,34 @@ public static class DiscordHelpers
|
||||
|
||||
return embed;
|
||||
}
|
||||
}
|
||||
|
||||
public static Embed GetFundingRatesEmbed(List<FundingRate> fundingRates, string leaderboardOpenPosition)
|
||||
{
|
||||
var fields = new List<EmbedFieldBuilder>();
|
||||
|
||||
foreach (var fundingRate in fundingRates)
|
||||
{
|
||||
decimal ratePerYear = fundingRate.Rate; // Rate per year
|
||||
decimal ratePerDay = ratePerYear / 365; // Rate per day
|
||||
decimal ratePerMonth = ratePerYear / 12; // Rate per month
|
||||
decimal ratePerHour = ratePerDay / 24; // Rate per hour
|
||||
|
||||
fields.Add(new EmbedFieldBuilder
|
||||
{
|
||||
Name = $"{fundingRate.Ticker}",
|
||||
Value =
|
||||
$"Hour: {ratePerHour:#.##}% / Day: {ratePerDay:#.##}% / Month: {ratePerMonth:#.##}% / Year: {ratePerYear:#.##}%",
|
||||
});
|
||||
}
|
||||
|
||||
var embed = new EmbedBuilder
|
||||
{
|
||||
Author = new EmbedAuthorBuilder() { Name = "GMX" },
|
||||
Title = $"Best Funding Rate {DateTime.UtcNow:d}",
|
||||
Color = Color.DarkGreen,
|
||||
Fields = fields,
|
||||
}.Build();
|
||||
|
||||
return embed;
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
}
|
||||
|
||||
#region Setup
|
||||
|
||||
// The hosted service has started
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -85,7 +86,9 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
case Constants.DiscordSlashCommand.LeaderboardPosition:
|
||||
await SlashCommands.HandleLeadboardPositionCommand(_services, command);
|
||||
break;
|
||||
|
||||
case Constants.DiscordSlashCommand.FundingRates:
|
||||
await SlashCommands.HandleFundingRateCommand(_services, command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +120,10 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
noobiesboardCommand.WithDescription("Shows the last Noobies board");
|
||||
applicationCommandProperties.Add(noobiesboardCommand.Build());
|
||||
|
||||
var fundingRatesCommand = new SlashCommandBuilder();
|
||||
fundingRatesCommand.WithName(Constants.DiscordSlashCommand.FundingRates);
|
||||
fundingRatesCommand.WithDescription("Shows the last funding rates");
|
||||
applicationCommandProperties.Add(fundingRatesCommand.Build());
|
||||
|
||||
await _client.BulkOverwriteGlobalApplicationCommandsAsync(applicationCommandProperties.ToArray());
|
||||
}
|
||||
@@ -131,7 +138,6 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
{
|
||||
List<ApplicationCommandProperties> commands = new();
|
||||
|
||||
|
||||
|
||||
return commands;
|
||||
}
|
||||
@@ -139,10 +145,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
// logging
|
||||
private async Task Log(LogMessage arg)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
_logger.LogInformation(arg.ToString());
|
||||
});
|
||||
await Task.Run(() => { _logger.LogInformation(arg.ToString()); });
|
||||
}
|
||||
|
||||
private async Task CommandExecuted(Optional<CommandInfo> command, ICommandContext context, IResult result)
|
||||
@@ -160,6 +163,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
await Log(new LogMessage(LogSeverity.Error, nameof(CommandExecuted), $"Error: {result.ErrorReason}"));
|
||||
return;
|
||||
}
|
||||
|
||||
// react to message
|
||||
await context.Message.AddReactionAsync(new Emoji("🤖")); // robot emoji
|
||||
}
|
||||
@@ -177,13 +181,16 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
_client.ButtonExecuted -= ButtonHandler;
|
||||
_client.SlashCommandExecuted -= SlashCommandHandler;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client?.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region In
|
||||
|
||||
public async Task ButtonHandler(SocketMessageComponent component)
|
||||
{
|
||||
var parameters = component.Data.CustomId.Split(new[] { _separator }, StringSplitOptions.None);
|
||||
@@ -191,7 +198,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
|
||||
if (component.User.GlobalName != "crypto_saitama")
|
||||
{
|
||||
await component.Channel.SendMessageAsync("Sorry bro, this feature is not accessible for you.. Do not hesitate to send me approx. 456 121 $ and i give you full access");
|
||||
await component.Channel.SendMessageAsync(
|
||||
"Sorry bro, this feature is not accessible for you.. Do not hesitate to send me approx. 456 121 $ and i give you full access");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -219,7 +227,10 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
|
||||
var json = MiscExtensions.Base64Decode(parameters[1]);
|
||||
var trade = JsonConvert.DeserializeObject<CopyTradeData>(json);
|
||||
await OpenPosition(component, trade.AccountName, trade.MoneyManagementName, PositionInitiator.CopyTrading, trade.Ticker, trade.Direction, Timeframe.FifteenMinutes, DateTime.Now.AddMinutes(trade.ExpirationMinute), true, trade.Leverage); ;
|
||||
await OpenPosition(component, trade.AccountName, trade.MoneyManagementName, PositionInitiator.CopyTrading,
|
||||
trade.Ticker, trade.Direction, Timeframe.FifteenMinutes,
|
||||
DateTime.Now.AddMinutes(trade.ExpirationMinute), true, trade.Leverage);
|
||||
;
|
||||
}
|
||||
|
||||
public async Task OpenPosition(SocketMessageComponent component, string[] parameters)
|
||||
@@ -234,19 +245,24 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
var moneyManagementName = parameters[5];
|
||||
var expiration = DateTime.Parse(parameters[6]);
|
||||
|
||||
await OpenPosition(component, accountName, moneyManagementName, PositionInitiator.User, ticker, direction, timeframe, expiration, false);
|
||||
await OpenPosition(component, accountName, moneyManagementName, PositionInitiator.User, ticker, direction,
|
||||
timeframe, expiration, false);
|
||||
}
|
||||
|
||||
private async Task OpenPosition(SocketMessageComponent component, string accountName, string moneyManagement, PositionInitiator initiator, Ticker ticker, TradeDirection direction, Timeframe timeframe, DateTime expiration, bool ignoreSLTP, decimal? leverage = null)
|
||||
private async Task OpenPosition(SocketMessageComponent component, string accountName, string moneyManagement,
|
||||
PositionInitiator initiator, Ticker ticker, TradeDirection direction, Timeframe timeframe,
|
||||
DateTime expiration, bool ignoreSLTP, decimal? leverage = null)
|
||||
{
|
||||
if (DateTime.Now > expiration)
|
||||
{
|
||||
await component.Channel.SendMessageAsync("Sorry I can't open position because you tried to click on a expired button.");
|
||||
await component.Channel.SendMessageAsync(
|
||||
"Sorry I can't open position because you tried to click on a expired button.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var exchangeService = (IExchangeService)_services.GetService(typeof(IExchangeService));
|
||||
var moneyManagementService = (IMoneyManagementService)_services.GetService(typeof(IMoneyManagementService));
|
||||
var moneyManagementService =
|
||||
(IMoneyManagementService)_services.GetService(typeof(IMoneyManagementService));
|
||||
var accountService = (IAccountService)_services.GetService(typeof(IAccountService));
|
||||
var tradingService = (ITradingService)_services.GetService(typeof(ITradingService));
|
||||
|
||||
@@ -261,7 +277,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
|
||||
.Handle(tradeCommand);
|
||||
|
||||
var builder = new ComponentBuilder().WithButton("Close Position", $"{Constants.DiscordButtonAction.ClosePosition}{_separator}{position.Identifier}");
|
||||
var builder = new ComponentBuilder().WithButton("Close Position",
|
||||
$"{Constants.DiscordButtonAction.ClosePosition}{_separator}{position.Identifier}");
|
||||
|
||||
await component.Channel.SendMessageAsync(MessengerHelpers.GetPositionMessage(position),
|
||||
components: builder.Build());
|
||||
@@ -272,10 +289,10 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
private string GetClosingPositionMessage(Position position)
|
||||
{
|
||||
return $"Closing : {position.OriginDirection} {position.Open.Ticker} \n" +
|
||||
$"Open Price : {position.Open.Price} \n" +
|
||||
$"Closing Price : {position.Open.Price} \n" +
|
||||
$"Quantity :{position.Open.Quantity} \n" +
|
||||
$"PNL : {position.ProfitAndLoss.Net} $";
|
||||
$"Open Price : {position.Open.Price} \n" +
|
||||
$"Closing Price : {position.Open.Price} \n" +
|
||||
$"Quantity :{position.Open.Quantity} \n" +
|
||||
$"PNL : {position.ProfitAndLoss.Net} $";
|
||||
}
|
||||
|
||||
private async Task ClosePosition(SocketMessageComponent component, string[] parameters)
|
||||
@@ -287,42 +304,45 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
await component.RespondAsync("Alright, let met few seconds to close this position");
|
||||
var position = _tradingService.GetPositionByIdentifier(parameters[1]);
|
||||
var command = new ClosePositionCommand(position);
|
||||
var result = await new ClosePositionCommandHandler(exchangeService, accountService, tradingService).Handle(command);
|
||||
var result =
|
||||
await new ClosePositionCommandHandler(exchangeService, accountService, tradingService).Handle(command);
|
||||
var fields = new List<EmbedFieldBuilder>()
|
||||
{
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Direction",
|
||||
Value = position.OriginDirection,
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Open Price",
|
||||
Value = $"{position.Open.Price:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Quantity",
|
||||
Value = $"{position.Open.Quantity:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Pnl",
|
||||
Value = $"{position.ProfitAndLoss.Net:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Direction",
|
||||
Value = position.OriginDirection,
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Open Price",
|
||||
Value = $"{position.Open.Price:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Quantity",
|
||||
Value = $"{position.Open.Quantity:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Pnl",
|
||||
Value = $"{position.ProfitAndLoss.Net:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
};
|
||||
var embed = DiscordHelpers.GetEmbed(position.AccountName, $"Position status is now {result.Status}", fields, position.ProfitAndLoss.Net > 0 ? Color.Green : Color.Red);
|
||||
var embed = DiscordHelpers.GetEmbed(position.AccountName, $"Position status is now {result.Status}", fields,
|
||||
position.ProfitAndLoss.Net > 0 ? Color.Green : Color.Red);
|
||||
await component.Channel.SendMessageAsync("", embed: embed);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Out
|
||||
|
||||
public async Task SendSignal(string message)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
||||
@@ -330,18 +350,21 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
await channel.SendMessageAsync(message, components: builder.Build());
|
||||
}
|
||||
|
||||
public async Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe)
|
||||
public async Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction,
|
||||
Timeframe timeframe)
|
||||
{
|
||||
var expirationDate = DateTime.Now.AddMinutes(_settings.ButtonExpirationMinutes).ToString("G");
|
||||
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
||||
var builder = new ComponentBuilder().WithButton("Open Position", $"{Constants.DiscordButtonAction.OpenPosition}{_separator}{exchange}{_separator}{ticker}{_separator}{direction}{_separator}{timeframe}{_separator}{expirationDate}");
|
||||
var builder = new ComponentBuilder().WithButton("Open Position",
|
||||
$"{Constants.DiscordButtonAction.OpenPosition}{_separator}{exchange}{_separator}{ticker}{_separator}{direction}{_separator}{timeframe}{_separator}{expirationDate}");
|
||||
await channel.SendMessageAsync(message, components: builder.Build());
|
||||
}
|
||||
|
||||
public async Task SendIncreasePosition(string address, Trade trade, string copyAccountName, Trade? oldTrade = null)
|
||||
public async Task SendIncreasePosition(string address, Trade trade, string copyAccountName,
|
||||
Trade? oldTrade = null)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel;
|
||||
|
||||
|
||||
|
||||
var fields = new List<EmbedFieldBuilder>()
|
||||
{
|
||||
@@ -373,7 +396,10 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
|
||||
if (oldTrade != null)
|
||||
{
|
||||
fields.Add(new EmbedFieldBuilder { Name = "Increasy by", Value = $"{(trade.Quantity - oldTrade.Quantity) / trade.Leverage:#.##} $" });
|
||||
fields.Add(new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Increasy by", Value = $"{(trade.Quantity - oldTrade.Quantity) / trade.Leverage:#.##} $"
|
||||
});
|
||||
}
|
||||
|
||||
var titlePrefix = oldTrade != null ? "Increase " : "";
|
||||
@@ -398,24 +424,29 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
|
||||
if (oldTrade == null)
|
||||
{
|
||||
builder.WithButton($"Copy with {mm.Name}", $"{Constants.DiscordButtonAction.CopyPosition}{_separator}{encodedData}");
|
||||
builder.WithButton($"Copy with {mm.Name}",
|
||||
$"{Constants.DiscordButtonAction.CopyPosition}{_separator}{encodedData}");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.WithButton($"Increase with {mm.Name}", $"{Constants.DiscordButtonAction.CopyPosition}{_separator}{encodedData}");
|
||||
builder.WithButton($"Increase with {mm.Name}",
|
||||
$"{Constants.DiscordButtonAction.CopyPosition}{_separator}{encodedData}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"{titlePrefix}{trade.Direction} {trade.Ticker}", fields, trade.Direction == TradeDirection.Long ? Color.Green : Color.Red);
|
||||
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"{titlePrefix}{trade.Direction} {trade.Ticker}", fields,
|
||||
trade.Direction == TradeDirection.Long ? Color.Green : Color.Red);
|
||||
await channel.SendMessageAsync("", components: builder.Build(), embed: embed);
|
||||
}
|
||||
|
||||
public async Task SendPosition(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe)
|
||||
public async Task SendPosition(string message, TradingExchanges exchange, Ticker ticker,
|
||||
TradeDirection direction, Timeframe timeframe)
|
||||
{
|
||||
var expirationDate = DateTime.Now.AddMinutes(_settings.ButtonExpirationMinutes).ToString("G");
|
||||
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
||||
var builder = new ComponentBuilder().WithButton("Open Position", $"{Constants.DiscordButtonAction.OpenPosition}{_separator}{exchange}{_separator}{ticker}{_separator}{direction}{_separator}{timeframe}{_separator}{expirationDate}");
|
||||
var builder = new ComponentBuilder().WithButton("Open Position",
|
||||
$"{Constants.DiscordButtonAction.OpenPosition}{_separator}{exchange}{_separator}{ticker}{_separator}{direction}{_separator}{timeframe}{_separator}{expirationDate}");
|
||||
await channel.SendMessageAsync(message, components: builder.Build());
|
||||
}
|
||||
|
||||
@@ -433,7 +464,9 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
|
||||
public async Task SendTradeMessage(string message, bool isBadBehavior = false)
|
||||
{
|
||||
var channel = _client.GetChannel(isBadBehavior ? _settings.TroublesChannelId : _settings.TradesChannelId) as IMessageChannel;
|
||||
var channel =
|
||||
_client.GetChannel(isBadBehavior ? _settings.TroublesChannelId : _settings.TradesChannelId) as
|
||||
IMessageChannel;
|
||||
await channel.SendMessageAsync(message);
|
||||
}
|
||||
|
||||
@@ -467,7 +500,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
}
|
||||
};
|
||||
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"Closed {oldTrade.Direction} {oldTrade.Ticker}", fields, oldTrade.Direction == TradeDirection.Long ? Color.DarkGreen : Color.DarkRed);
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"Closed {oldTrade.Direction} {oldTrade.Ticker}", fields,
|
||||
oldTrade.Direction == TradeDirection.Long ? Color.DarkGreen : Color.DarkRed);
|
||||
var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync("", embed: embed);
|
||||
}
|
||||
@@ -508,7 +542,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
}
|
||||
};
|
||||
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"Decrease {trade.Direction} {trade.Ticker}", fields, Color.Blue);
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"Decrease {trade.Direction} {trade.Ticker}", fields,
|
||||
Color.Blue);
|
||||
var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync("", embed: embed);
|
||||
}
|
||||
@@ -517,7 +552,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
public async Task SendPosition(Position position)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.TradesChannelId) as IMessageChannel;
|
||||
var builder = new ComponentBuilder().WithButton("Close Position", $"{Constants.DiscordButtonAction.ClosePosition}{_separator}{position.Open.ExchangeOrderId}");
|
||||
var builder = new ComponentBuilder().WithButton("Close Position",
|
||||
$"{Constants.DiscordButtonAction.ClosePosition}{_separator}{position.Open.ExchangeOrderId}");
|
||||
await channel.SendMessageAsync(MessengerHelpers.GetPositionMessage(position), components: builder.Build());
|
||||
}
|
||||
|
||||
@@ -525,7 +561,6 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.LeaderboardChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync("", embed: DiscordHelpers.GetTradersEmbed(traders, "Leaderboard"));
|
||||
|
||||
}
|
||||
|
||||
public async Task SendBadTraders(List<Trader> traders)
|
||||
@@ -534,24 +569,37 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
await channel.SendMessageAsync("", embed: DiscordHelpers.GetTradersEmbed(traders, "Noobiesboard"));
|
||||
}
|
||||
|
||||
public async Task SendDowngradedFundingRate(FundingRate fundingRate)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.FundingRateChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync("",
|
||||
embed: DiscordHelpers.GetFundingRateEmbed(fundingRate, "Funding rate new opportunity"));
|
||||
}
|
||||
|
||||
public Task SendNewTopFundingRate(FundingRate newRate)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.FundingRateChannelId) as IMessageChannel;
|
||||
return channel.SendMessageAsync("",
|
||||
embed: DiscordHelpers.GetFundingRateEmbed(newRate, "Funding rate new opportunity"));
|
||||
}
|
||||
|
||||
public Task SendFundingRateUpdate(FundingRate oldRate, FundingRate newRate)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.FundingRateChannelId) as IMessageChannel;
|
||||
return channel.SendMessageAsync("",
|
||||
embed: DiscordHelpers.GetFundingRateEmbed(newRate, "Funding rate new opportunity", oldRate));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class CopyTradeData
|
||||
{
|
||||
[JsonProperty(PropertyName = "D")]
|
||||
public TradeDirection Direction { get; set; }
|
||||
[JsonProperty(PropertyName = "T")]
|
||||
public Ticker Ticker { get; set; }
|
||||
[JsonProperty(PropertyName = "A")]
|
||||
public string AccountName { get; set; }
|
||||
[JsonProperty(PropertyName = "E")]
|
||||
public int ExpirationMinute { get; set; }
|
||||
[JsonProperty(PropertyName = "L")]
|
||||
public decimal Leverage { get; set; }
|
||||
[JsonProperty(PropertyName = "M")]
|
||||
public string MoneyManagementName { get; internal set; }
|
||||
[JsonProperty(PropertyName = "D")] public TradeDirection Direction { get; set; }
|
||||
[JsonProperty(PropertyName = "T")] public Ticker Ticker { get; set; }
|
||||
[JsonProperty(PropertyName = "A")] public string AccountName { get; set; }
|
||||
[JsonProperty(PropertyName = "E")] public int ExpirationMinute { get; set; }
|
||||
[JsonProperty(PropertyName = "L")] public decimal Leverage { get; set; }
|
||||
[JsonProperty(PropertyName = "M")] public string MoneyManagementName { get; internal set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
RequestsChannelId = config.GetValue<ulong>("Discord:RequestsChannelId");
|
||||
LeaderboardChannelId = config.GetValue<ulong>("Discord:LeaderboardChannelId");
|
||||
NoobiesboardChannelId = config.GetValue<ulong>("Discord:NoobiesboardChannelId");
|
||||
FundingRateChannelId = config.GetValue<ulong>("Discord:FundingRateChannelId");
|
||||
ButtonExpirationMinutes = config.GetValue<int>("Discord:ButtonExpirationMinutes");
|
||||
HandleUserAction = config.GetValue<bool>("Discord:HandleUserAction");
|
||||
BotActivity = config.GetValue<string>("Discord:BotActivity");
|
||||
@@ -32,6 +33,6 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
public bool BotEnabled { get; set; }
|
||||
public ulong LeaderboardChannelId { get; set; }
|
||||
public ulong NoobiesboardChannelId { get; set; }
|
||||
|
||||
public ulong FundingRateChannelId { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Discord.WebSocket;
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
using Managing.Domain.Statistics;
|
||||
|
||||
namespace Managing.Infrastructure.Messengers.Discord;
|
||||
|
||||
@@ -23,6 +24,15 @@ public static class SlashCommands
|
||||
{
|
||||
var statisticService = (IStatisticService)service.GetService(typeof(IStatisticService));
|
||||
var trades = await statisticService.GetLeadboardPositons();
|
||||
await command.FollowupAsync(embed: DiscordHelpers.GetTradesEmbed(trades, "Leaderboard Open position"), ephemeral: true);
|
||||
await command.FollowupAsync(embed: DiscordHelpers.GetTradesEmbed(trades, "Leaderboard Open position"),
|
||||
ephemeral: true);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task HandleFundingRateCommand(IServiceProvider service, SocketSlashCommand command)
|
||||
{
|
||||
var statisticService = (IStatisticService)service.GetService(typeof(IStatisticService));
|
||||
List<FundingRate> fundingRates = await statisticService.GetFundingRates();
|
||||
await command.FollowupAsync(
|
||||
embed: DiscordHelpers.GetFundingRatesEmbed(fundingRates, "Leaderboard Open position"), ephemeral: true);
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,15 @@ namespace Managing.Infrastructure.Tests;
|
||||
|
||||
public class TradaoTests
|
||||
{
|
||||
private readonly TradaoService _tradaoService;
|
||||
|
||||
public TradaoTests()
|
||||
{
|
||||
_tradaoService = new TradaoService();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void Should_return_best_trader()
|
||||
public async void Should_return_best_trader()
|
||||
{
|
||||
var service = new TradaoService();
|
||||
var details = await service.GetBestTrader();
|
||||
@@ -21,4 +28,39 @@ public class TradaoTests
|
||||
var details = (await service.GetBadTrader()).FindBadTrader();
|
||||
Assert.NotNull(details);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void Should_return_funding_rates()
|
||||
{
|
||||
var service = new TradaoService();
|
||||
var details = await service.GetFundingRates();
|
||||
Assert.NotNull(details);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApy_WithPositiveRate_ReturnsExpectedPositiveApy()
|
||||
{
|
||||
var result = _tradaoService.GetApy("0.0055");
|
||||
Assert.True(result > 48m);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApy_WithNegativeRate_ReturnsPositiveApy()
|
||||
{
|
||||
var result = _tradaoService.GetApy("-0.00834530");
|
||||
Assert.True(result < -73m);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApy_WithZeroRate_ReturnsZeroApy()
|
||||
{
|
||||
var result = _tradaoService.GetApy("0");
|
||||
Assert.Equal(0m, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApy_WithInvalidFormat_ThrowsFormatException()
|
||||
{
|
||||
Assert.Throws<FormatException>(() => _tradaoService.GetApy("invalid"));
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,26 @@
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using System.Net.Http.Json;
|
||||
using System.Numerics;
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Common;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Evm;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Evm.Abstractions;
|
||||
using Managing.Infrastructure.Evm.Models.Gmx;
|
||||
using Managing.Infrastructure.Evm.Referentials;
|
||||
using Managing.Infrastructure.Evm.Services;
|
||||
using Managing.Infrastructure.Evm.Services.Gmx;
|
||||
using NBitcoin;
|
||||
using Nethereum.Contracts;
|
||||
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
|
||||
using Nethereum.HdWallet;
|
||||
using Nethereum.Hex.HexTypes;
|
||||
using Nethereum.Signer;
|
||||
using Nethereum.Web3;
|
||||
using Nethereum.HdWallet;
|
||||
using System.Numerics;
|
||||
using System.Net.Http.Json;
|
||||
using Managing.Infrastructure.Evm.Services;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Infrastructure.Evm.Abstractions;
|
||||
using Managing.Core;
|
||||
using static Managing.Common.Enums;
|
||||
using Managing.Infrastructure.Evm.Services.Gmx;
|
||||
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Infrastructure.Evm.Models.Gmx;
|
||||
using Managing.Infrastructure.Evm.Referentials;
|
||||
|
||||
namespace Managing.Infrastructure.Evm;
|
||||
|
||||
@@ -590,6 +591,15 @@ public class EvmManager : IEvmManager
|
||||
return await GmxService.GetTrade(web3, reference, ticker);
|
||||
}
|
||||
|
||||
public Task<List<FundingRate>> GetFundingRates()
|
||||
{
|
||||
// Call GMX v2
|
||||
// Call hyperliquid
|
||||
|
||||
// Map the results
|
||||
return Task.FromResult(new List<FundingRate>());
|
||||
}
|
||||
|
||||
public async Task<decimal> QuantityInPosition(string chainName, string publicAddress, Ticker ticker)
|
||||
{
|
||||
var chain = ChainService.GetChain(chainName);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using System.Net.Http.Json;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Common;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Evm.Models;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Services;
|
||||
|
||||
@@ -10,14 +12,32 @@ public class TradaoService : ITradaoService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
private readonly HashSet<Enums.Ticker> _deltaNeutralTickers = new()
|
||||
{
|
||||
Enums.Ticker.BTC,
|
||||
Enums.Ticker.ARB,
|
||||
Enums.Ticker.ETH,
|
||||
Enums.Ticker.AVAX,
|
||||
Enums.Ticker.BNB,
|
||||
Enums.Ticker.SOL,
|
||||
Enums.Ticker.LINK,
|
||||
Enums.Ticker.OP,
|
||||
Enums.Ticker.UNI,
|
||||
Enums.Ticker.AAVE,
|
||||
Enums.Ticker.PEPE,
|
||||
Enums.Ticker.WIF,
|
||||
};
|
||||
|
||||
public TradaoService()
|
||||
{
|
||||
_httpClient = new HttpClient(); ;
|
||||
_httpClient = new HttpClient();
|
||||
;
|
||||
}
|
||||
|
||||
public async Task<List<Trader>> GetBadTrader()
|
||||
{
|
||||
var bestTraders = await _httpClient.GetFromJsonAsync<TradaoList>($"https://api.tradao.xyz/v1/td/dashboard/42161/gmx/pnlTop/500/asc/2592000/0/?current=1&limit=500&order=asc&window=2592000&chain=42161&exchange=gmx&openPosition=0");
|
||||
var bestTraders = await _httpClient.GetFromJsonAsync<TradaoList>(
|
||||
$"https://api.tradao.xyz/v1/td/dashboard/42161/gmx/pnlTop/500/asc/2592000/0/?current=1&limit=500&order=asc&window=2592000&chain=42161&exchange=gmx&openPosition=0");
|
||||
|
||||
if (bestTraders == null || bestTraders.row.Count == 0)
|
||||
{
|
||||
@@ -30,7 +50,8 @@ public class TradaoService : ITradaoService
|
||||
|
||||
public async Task<List<Trader>> GetBestTrader()
|
||||
{
|
||||
var bestTraders = await _httpClient.GetFromJsonAsync<TradaoList>($"https://api.tradao.xyz/v1/td/dashboard/42161/gmx/pnlTop/500/desc/2592000/0/?current=1&limit=500&order=desc&window=2592000&chain=42161&exchange=gmx&openPosition=0");
|
||||
var bestTraders = await _httpClient.GetFromJsonAsync<TradaoList>(
|
||||
$"https://api.tradao.xyz/v1/td/dashboard/42161/gmx/pnlTop/500/desc/2592000/0/?current=1&limit=500&order=desc&window=2592000&chain=42161&exchange=gmx&openPosition=0");
|
||||
|
||||
if (bestTraders == null || bestTraders.row.Count == 0)
|
||||
{
|
||||
@@ -42,7 +63,9 @@ public class TradaoService : ITradaoService
|
||||
|
||||
public async Task<List<Trade>> GetTrades(string address)
|
||||
{
|
||||
var response = await _httpClient.GetFromJsonAsync<TradaoUserDetails>($"https://api.tradao.xyz/v1/td/trader/42161/gmx/insights/{address}");
|
||||
var response =
|
||||
await _httpClient.GetFromJsonAsync<TradaoUserDetails>(
|
||||
$"https://api.tradao.xyz/v1/td/trader/42161/gmx/insights/{address}");
|
||||
|
||||
var trades = new List<Trade>();
|
||||
|
||||
@@ -60,7 +83,7 @@ public class TradaoService : ITradaoService
|
||||
Convert.ToDecimal(position.averagePrice),
|
||||
Convert.ToDecimal(position.position) / Convert.ToDecimal(position.collateral),
|
||||
address, position.liqPrice
|
||||
);
|
||||
);
|
||||
|
||||
trades.Add(trade);
|
||||
}
|
||||
@@ -68,12 +91,15 @@ public class TradaoService : ITradaoService
|
||||
return trades;
|
||||
}
|
||||
|
||||
|
||||
private async Task<List<Trader>> GetTraderDetails(TradaoList traders)
|
||||
{
|
||||
var result = new List<Trader>();
|
||||
foreach (var trader in traders.row)
|
||||
{
|
||||
var response = await _httpClient.GetFromJsonAsync<TradaoUserDetails>($"https://api.tradao.xyz/v1/td/trader/42161/gmx/insights/{trader.user}");
|
||||
var response =
|
||||
await _httpClient.GetFromJsonAsync<TradaoUserDetails>(
|
||||
$"https://api.tradao.xyz/v1/td/trader/42161/gmx/insights/{trader.user}");
|
||||
|
||||
if (response != null)
|
||||
result.Add(Map(response, trader.user));
|
||||
@@ -95,4 +121,94 @@ public class TradaoService : ITradaoService
|
||||
Roi = Convert.ToDecimal(response.summary.roi)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<FundingRate>> GetFundingRates()
|
||||
{
|
||||
_httpClient.DefaultRequestHeaders.Accept.Clear();
|
||||
_httpClient.DefaultRequestHeaders.Accept.Add(
|
||||
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
var response =
|
||||
await _httpClient.GetFromJsonAsync<List<TradaoFundingRate>>("https://api.tradao.xyz/v1/td/fr/all");
|
||||
|
||||
return Map(response);
|
||||
}
|
||||
|
||||
private List<FundingRate> Map(List<TradaoFundingRate>? response)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
return new List<FundingRate>();
|
||||
}
|
||||
|
||||
var result = new List<FundingRate>();
|
||||
|
||||
foreach (var fundingRate in response)
|
||||
{
|
||||
var ticker = Map(fundingRate.Symbol);
|
||||
|
||||
if (ticker == null || fundingRate.ExchangeName != "gmxv2")
|
||||
continue;
|
||||
|
||||
if (!_deltaNeutralTickers.Contains(ticker.Value))
|
||||
continue;
|
||||
|
||||
var longRate = GetApy(fundingRate.LongFundingRate);
|
||||
var shortRate = GetApy(fundingRate.ShortFundingRate);
|
||||
result.Add(new FundingRate
|
||||
{
|
||||
Ticker = ticker.Value,
|
||||
Rate = longRate,
|
||||
Direction = Enums.TradeDirection.Long,
|
||||
Date = DateTimeOffset.FromUnixTimeSeconds(fundingRate.Timestamp).DateTime
|
||||
});
|
||||
|
||||
result.Add(new FundingRate
|
||||
{
|
||||
Ticker = ticker.Value,
|
||||
Rate = shortRate,
|
||||
Direction = Enums.TradeDirection.Short,
|
||||
Date = DateTimeOffset.FromUnixTimeSeconds(fundingRate.Timestamp).DateTime
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Enums.Ticker? Map(string fundingRateSymbol)
|
||||
{
|
||||
try
|
||||
{
|
||||
return MiscExtensions.ParseEnum<Enums.Ticker>(fundingRateSymbol);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public decimal GetApy(string fundingRate)
|
||||
{
|
||||
try
|
||||
{
|
||||
decimal rate = Convert.ToDecimal(fundingRate, System.Globalization.CultureInfo.InvariantCulture);
|
||||
return rate * 100 * 24 * 365;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// Handle the case where conversion fails due to an incorrect format
|
||||
// Log the error, return 0, or handle as appropriate for your application
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class TradaoFundingRate
|
||||
{
|
||||
public string Symbol { get; set; }
|
||||
public string ChainId { get; set; }
|
||||
public string ExchangeName { get; set; }
|
||||
public string LongFundingRate { get; set; }
|
||||
public string ShortFundingRate { get; set; }
|
||||
public long Timestamp { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user