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:
32
.github/workflows/caprover.yml
vendored
32
.github/workflows/caprover.yml
vendored
@@ -17,19 +17,19 @@ jobs:
|
|||||||
# npm install
|
# npm install
|
||||||
# npm run build
|
# npm run build
|
||||||
# npm run test
|
# npm run test
|
||||||
- name: Create deploy.tar
|
# - name: Create deploy.tar
|
||||||
uses: a7ul/tar-action@v1.1.0
|
# uses: a7ul/tar-action@v1.1.0
|
||||||
with:
|
# with:
|
||||||
command: c
|
# command: c
|
||||||
cwd: "./"
|
# cwd: "./"
|
||||||
files: |
|
# files: |
|
||||||
scripts/build_and_run.sh
|
# scripts/build_and_run.sh
|
||||||
captain-definition
|
# captain-definition
|
||||||
outPath: deploy.tar
|
# outPath: deploy.tar
|
||||||
- name: Deploy App to CapRover
|
# - name: Deploy App to CapRover
|
||||||
uses: caprover/deploy-from-github@v1.0.1
|
# uses: caprover/deploy-from-github@v1.0.1
|
||||||
with:
|
# with:
|
||||||
server: '${{ secrets.CAPROVER_SERVER }}'
|
# server: '${{ secrets.CAPROVER_SERVER }}'
|
||||||
app: '${{ secrets.APP_NAME }}'
|
# app: '${{ secrets.APP_NAME }}'
|
||||||
token: '${{ secrets.MANAGING_APPS }}'
|
# token: '${{ secrets.MANAGING_APPS }}'
|
||||||
|
#
|
||||||
|
|||||||
30
.github/workflows/dotnet.yml
vendored
Normal file
30
.github/workflows/dotnet.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# This workflow will build a .NET project
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||||
|
|
||||||
|
name: .NET
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "dev" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "dev" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: 8.0.x
|
||||||
|
- name: Restore API dependencies
|
||||||
|
run: dotnet restore ./src/Managing.Api/Managing.Api.csproj
|
||||||
|
- name: Build API
|
||||||
|
run: dotnet build --no-restore ./src/Managing.Api/Managing.Api.csproj
|
||||||
|
- name: Restore Worker dependencies
|
||||||
|
run: dotnet restore ./src/Managing.Api.Workers/Managing.Api.Workers.csproj
|
||||||
|
- name: Build Worker
|
||||||
|
run: dotnet build --no-restore ./src/Managing.Api.Workers/Managing.Api.Workers.csproj
|
||||||
29
assets/documentation/DeltaNeutralWorker.md
Normal file
29
assets/documentation/DeltaNeutralWorker.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant User
|
||||||
|
participant DeltaNeutralWatcher as Delta Neutral Watcher
|
||||||
|
participant GMX as GMX Exchange
|
||||||
|
participant Hyperliquid as Hyperliquid Exchange
|
||||||
|
participant Database
|
||||||
|
participant TradeBot as Trade Bot
|
||||||
|
|
||||||
|
User->>DeltaNeutralWatcher: Start Bot
|
||||||
|
|
||||||
|
loop Watcher
|
||||||
|
DeltaNeutralWatcher->>GMX: Request Market Data
|
||||||
|
DeltaNeutralWatcher->>Hyperliquid: Request Market Data
|
||||||
|
GMX-->>DeltaNeutralWatcher: Provide Market Data
|
||||||
|
Hyperliquid-->>DeltaNeutralWatcher: Provide Market Data
|
||||||
|
DeltaNeutralWatcher->>DeltaNeutralWatcher: Check Delta Neutral Opportunities
|
||||||
|
DeltaNeutralWatcher->>Database: Save Opportunities
|
||||||
|
end
|
||||||
|
|
||||||
|
loop Bot
|
||||||
|
TradeBot->>Database: Fetch Opportunities
|
||||||
|
TradeBot->>GMX: Send Orders for Long Position
|
||||||
|
TradeBot->>Hyperliquid: Send Orders for Short Position
|
||||||
|
GMX-->>TradeBot: Execution Report
|
||||||
|
Hyperliquid-->>TradeBot: Execution Report
|
||||||
|
TradeBot->>Database: Fetch for Better Opportunities
|
||||||
|
end
|
||||||
|
```
|
||||||
@@ -85,10 +85,13 @@ builder.Services.AddSwaggerGen(options =>
|
|||||||
Scheme = "Bearer",
|
Scheme = "Bearer",
|
||||||
BearerFormat = "JWT"
|
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,
|
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
|
||||||
Id = "Bearer"
|
Id = "Bearer"
|
||||||
}
|
}
|
||||||
@@ -112,6 +115,7 @@ builder.Services.AddHostedService<SpotlightWorker>();
|
|||||||
builder.Services.AddHostedService<TraderWatcher>();
|
builder.Services.AddHostedService<TraderWatcher>();
|
||||||
builder.Services.AddHostedService<LeaderboardWorker>();
|
builder.Services.AddHostedService<LeaderboardWorker>();
|
||||||
builder.Services.AddHostedService<NoobiesboardWorker>();
|
builder.Services.AddHostedService<NoobiesboardWorker>();
|
||||||
|
builder.Services.AddHostedService<FundingRatesWatcher>();
|
||||||
|
|
||||||
// App
|
// App
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|||||||
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,
|
"TradesChannelId": 998374177763491851,
|
||||||
"TroublesChannelId": 1015761955321040917,
|
"TroublesChannelId": 1015761955321040917,
|
||||||
"CopyTradingChannelId": 1132022887012909126,
|
"CopyTradingChannelId": 1132022887012909126,
|
||||||
|
"FundingRateChannelId": 1263566138709774336,
|
||||||
"RequestsChannelId": 1018589494968078356,
|
"RequestsChannelId": 1018589494968078356,
|
||||||
"ButtonExpirationMinutes": 10
|
"ButtonExpirationMinutes": 10
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
"Authentication": {
|
"Authentication": {
|
||||||
"Schemes": {
|
"Schemes": {
|
||||||
"Bearer": {
|
"Bearer": {
|
||||||
"ValidAudiences": [ "http://localhost:3000/" ],
|
"ValidAudiences": [
|
||||||
|
"http://localhost:3000/"
|
||||||
|
],
|
||||||
"ValidIssuer": "Managing"
|
"ValidIssuer": "Managing"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,6 +44,7 @@
|
|||||||
"TroublesChannelId": 1015761955321040917,
|
"TroublesChannelId": 1015761955321040917,
|
||||||
"CopyTradingChannelId": 1132022887012909126,
|
"CopyTradingChannelId": 1132022887012909126,
|
||||||
"RequestsChannelId": 1018589494968078356,
|
"RequestsChannelId": 1018589494968078356,
|
||||||
|
"FundingRateChannelId": 1263566138709774336,
|
||||||
"ButtonExpirationMinutes": 10
|
"ButtonExpirationMinutes": 10
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.Evm;
|
using Managing.Domain.Evm;
|
||||||
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -33,4 +34,5 @@ public interface IEvmManager
|
|||||||
Task<decimal> GetFee(string chainName);
|
Task<decimal> GetFee(string chainName);
|
||||||
Task<List<Trade>> GetOrders(Account account, Ticker ticker);
|
Task<List<Trade>> GetOrders(Account account, Ticker ticker);
|
||||||
Task<Trade> GetTrade(string reference, string arbitrum, 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);
|
void UpdateBadTrader(Trader trader);
|
||||||
Task InsertBadTrader(Trader trader);
|
Task InsertBadTrader(Trader trader);
|
||||||
Task RemoveBadTrader(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
|
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 SendPosition(Position position);
|
||||||
Task SendClosingPosition(Position position);
|
Task SendClosingPosition(Position position);
|
||||||
Task SendMessage(string v);
|
Task SendMessage(string v);
|
||||||
@@ -16,4 +18,7 @@ public interface IDiscordService
|
|||||||
Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount);
|
Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount);
|
||||||
Task SendBestTraders(List<Trader> traders);
|
Task SendBestTraders(List<Trader> traders);
|
||||||
Task SendBadTraders(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.Candles;
|
||||||
|
using Managing.Domain.Statistics;
|
||||||
|
using Managing.Domain.Trades;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
using Managing.Domain.Accounts;
|
|
||||||
|
|
||||||
namespace Managing.Application.Abstractions.Services;
|
namespace Managing.Application.Abstractions.Services;
|
||||||
|
|
||||||
@@ -19,16 +20,22 @@ public interface IExchangeService
|
|||||||
bool isForPaperTrading = false,
|
bool isForPaperTrading = false,
|
||||||
DateTime? currentDate = null,
|
DateTime? currentDate = null,
|
||||||
bool ioc = true);
|
bool ioc = true);
|
||||||
|
|
||||||
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
|
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
|
||||||
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
||||||
decimal GetPrice(Account account, Ticker ticker, DateTime date);
|
decimal GetPrice(Account account, Ticker ticker, DateTime date);
|
||||||
Task<Trade> GetTrade(Account account, string order, Ticker ticker);
|
Task<Trade> GetTrade(Account account, string order, Ticker ticker);
|
||||||
Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval);
|
Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval);
|
||||||
|
|
||||||
Task<Trade> OpenStopLoss(Account account, Ticker ticker, TradeDirection originalDirection, decimal stopLossPrice,
|
Task<Trade> OpenStopLoss(Account account, Ticker ticker, TradeDirection originalDirection, decimal stopLossPrice,
|
||||||
decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null);
|
decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null);
|
||||||
|
|
||||||
Task<List<Ticker>> GetTickers(Account account, Timeframe timeframe);
|
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);
|
decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null);
|
||||||
|
|
||||||
Task<Trade> ClosePosition(Account account, Position position, decimal lastPrice, bool isForPaperTrading = false);
|
Task<Trade> ClosePosition(Account account, Position position, decimal lastPrice, bool isForPaperTrading = false);
|
||||||
decimal GetVolume(Account account, Ticker ticker);
|
decimal GetVolume(Account account, Ticker ticker);
|
||||||
Task<List<Trade>> GetTrades(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);
|
decimal GetFee(Account account, bool isForPaperTrading = false);
|
||||||
Candle GetCandle(Account account, Ticker ticker, DateTime date);
|
Candle GetCandle(Account account, Ticker ticker, DateTime date);
|
||||||
Task<decimal> GetQuantityInPosition(Account account, Ticker ticker);
|
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);
|
decimal GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction);
|
||||||
Orderbook GetOrderbook(Account account, Ticker ticker);
|
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<List<Trade>> GetOpenOrders(Account account, Ticker ticker);
|
||||||
Task<Trade> GetTrade(string reference, string orderId, 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
|
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 SendPosition(Position position);
|
||||||
Task SendClosingPosition(Position position);
|
Task SendClosingPosition(Position position);
|
||||||
Task SendMessage(string v);
|
Task SendMessage(string v);
|
||||||
@@ -16,4 +18,7 @@ public interface IMessengerService
|
|||||||
Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount);
|
Task SendDecreasePosition(string address, Trade newTrade, decimal decreaseAmount);
|
||||||
Task SendBestTraders(List<Trader> traders);
|
Task SendBestTraders(List<Trader> traders);
|
||||||
Task SendBadTraders(List<Trader> filteredTrader);
|
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>> GetBadTrader();
|
||||||
Task<List<Trader>> GetBestTrader();
|
Task<List<Trader>> GetBestTrader();
|
||||||
Task<List<Trade>> GetTrades(string address);
|
Task<List<Trade>> GetTrades(string address);
|
||||||
|
Task<List<FundingRate>> GetFundingRates();
|
||||||
}
|
}
|
||||||
@@ -32,4 +32,5 @@ public interface ITradingService
|
|||||||
decimal GetFee(Account account, bool isForPaperTrading = false);
|
decimal GetFee(Account account, bool isForPaperTrading = false);
|
||||||
Task WatchTrader();
|
Task WatchTrader();
|
||||||
IEnumerable<Trader> GetTradersWatch();
|
IEnumerable<Trader> GetTradersWatch();
|
||||||
|
void UpdateDeltaNeutralOpportunities();
|
||||||
}
|
}
|
||||||
@@ -16,4 +16,6 @@ public interface IStatisticService
|
|||||||
Task UpdateNoobiesboard();
|
Task UpdateNoobiesboard();
|
||||||
Task UpdateSpotlight();
|
Task UpdateSpotlight();
|
||||||
Task UpdateTopVolumeTicker(Enums.TradingExchanges exchange, int top);
|
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.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -40,7 +39,8 @@ public abstract class BaseWorker<T> : BackgroundService where T : class
|
|||||||
}
|
}
|
||||||
else
|
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;
|
_executionCount = worker.ExecutionCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,6 @@ public abstract class BaseWorker<T> : BackgroundService where T : class
|
|||||||
{
|
{
|
||||||
worker = await _workerService.GetWorker(_workerType);
|
worker = await _workerService.GetWorker(_workerType);
|
||||||
|
|
||||||
//if (true)
|
|
||||||
if (worker.IsActive)
|
if (worker.IsActive)
|
||||||
{
|
{
|
||||||
await Run(cancellationToken);
|
await Run(cancellationToken);
|
||||||
@@ -60,11 +59,13 @@ public abstract class BaseWorker<T> : BackgroundService where T : class
|
|||||||
}
|
}
|
||||||
else
|
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);
|
await Task.Delay(_delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation($"[{_workerType}] Stopped");
|
_logger.LogInformation($"[{_workerType}] Stopped");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -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()
|
public IList<TopVolumeTicker> GetLastTopVolumeTicker()
|
||||||
{
|
{
|
||||||
var from = DateTime.UtcNow.AddDays(-1);
|
var from = DateTime.UtcNow.AddDays(-1);
|
||||||
@@ -115,7 +172,8 @@ public class StatisticService : IStatisticService
|
|||||||
{
|
{
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ public class MessengerService : IMessengerService
|
|||||||
await _discordService.SendPosition(position);
|
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);
|
await _discordService.SendSignal(message, exchange, ticker, direction, timeframe);
|
||||||
}
|
}
|
||||||
@@ -63,4 +64,19 @@ public class MessengerService : IMessengerService
|
|||||||
{
|
{
|
||||||
await _discordService.SendBadTraders(traders);
|
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.Application.Abstractions.Services;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
|
using Managing.Domain.Shared.Helpers;
|
||||||
using Managing.Domain.Statistics;
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Domain.Shared.Helpers;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -260,6 +260,11 @@ public class TradingService : ITradingService
|
|||||||
return watchAccount;
|
return watchAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateDeltaNeutralOpportunities()
|
||||||
|
{
|
||||||
|
var fundingRates = _exchangeService.GetFundingRates();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ManageTrader(TraderFollowup a, List<Ticker> tickers)
|
private async Task ManageTrader(TraderFollowup a, List<Ticker> tickers)
|
||||||
{
|
{
|
||||||
var shortAddress = a.Account.Address.Substring(0, 6);
|
var shortAddress = a.Account.Address.Substring(0, 6);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
public const string Leaderboard = "leaderboard";
|
public const string Leaderboard = "leaderboard";
|
||||||
public const string Noobiesboard = "noobiesboard";
|
public const string Noobiesboard = "noobiesboard";
|
||||||
public const string LeaderboardPosition = "leaderboardposition";
|
public const string LeaderboardPosition = "leaderboardposition";
|
||||||
|
public const string FundingRates = "fundingrates";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DiscordButtonAction
|
public class DiscordButtonAction
|
||||||
|
|||||||
@@ -113,54 +113,67 @@ public static class Enums
|
|||||||
// Summary:
|
// Summary:
|
||||||
// Limit order
|
// Limit order
|
||||||
Limit = 0,
|
Limit = 0,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Symbol order
|
// Symbol order
|
||||||
Market = 1,
|
Market = 1,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Stop market order
|
// Stop market order
|
||||||
StopMarket = 2,
|
StopMarket = 2,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Stop limit order
|
// Stop limit order
|
||||||
StopLimit = 3,
|
StopLimit = 3,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Stop loss order
|
// Stop loss order
|
||||||
StopLoss = 4,
|
StopLoss = 4,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Take profit order
|
// Take profit order
|
||||||
TakeProfit = 5,
|
TakeProfit = 5,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Stop loss profit order
|
// Stop loss profit order
|
||||||
StopLossProfit = 6,
|
StopLossProfit = 6,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Stop loss profit limit order
|
// Stop loss profit limit order
|
||||||
StopLossProfitLimit = 7,
|
StopLossProfitLimit = 7,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Stop loss limit order
|
// Stop loss limit order
|
||||||
StopLossLimit = 8,
|
StopLossLimit = 8,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Take profit limit order
|
// Take profit limit order
|
||||||
TakeProfitLimit = 9,
|
TakeProfitLimit = 9,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Trailing stop order
|
// Trailing stop order
|
||||||
TrailingStop = 10,
|
TrailingStop = 10,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Trailing stop limit order
|
// Trailing stop limit order
|
||||||
TrailingStopLimit = 11,
|
TrailingStopLimit = 11,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Stop loss and limit order
|
// Stop loss and limit order
|
||||||
StopLossAndLimit = 12,
|
StopLossAndLimit = 12,
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Settle position
|
// Settle position
|
||||||
@@ -201,22 +214,27 @@ public static class Enums
|
|||||||
/// 5m
|
/// 5m
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FiveMinutes,
|
FiveMinutes,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 15m
|
/// 15m
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FifteenMinutes,
|
FifteenMinutes,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 30m
|
/// 30m
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ThirtyMinutes,
|
ThirtyMinutes,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 1h
|
/// 1h
|
||||||
/// </summary>
|
/// </summary>
|
||||||
OneHour,
|
OneHour,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 4h
|
/// 4h
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FourHour,
|
FourHour,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 1d
|
/// 1d
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -225,25 +243,20 @@ public static class Enums
|
|||||||
|
|
||||||
public enum Ticker
|
public enum Ticker
|
||||||
{
|
{
|
||||||
|
AAVE,
|
||||||
ADA,
|
ADA,
|
||||||
APE,
|
APE,
|
||||||
ALICE,
|
|
||||||
ALGO,
|
ALGO,
|
||||||
|
ARB,
|
||||||
ATOM,
|
ATOM,
|
||||||
AVAX,
|
AVAX,
|
||||||
AXS,
|
|
||||||
BAT,
|
|
||||||
BNB,
|
BNB,
|
||||||
BTC,
|
BTC,
|
||||||
BAL,
|
BAL,
|
||||||
C98,
|
|
||||||
CHR,
|
|
||||||
CHZ,
|
CHZ,
|
||||||
COMP,
|
COMP,
|
||||||
CRO,
|
CRO,
|
||||||
CRV,
|
CRV,
|
||||||
CVC,
|
|
||||||
DEFI,
|
|
||||||
DOGE,
|
DOGE,
|
||||||
DOT,
|
DOT,
|
||||||
DYDX,
|
DYDX,
|
||||||
@@ -254,29 +267,22 @@ public static class Enums
|
|||||||
FLM,
|
FLM,
|
||||||
FTM,
|
FTM,
|
||||||
GALA,
|
GALA,
|
||||||
GMT,
|
|
||||||
GMX,
|
GMX,
|
||||||
GRT,
|
GRT,
|
||||||
HNT,
|
|
||||||
IMX,
|
IMX,
|
||||||
JASMY,
|
JASMY,
|
||||||
KAVA,
|
|
||||||
KSM,
|
KSM,
|
||||||
LDO,
|
LDO,
|
||||||
LINK,
|
LINK,
|
||||||
LOOKS,
|
|
||||||
LRC,
|
LRC,
|
||||||
LTC,
|
LTC,
|
||||||
MANA,
|
MANA,
|
||||||
MATIC,
|
MATIC,
|
||||||
MKR,
|
MKR,
|
||||||
NEAR,
|
NEAR,
|
||||||
NEO,
|
OP,
|
||||||
OMG,
|
PEPE,
|
||||||
ONE,
|
|
||||||
ONT,
|
|
||||||
QTUM,
|
QTUM,
|
||||||
REEF,
|
|
||||||
REN,
|
REN,
|
||||||
ROSE,
|
ROSE,
|
||||||
RSR,
|
RSR,
|
||||||
@@ -284,21 +290,15 @@ public static class Enums
|
|||||||
SAND,
|
SAND,
|
||||||
SOL,
|
SOL,
|
||||||
SRM,
|
SRM,
|
||||||
STMX,
|
|
||||||
SUSHI,
|
SUSHI,
|
||||||
SXP,
|
|
||||||
THETA,
|
THETA,
|
||||||
UNI,
|
UNI,
|
||||||
USDC,
|
USDC,
|
||||||
USDT,
|
USDT,
|
||||||
VET,
|
WIF,
|
||||||
WAVES,
|
|
||||||
XMR,
|
XMR,
|
||||||
XRP,
|
XRP,
|
||||||
XTZ,
|
XTZ,
|
||||||
YFI,
|
|
||||||
ZEC,
|
|
||||||
ZIL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WorkerType
|
public enum WorkerType
|
||||||
@@ -318,7 +318,8 @@ public static class Enums
|
|||||||
TraderWatcher,
|
TraderWatcher,
|
||||||
LeaderboardWorker,
|
LeaderboardWorker,
|
||||||
Noobiesboard,
|
Noobiesboard,
|
||||||
BotManager
|
BotManager,
|
||||||
|
FundingRatesWatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WorkflowUsage
|
public enum WorkflowUsage
|
||||||
@@ -341,5 +342,4 @@ public static class Enums
|
|||||||
Position,
|
Position,
|
||||||
MoneyManagement
|
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; }
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ namespace Managing.Infrastructure.Databases.MongoDb;
|
|||||||
public static class MongoMappers
|
public static class MongoMappers
|
||||||
{
|
{
|
||||||
#region Statistics
|
#region Statistics
|
||||||
|
|
||||||
internal static TopVolumeTickerDto Map(TopVolumeTicker topVolumeTicker)
|
internal static TopVolumeTickerDto Map(TopVolumeTicker topVolumeTicker)
|
||||||
{
|
{
|
||||||
return new TopVolumeTickerDto
|
return new TopVolumeTickerDto
|
||||||
@@ -44,6 +45,7 @@ public static class MongoMappers
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Accounts
|
#region Accounts
|
||||||
|
|
||||||
internal static AccountDto Map(Account request)
|
internal static AccountDto Map(Account request)
|
||||||
{
|
{
|
||||||
return new AccountDto
|
return new AccountDto
|
||||||
@@ -84,9 +86,11 @@ public static class MongoMappers
|
|||||||
|
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Workers
|
#region Workers
|
||||||
|
|
||||||
internal static WorkerDto Map(Worker worker)
|
internal static WorkerDto Map(Worker worker)
|
||||||
{
|
{
|
||||||
return new WorkerDto
|
return new WorkerDto
|
||||||
@@ -118,6 +122,7 @@ public static class MongoMappers
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Backtests
|
#region Backtests
|
||||||
|
|
||||||
internal static Backtest Map(BacktestDto b)
|
internal static Backtest Map(BacktestDto b)
|
||||||
{
|
{
|
||||||
return new Backtest(
|
return new Backtest(
|
||||||
@@ -166,6 +171,7 @@ public static class MongoMappers
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Candles
|
#region Candles
|
||||||
|
|
||||||
public static Candle Map(CandleDto candle)
|
public static Candle Map(CandleDto candle)
|
||||||
{
|
{
|
||||||
if (candle == null)
|
if (candle == null)
|
||||||
@@ -213,10 +219,10 @@ public static class MongoMappers
|
|||||||
return candles.ConvertAll(candle => Map(candle));
|
return candles.ConvertAll(candle => Map(candle));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Positions
|
#region Positions
|
||||||
|
|
||||||
public static PositionDto Map(Position position)
|
public static PositionDto Map(Position position)
|
||||||
{
|
{
|
||||||
var p = new PositionDto
|
var p = new PositionDto
|
||||||
@@ -267,9 +273,13 @@ public static class MongoMappers
|
|||||||
|
|
||||||
public static Position Map(PositionDto dto)
|
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 },
|
ProfitAndLoss = new ProfitAndLoss { Realized = dto.ProfitAndLoss },
|
||||||
Status = dto.Status,
|
Status = dto.Status,
|
||||||
SignalIdentifier = dto.SignalIdentifier,
|
SignalIdentifier = dto.SignalIdentifier,
|
||||||
@@ -278,17 +288,26 @@ public static class MongoMappers
|
|||||||
|
|
||||||
if (dto.StopLoss != null)
|
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)
|
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)
|
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;
|
return position;
|
||||||
@@ -302,6 +321,7 @@ public static class MongoMappers
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Signals
|
#region Signals
|
||||||
|
|
||||||
public static SignalDto Map(Signal signal)
|
public static SignalDto Map(Signal signal)
|
||||||
{
|
{
|
||||||
return new SignalDto
|
return new SignalDto
|
||||||
@@ -331,6 +351,7 @@ public static class MongoMappers
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Scenarios
|
#region Scenarios
|
||||||
|
|
||||||
public static ScenarioDto Map(Scenario scenario)
|
public static ScenarioDto Map(Scenario scenario)
|
||||||
{
|
{
|
||||||
return new ScenarioDto()
|
return new ScenarioDto()
|
||||||
@@ -373,6 +394,7 @@ public static class MongoMappers
|
|||||||
CyclePeriods = strategyDto.CyclePeriods
|
CyclePeriods = strategyDto.CyclePeriods
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static StrategyDto Map(Strategy strategy)
|
internal static StrategyDto Map(Strategy strategy)
|
||||||
{
|
{
|
||||||
var dto = new StrategyDto
|
var dto = new StrategyDto
|
||||||
@@ -430,6 +452,7 @@ public static class MongoMappers
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Money Management
|
#region Money Management
|
||||||
|
|
||||||
public static MoneyManagementDto Map(MoneyManagement request)
|
public static MoneyManagementDto Map(MoneyManagement request)
|
||||||
{
|
{
|
||||||
if (request == null) return null;
|
if (request == null) return null;
|
||||||
@@ -497,16 +520,25 @@ public static class MongoMappers
|
|||||||
Scenario = new ScenarioDto
|
Scenario = new ScenarioDto
|
||||||
{
|
{
|
||||||
Name = spotlight.Scenario.Name,
|
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
|
TickerSignals = spotlight.TickerSignals.ConvertAll(spotlightTickerSignal => new TickerSignalDto
|
||||||
{
|
{
|
||||||
Ticker = spotlightTickerSignal.Ticker,
|
Ticker = spotlightTickerSignal.Ticker,
|
||||||
FiveMinutes = spotlightTickerSignal.FiveMinutes?.ConvertAll(spotlightTickerSignalFiveMinute => Map(spotlightTickerSignalFiveMinute)) ?? new List<SignalDto>(),
|
FiveMinutes =
|
||||||
FifteenMinutes = spotlightTickerSignal.FifteenMinutes?.ConvertAll(spotlightTickerSignalFifteenMinute => Map(spotlightTickerSignalFifteenMinute)) ?? new List<SignalDto>(),
|
spotlightTickerSignal.FiveMinutes?.ConvertAll(spotlightTickerSignalFiveMinute =>
|
||||||
OneHour = spotlightTickerSignal.OneHour?.ConvertAll(spotlightTickerSignalOneHour => Map(spotlightTickerSignalOneHour)) ?? new List<SignalDto>(),
|
Map(spotlightTickerSignalFiveMinute)) ?? new List<SignalDto>(),
|
||||||
FourHour = spotlightTickerSignal.FourHour?.ConvertAll(spotlightTickerSignalFourHour => Map(spotlightTickerSignalFourHour)) ?? new List<SignalDto>(),
|
FifteenMinutes =
|
||||||
OneDay = spotlightTickerSignal.OneDay?.ConvertAll(spotlightTickerSignalOneDay => Map(spotlightTickerSignalOneDay)) ?? new List<SignalDto>()
|
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)
|
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
|
TickerSignals = spotlight.TickerSignals.ConvertAll(spotlightTickerSignal => new TickerSignal
|
||||||
{
|
{
|
||||||
Ticker = spotlightTickerSignal.Ticker,
|
Ticker = spotlightTickerSignal.Ticker,
|
||||||
FiveMinutes = spotlightTickerSignal.FiveMinutes.ConvertAll(spotlightTickerSignalFiveMinute => Map(spotlightTickerSignalFiveMinute)),
|
FiveMinutes = spotlightTickerSignal.FiveMinutes.ConvertAll(spotlightTickerSignalFiveMinute =>
|
||||||
FifteenMinutes = spotlightTickerSignal.FifteenMinutes.ConvertAll(spotlightTickerSignalFifteenMinute => Map(spotlightTickerSignalFifteenMinute)),
|
Map(spotlightTickerSignalFiveMinute)),
|
||||||
OneHour = spotlightTickerSignal.OneHour.ConvertAll(spotlightTickerSignalOneHour => Map(spotlightTickerSignalOneHour)),
|
FifteenMinutes = spotlightTickerSignal.FifteenMinutes.ConvertAll(spotlightTickerSignalFifteenMinute =>
|
||||||
FourHour = spotlightTickerSignal.FourHour.ConvertAll(spotlightTickerSignalFourHour => Map(spotlightTickerSignalFourHour)),
|
Map(spotlightTickerSignalFifteenMinute)),
|
||||||
OneDay = spotlightTickerSignal.OneDay.ConvertAll(spotlightTickerSignalOneDay => Map(spotlightTickerSignalOneDay))
|
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
|
#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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,17 +12,20 @@ public class StatisticRepository : IStatisticRepository
|
|||||||
private readonly IMongoRepository<BadTraderDto> _badTrader;
|
private readonly IMongoRepository<BadTraderDto> _badTrader;
|
||||||
private readonly IMongoRepository<TopVolumeTickerDto> _topRepository;
|
private readonly IMongoRepository<TopVolumeTickerDto> _topRepository;
|
||||||
private readonly IMongoRepository<SpotlighOverviewDto> _spotlightRepository;
|
private readonly IMongoRepository<SpotlighOverviewDto> _spotlightRepository;
|
||||||
|
private readonly IMongoRepository<FundingRateDto> _fundingRateRepository;
|
||||||
|
|
||||||
public StatisticRepository(
|
public StatisticRepository(
|
||||||
IMongoRepository<TopVolumeTickerDto> topRepository,
|
IMongoRepository<TopVolumeTickerDto> topRepository,
|
||||||
IMongoRepository<SpotlighOverviewDto> spotlightRepository,
|
IMongoRepository<SpotlighOverviewDto> spotlightRepository,
|
||||||
IMongoRepository<BestTraderDto> bestTrader,
|
IMongoRepository<BestTraderDto> bestTrader,
|
||||||
IMongoRepository<BadTraderDto> badTrader)
|
IMongoRepository<BadTraderDto> badTrader,
|
||||||
|
IMongoRepository<FundingRateDto> fundingRateRepository)
|
||||||
{
|
{
|
||||||
_topRepository = topRepository;
|
_topRepository = topRepository;
|
||||||
_spotlightRepository = spotlightRepository;
|
_spotlightRepository = spotlightRepository;
|
||||||
_bestTrader = bestTrader;
|
_bestTrader = bestTrader;
|
||||||
_badTrader = badTrader;
|
_badTrader = badTrader;
|
||||||
|
_fundingRateRepository = fundingRateRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker)
|
public async Task InsertTopVolumeTicker(TopVolumeTicker topVolumeTicker)
|
||||||
@@ -100,4 +103,28 @@ public class StatisticRepository : IStatisticRepository
|
|||||||
{
|
{
|
||||||
await _badTrader.DeleteOneAsync(t => t.Address == trader.Address);
|
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.Common;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -36,4 +37,5 @@ public interface IExchangeProcessor
|
|||||||
Orderbook GetOrderbook(Account account, Ticker ticker);
|
Orderbook GetOrderbook(Account account, Ticker ticker);
|
||||||
Task<List<Trade>> GetOrders(Account account, Ticker ticker);
|
Task<List<Trade>> GetOrders(Account account, Ticker ticker);
|
||||||
Task<Trade> GetTrade(string reference, string orderId, 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.Candles;
|
||||||
|
using Managing.Domain.Statistics;
|
||||||
|
using Managing.Domain.Trades;
|
||||||
|
using Managing.Infrastructure.Exchanges.Abstractions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
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
|
namespace Managing.Infrastructure.Exchanges
|
||||||
{
|
{
|
||||||
@@ -145,6 +146,12 @@ namespace Managing.Infrastructure.Exchanges
|
|||||||
return await processor.GetTrade(reference, orderId, ticker);
|
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)
|
public async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
|
||||||
{
|
{
|
||||||
var processor = GetProcessor(account);
|
var processor = GetProcessor(account);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Infrastructure.Exchanges.Abstractions;
|
using Managing.Infrastructure.Exchanges.Abstractions;
|
||||||
using static Managing.Common.Enums;
|
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<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
||||||
public abstract Task<List<Trade>> GetOrders(Account account, Ticker ticker);
|
public abstract Task<List<Trade>> GetOrders(Account account, Ticker ticker);
|
||||||
public abstract Task<Trade> GetTrade(string reference, string orderId, 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.Core;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Infrastructure.Exchanges.Helpers;
|
using Managing.Infrastructure.Exchanges.Helpers;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -98,6 +99,11 @@ public class BinanceProcessor : BaseProcessor
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task<List<FundingRate>> GetFundingRates()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
|
public override async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
|
||||||
{
|
{
|
||||||
var binanceOrder =
|
var binanceOrder =
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Managing.Common;
|
|||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.Evm;
|
using Managing.Domain.Evm;
|
||||||
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -96,6 +97,11 @@ public class EvmProcessor : BaseProcessor
|
|||||||
return await _evmManager.GetTrade(reference, Constants.Chains.Arbitrum, ticker);
|
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)
|
public override decimal GetVolume(Account account, Ticker ticker)
|
||||||
{
|
{
|
||||||
var volume = _evmManager.GetVolume(SubgraphProvider.ChainlinkPrice, ticker);
|
var volume = _evmManager.GetVolume(SubgraphProvider.ChainlinkPrice, ticker);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using FTX.Net.Objects;
|
|||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Infrastructure.Exchanges.Helpers;
|
using Managing.Infrastructure.Exchanges.Helpers;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -194,4 +195,9 @@ public class FtxProcessor : BaseProcessor
|
|||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
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.Common;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Infrastructure.Exchanges.Helpers;
|
using Managing.Infrastructure.Exchanges.Helpers;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -92,6 +93,11 @@ public class KrakenProcessor : BaseProcessor
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task<List<FundingRate>> GetFundingRates()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
|
public override async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
|
||||||
{
|
{
|
||||||
LoadClient(account);
|
LoadClient(account);
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
|
|
||||||
return new Trade(data.CreateTime,
|
return new Trade(data.CreateTime,
|
||||||
(data.Side == OrderSide.Buy) ? TradeDirection.Long : TradeDirection.Short,
|
(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, "");
|
data.ClientOrderId, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,16 +116,12 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
return "ATOMUSDT";
|
return "ATOMUSDT";
|
||||||
case Ticker.AVAX:
|
case Ticker.AVAX:
|
||||||
return "AVAXUSDT";
|
return "AVAXUSDT";
|
||||||
case Ticker.BAT:
|
|
||||||
return "BATUSDT";
|
|
||||||
case Ticker.BNB:
|
case Ticker.BNB:
|
||||||
return "BNBUSDT";
|
return "BNBUSDT";
|
||||||
case Ticker.BTC:
|
case Ticker.BTC:
|
||||||
return "BTCUSDT";
|
return "BTCUSDT";
|
||||||
case Ticker.CRV:
|
case Ticker.CRV:
|
||||||
return "CRVUSDT";
|
return "CRVUSDT";
|
||||||
case Ticker.DEFI:
|
|
||||||
return "DEFIUSDT";
|
|
||||||
case Ticker.DOGE:
|
case Ticker.DOGE:
|
||||||
return "DOGEUSDT";
|
return "DOGEUSDT";
|
||||||
case Ticker.DOT:
|
case Ticker.DOT:
|
||||||
@@ -143,8 +140,6 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
return "GRTUSDT";
|
return "GRTUSDT";
|
||||||
case Ticker.IMX:
|
case Ticker.IMX:
|
||||||
return "IMXUSDT";
|
return "IMXUSDT";
|
||||||
case Ticker.KAVA:
|
|
||||||
return "KAVAUSDT";
|
|
||||||
case Ticker.KSM:
|
case Ticker.KSM:
|
||||||
return "KSMUSDT";
|
return "KSMUSDT";
|
||||||
case Ticker.LINK:
|
case Ticker.LINK:
|
||||||
@@ -159,10 +154,6 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
return "MKRUSDT";
|
return "MKRUSDT";
|
||||||
case Ticker.NEAR:
|
case Ticker.NEAR:
|
||||||
return "NEARUSDT";
|
return "NEARUSDT";
|
||||||
case Ticker.NEO:
|
|
||||||
return "NEOUSDT";
|
|
||||||
case Ticker.ONT:
|
|
||||||
return "ONTUSDT";
|
|
||||||
case Ticker.SAND:
|
case Ticker.SAND:
|
||||||
return "SANDUSDT";
|
return "SANDUSDT";
|
||||||
case Ticker.SOL:
|
case Ticker.SOL:
|
||||||
@@ -175,19 +166,16 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
return "THETAUSDT";
|
return "THETAUSDT";
|
||||||
case Ticker.UNI:
|
case Ticker.UNI:
|
||||||
return "UNIUSDT";
|
return "UNIUSDT";
|
||||||
case Ticker.WAVES:
|
|
||||||
return "WAVESUSDT";
|
|
||||||
case Ticker.XMR:
|
case Ticker.XMR:
|
||||||
return "XMRUSDT";
|
return "XMRUSDT";
|
||||||
case Ticker.XRP:
|
case Ticker.XRP:
|
||||||
return "XRPUSDT";
|
return "XRPUSDT";
|
||||||
case Ticker.XTZ:
|
case Ticker.XTZ:
|
||||||
return "XTZUSDT";
|
return "XTZUSDT";
|
||||||
case Ticker.ZEC:
|
|
||||||
return "ZECUSDT";
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,28 +19,18 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
return "ADA-PERP";
|
return "ADA-PERP";
|
||||||
case Ticker.APE:
|
case Ticker.APE:
|
||||||
return "APE-PERP";
|
return "APE-PERP";
|
||||||
case Ticker.ALICE:
|
|
||||||
return "ALICE-PERP";
|
|
||||||
case Ticker.ALGO:
|
case Ticker.ALGO:
|
||||||
return "ALGO-PERP";
|
return "ALGO-PERP";
|
||||||
case Ticker.ATOM:
|
case Ticker.ATOM:
|
||||||
return "ATOM-PERP";
|
return "ATOM-PERP";
|
||||||
case Ticker.AVAX:
|
case Ticker.AVAX:
|
||||||
return "AVAX-PERP";
|
return "AVAX-PERP";
|
||||||
case Ticker.AXS:
|
|
||||||
return "AXS-PERP";
|
|
||||||
case Ticker.BAT:
|
|
||||||
return "BAT-PERP";
|
|
||||||
case Ticker.BNB:
|
case Ticker.BNB:
|
||||||
return "BNB-PERP";
|
return "BNB-PERP";
|
||||||
case Ticker.BTC:
|
case Ticker.BTC:
|
||||||
return "BTC-PERP";
|
return "BTC-PERP";
|
||||||
case Ticker.BAL:
|
case Ticker.BAL:
|
||||||
return "BAL-PERP";
|
return "BAL-PERP";
|
||||||
case Ticker.C98:
|
|
||||||
return "C98-PERP";
|
|
||||||
case Ticker.CHR:
|
|
||||||
return "CHR-PERP";
|
|
||||||
case Ticker.CHZ:
|
case Ticker.CHZ:
|
||||||
return "CHZ-PERP";
|
return "CHZ-PERP";
|
||||||
case Ticker.COMP:
|
case Ticker.COMP:
|
||||||
@@ -49,10 +39,6 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
return "CRO-PERP";
|
return "CRO-PERP";
|
||||||
case Ticker.CRV:
|
case Ticker.CRV:
|
||||||
return "CRV-PERP";
|
return "CRV-PERP";
|
||||||
case Ticker.CVC:
|
|
||||||
return "CVC-PERP";
|
|
||||||
case Ticker.DEFI:
|
|
||||||
return "DEFI-PERP";
|
|
||||||
case Ticker.DOGE:
|
case Ticker.DOGE:
|
||||||
return "DOGE-PERP";
|
return "DOGE-PERP";
|
||||||
case Ticker.DOT:
|
case Ticker.DOT:
|
||||||
@@ -73,26 +59,14 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
return "FTM-PERP";
|
return "FTM-PERP";
|
||||||
case Ticker.GALA:
|
case Ticker.GALA:
|
||||||
return "GALA-PERP";
|
return "GALA-PERP";
|
||||||
case Ticker.GMT:
|
|
||||||
return "GMT-PERP";
|
|
||||||
case Ticker.GRT:
|
case Ticker.GRT:
|
||||||
return "GRT-PERP";
|
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:
|
case Ticker.KSM:
|
||||||
return "KSM-PERP";
|
return "KSM-PERP";
|
||||||
case Ticker.LDO:
|
case Ticker.LDO:
|
||||||
return "LDO-PERP";
|
return "LDO-PERP";
|
||||||
case Ticker.LINK:
|
case Ticker.LINK:
|
||||||
return "LINK-PERP";
|
return "LINK-PERP";
|
||||||
case Ticker.LOOKS:
|
|
||||||
return "LOOKS-PERP";
|
|
||||||
case Ticker.LRC:
|
case Ticker.LRC:
|
||||||
return "LRC-PERP";
|
return "LRC-PERP";
|
||||||
case Ticker.LTC:
|
case Ticker.LTC:
|
||||||
@@ -105,18 +79,8 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
return "MKR-PERP";
|
return "MKR-PERP";
|
||||||
case Ticker.NEAR:
|
case Ticker.NEAR:
|
||||||
return "NEAR-PERP";
|
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:
|
case Ticker.QTUM:
|
||||||
return "QTUM-PERP";
|
return "QTUM-PERP";
|
||||||
case Ticker.REEF:
|
|
||||||
return "REEF-PERP";
|
|
||||||
case Ticker.REN:
|
case Ticker.REN:
|
||||||
return "REN-PERP";
|
return "REN-PERP";
|
||||||
case Ticker.ROSE:
|
case Ticker.ROSE:
|
||||||
@@ -131,35 +95,22 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
return "SOL-PERP";
|
return "SOL-PERP";
|
||||||
case Ticker.SRM:
|
case Ticker.SRM:
|
||||||
return "SRM-PERP";
|
return "SRM-PERP";
|
||||||
case Ticker.STMX:
|
|
||||||
return "STMX-PERP";
|
|
||||||
case Ticker.SUSHI:
|
case Ticker.SUSHI:
|
||||||
return "SUSHI-PERP";
|
return "SUSHI-PERP";
|
||||||
case Ticker.SXP:
|
|
||||||
return "SXP-PERP";
|
|
||||||
case Ticker.THETA:
|
case Ticker.THETA:
|
||||||
return "THETA-PERP";
|
return "THETA-PERP";
|
||||||
case Ticker.UNI:
|
case Ticker.UNI:
|
||||||
return "UNI-PERP";
|
return "UNI-PERP";
|
||||||
case Ticker.VET:
|
|
||||||
return "VET-PERP";
|
|
||||||
case Ticker.WAVES:
|
|
||||||
return "WAVES-PERP";
|
|
||||||
case Ticker.XMR:
|
case Ticker.XMR:
|
||||||
return "XMR-PERP";
|
return "XMR-PERP";
|
||||||
case Ticker.XRP:
|
case Ticker.XRP:
|
||||||
return "XRP-PERP";
|
return "XRP-PERP";
|
||||||
case Ticker.XTZ:
|
case Ticker.XTZ:
|
||||||
return "XTZ-PERP";
|
return "XTZ-PERP";
|
||||||
case Ticker.YFI:
|
|
||||||
return "YFI-PERP";
|
|
||||||
case Ticker.ZEC:
|
|
||||||
return "ZEC-PERP";
|
|
||||||
case Ticker.ZIL:
|
|
||||||
return "ZIL-PERP";
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +128,8 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
|
|
||||||
return new Trade(data.CreateTime,
|
return new Trade(data.CreateTime,
|
||||||
(data.Side == OrderSide.Buy) ? TradeDirection.Long : TradeDirection.Short,
|
(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, "");
|
data.ClientOrderId, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +146,8 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
|
|
||||||
return new Trade(data.CreateTime,
|
return new Trade(data.CreateTime,
|
||||||
(data.Side == OrderSide.Buy) ? TradeDirection.Long : TradeDirection.Short,
|
(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(), "");
|
Guid.NewGuid().ToString(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +170,8 @@ namespace Managing.Infrastructure.Exchanges.Helpers
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new Trade(data.CreateTime, TradeDirection.None,
|
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, "");
|
data.ClientOrderId, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ public static class DiscordHelpers
|
|||||||
fields.Add(new EmbedFieldBuilder
|
fields.Add(new EmbedFieldBuilder
|
||||||
{
|
{
|
||||||
Name = $"{GetExplorerUrl(trader.Address)}",
|
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;
|
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)
|
public static Embed GetEmbed(string address, string title, List<EmbedFieldBuilder> fields, Color color)
|
||||||
{
|
{
|
||||||
return new EmbedBuilder
|
return new EmbedBuilder
|
||||||
@@ -57,14 +100,16 @@ public static class DiscordHelpers
|
|||||||
fields.Add(new EmbedFieldBuilder
|
fields.Add(new EmbedFieldBuilder
|
||||||
{
|
{
|
||||||
Name = $"{GetExplorerUrl(trade.ExchangeOrderId)}",
|
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
|
fields.Add(new EmbedFieldBuilder
|
||||||
{
|
{
|
||||||
Name = "Summary",
|
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
|
var embed = new EmbedBuilder
|
||||||
@@ -77,4 +122,34 @@ public static class DiscordHelpers
|
|||||||
|
|
||||||
return embed;
|
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
|
#region Setup
|
||||||
|
|
||||||
// The hosted service has started
|
// The hosted service has started
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -85,7 +86,9 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
case Constants.DiscordSlashCommand.LeaderboardPosition:
|
case Constants.DiscordSlashCommand.LeaderboardPosition:
|
||||||
await SlashCommands.HandleLeadboardPositionCommand(_services, command);
|
await SlashCommands.HandleLeadboardPositionCommand(_services, command);
|
||||||
break;
|
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");
|
noobiesboardCommand.WithDescription("Shows the last Noobies board");
|
||||||
applicationCommandProperties.Add(noobiesboardCommand.Build());
|
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());
|
await _client.BulkOverwriteGlobalApplicationCommandsAsync(applicationCommandProperties.ToArray());
|
||||||
}
|
}
|
||||||
@@ -132,17 +139,13 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
List<ApplicationCommandProperties> commands = new();
|
List<ApplicationCommandProperties> commands = new();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
private async Task Log(LogMessage arg)
|
private async Task Log(LogMessage arg)
|
||||||
{
|
{
|
||||||
await Task.Run(() =>
|
await Task.Run(() => { _logger.LogInformation(arg.ToString()); });
|
||||||
{
|
|
||||||
_logger.LogInformation(arg.ToString());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CommandExecuted(Optional<CommandInfo> command, ICommandContext context, IResult result)
|
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}"));
|
await Log(new LogMessage(LogSeverity.Error, nameof(CommandExecuted), $"Error: {result.ErrorReason}"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// react to message
|
// react to message
|
||||||
await context.Message.AddReactionAsync(new Emoji("🤖")); // robot emoji
|
await context.Message.AddReactionAsync(new Emoji("🤖")); // robot emoji
|
||||||
}
|
}
|
||||||
@@ -177,13 +181,16 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
_client.ButtonExecuted -= ButtonHandler;
|
_client.ButtonExecuted -= ButtonHandler;
|
||||||
_client.SlashCommandExecuted -= SlashCommandHandler;
|
_client.SlashCommandExecuted -= SlashCommandHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_client?.Dispose();
|
_client?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region In
|
#region In
|
||||||
|
|
||||||
public async Task ButtonHandler(SocketMessageComponent component)
|
public async Task ButtonHandler(SocketMessageComponent component)
|
||||||
{
|
{
|
||||||
var parameters = component.Data.CustomId.Split(new[] { _separator }, StringSplitOptions.None);
|
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")
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -219,7 +227,10 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
|
|
||||||
var json = MiscExtensions.Base64Decode(parameters[1]);
|
var json = MiscExtensions.Base64Decode(parameters[1]);
|
||||||
var trade = JsonConvert.DeserializeObject<CopyTradeData>(json);
|
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)
|
public async Task OpenPosition(SocketMessageComponent component, string[] parameters)
|
||||||
@@ -234,19 +245,24 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
var moneyManagementName = parameters[5];
|
var moneyManagementName = parameters[5];
|
||||||
var expiration = DateTime.Parse(parameters[6]);
|
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)
|
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
|
else
|
||||||
{
|
{
|
||||||
var exchangeService = (IExchangeService)_services.GetService(typeof(IExchangeService));
|
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 accountService = (IAccountService)_services.GetService(typeof(IAccountService));
|
||||||
var tradingService = (ITradingService)_services.GetService(typeof(ITradingService));
|
var tradingService = (ITradingService)_services.GetService(typeof(ITradingService));
|
||||||
|
|
||||||
@@ -261,7 +277,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
|
var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
|
||||||
.Handle(tradeCommand);
|
.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),
|
await component.Channel.SendMessageAsync(MessengerHelpers.GetPositionMessage(position),
|
||||||
components: builder.Build());
|
components: builder.Build());
|
||||||
@@ -287,7 +304,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
await component.RespondAsync("Alright, let met few seconds to close this position");
|
await component.RespondAsync("Alright, let met few seconds to close this position");
|
||||||
var position = _tradingService.GetPositionByIdentifier(parameters[1]);
|
var position = _tradingService.GetPositionByIdentifier(parameters[1]);
|
||||||
var command = new ClosePositionCommand(position);
|
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>()
|
var fields = new List<EmbedFieldBuilder>()
|
||||||
{
|
{
|
||||||
new EmbedFieldBuilder
|
new EmbedFieldBuilder
|
||||||
@@ -315,7 +333,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
IsInline = true
|
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);
|
await component.Channel.SendMessageAsync("", embed: embed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,6 +342,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
|
|
||||||
|
|
||||||
#region Out
|
#region Out
|
||||||
|
|
||||||
public async Task SendSignal(string message)
|
public async Task SendSignal(string message)
|
||||||
{
|
{
|
||||||
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
||||||
@@ -330,15 +350,18 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
await channel.SendMessageAsync(message, components: builder.Build());
|
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 expirationDate = DateTime.Now.AddMinutes(_settings.ButtonExpirationMinutes).ToString("G");
|
||||||
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
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());
|
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 channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel;
|
||||||
|
|
||||||
@@ -373,7 +396,10 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
|
|
||||||
if (oldTrade != null)
|
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 " : "";
|
var titlePrefix = oldTrade != null ? "Increase " : "";
|
||||||
@@ -398,24 +424,29 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
|
|
||||||
if (oldTrade == null)
|
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
|
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);
|
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 expirationDate = DateTime.Now.AddMinutes(_settings.ButtonExpirationMinutes).ToString("G");
|
||||||
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
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());
|
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)
|
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);
|
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;
|
var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel;
|
||||||
await channel.SendMessageAsync("", embed: embed);
|
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;
|
var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel;
|
||||||
await channel.SendMessageAsync("", embed: embed);
|
await channel.SendMessageAsync("", embed: embed);
|
||||||
}
|
}
|
||||||
@@ -517,7 +552,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
public async Task SendPosition(Position position)
|
public async Task SendPosition(Position position)
|
||||||
{
|
{
|
||||||
var channel = _client.GetChannel(_settings.TradesChannelId) as IMessageChannel;
|
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());
|
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;
|
var channel = _client.GetChannel(_settings.LeaderboardChannelId) as IMessageChannel;
|
||||||
await channel.SendMessageAsync("", embed: DiscordHelpers.GetTradersEmbed(traders, "Leaderboard"));
|
await channel.SendMessageAsync("", embed: DiscordHelpers.GetTradersEmbed(traders, "Leaderboard"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendBadTraders(List<Trader> traders)
|
public async Task SendBadTraders(List<Trader> traders)
|
||||||
@@ -534,24 +569,37 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
await channel.SendMessageAsync("", embed: DiscordHelpers.GetTradersEmbed(traders, "Noobiesboard"));
|
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
|
#endregion
|
||||||
|
|
||||||
public class CopyTradeData
|
public class CopyTradeData
|
||||||
{
|
{
|
||||||
[JsonProperty(PropertyName = "D")]
|
[JsonProperty(PropertyName = "D")] public TradeDirection Direction { get; set; }
|
||||||
public TradeDirection Direction { get; set; }
|
[JsonProperty(PropertyName = "T")] public Ticker Ticker { get; set; }
|
||||||
[JsonProperty(PropertyName = "T")]
|
[JsonProperty(PropertyName = "A")] public string AccountName { get; set; }
|
||||||
public Ticker Ticker { get; set; }
|
[JsonProperty(PropertyName = "E")] public int ExpirationMinute { get; set; }
|
||||||
[JsonProperty(PropertyName = "A")]
|
[JsonProperty(PropertyName = "L")] public decimal Leverage { get; set; }
|
||||||
public string AccountName { get; set; }
|
[JsonProperty(PropertyName = "M")] public string MoneyManagementName { get; internal 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");
|
RequestsChannelId = config.GetValue<ulong>("Discord:RequestsChannelId");
|
||||||
LeaderboardChannelId = config.GetValue<ulong>("Discord:LeaderboardChannelId");
|
LeaderboardChannelId = config.GetValue<ulong>("Discord:LeaderboardChannelId");
|
||||||
NoobiesboardChannelId = config.GetValue<ulong>("Discord:NoobiesboardChannelId");
|
NoobiesboardChannelId = config.GetValue<ulong>("Discord:NoobiesboardChannelId");
|
||||||
|
FundingRateChannelId = config.GetValue<ulong>("Discord:FundingRateChannelId");
|
||||||
ButtonExpirationMinutes = config.GetValue<int>("Discord:ButtonExpirationMinutes");
|
ButtonExpirationMinutes = config.GetValue<int>("Discord:ButtonExpirationMinutes");
|
||||||
HandleUserAction = config.GetValue<bool>("Discord:HandleUserAction");
|
HandleUserAction = config.GetValue<bool>("Discord:HandleUserAction");
|
||||||
BotActivity = config.GetValue<string>("Discord:BotActivity");
|
BotActivity = config.GetValue<string>("Discord:BotActivity");
|
||||||
@@ -32,6 +33,6 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
public bool BotEnabled { get; set; }
|
public bool BotEnabled { get; set; }
|
||||||
public ulong LeaderboardChannelId { get; set; }
|
public ulong LeaderboardChannelId { get; set; }
|
||||||
public ulong NoobiesboardChannelId { get; set; }
|
public ulong NoobiesboardChannelId { get; set; }
|
||||||
|
public ulong FundingRateChannelId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using Managing.Application.Workers.Abstractions;
|
using Managing.Application.Workers.Abstractions;
|
||||||
|
using Managing.Domain.Statistics;
|
||||||
|
|
||||||
namespace Managing.Infrastructure.Messengers.Discord;
|
namespace Managing.Infrastructure.Messengers.Discord;
|
||||||
|
|
||||||
@@ -23,6 +24,15 @@ public static class SlashCommands
|
|||||||
{
|
{
|
||||||
var statisticService = (IStatisticService)service.GetService(typeof(IStatisticService));
|
var statisticService = (IStatisticService)service.GetService(typeof(IStatisticService));
|
||||||
var trades = await statisticService.GetLeadboardPositons();
|
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,6 +6,13 @@ namespace Managing.Infrastructure.Tests;
|
|||||||
|
|
||||||
public class TradaoTests
|
public class TradaoTests
|
||||||
{
|
{
|
||||||
|
private readonly TradaoService _tradaoService;
|
||||||
|
|
||||||
|
public TradaoTests()
|
||||||
|
{
|
||||||
|
_tradaoService = new TradaoService();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void Should_return_best_trader()
|
public async void Should_return_best_trader()
|
||||||
{
|
{
|
||||||
@@ -21,4 +28,39 @@ public class TradaoTests
|
|||||||
var details = (await service.GetBadTrader()).FindBadTrader();
|
var details = (await service.GetBadTrader()).FindBadTrader();
|
||||||
Assert.NotNull(details);
|
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.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 NBitcoin;
|
||||||
using Nethereum.Contracts;
|
using Nethereum.Contracts;
|
||||||
|
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
|
||||||
|
using Nethereum.HdWallet;
|
||||||
using Nethereum.Hex.HexTypes;
|
using Nethereum.Hex.HexTypes;
|
||||||
using Nethereum.Signer;
|
using Nethereum.Signer;
|
||||||
using Nethereum.Web3;
|
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 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;
|
namespace Managing.Infrastructure.Evm;
|
||||||
|
|
||||||
@@ -590,6 +591,15 @@ public class EvmManager : IEvmManager
|
|||||||
return await GmxService.GetTrade(web3, reference, ticker);
|
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)
|
public async Task<decimal> QuantityInPosition(string chainName, string publicAddress, Ticker ticker)
|
||||||
{
|
{
|
||||||
var chain = ChainService.GetChain(chainName);
|
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.Statistics;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Infrastructure.Evm.Models;
|
using Managing.Infrastructure.Evm.Models;
|
||||||
using System.Net.Http.Json;
|
|
||||||
|
|
||||||
namespace Managing.Infrastructure.Evm.Services;
|
namespace Managing.Infrastructure.Evm.Services;
|
||||||
|
|
||||||
@@ -10,14 +12,32 @@ public class TradaoService : ITradaoService
|
|||||||
{
|
{
|
||||||
private readonly HttpClient _httpClient;
|
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()
|
public TradaoService()
|
||||||
{
|
{
|
||||||
_httpClient = new HttpClient(); ;
|
_httpClient = new HttpClient();
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Trader>> GetBadTrader()
|
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)
|
if (bestTraders == null || bestTraders.row.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -30,7 +50,8 @@ public class TradaoService : ITradaoService
|
|||||||
|
|
||||||
public async Task<List<Trader>> GetBestTrader()
|
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)
|
if (bestTraders == null || bestTraders.row.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -42,7 +63,9 @@ public class TradaoService : ITradaoService
|
|||||||
|
|
||||||
public async Task<List<Trade>> GetTrades(string address)
|
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>();
|
var trades = new List<Trade>();
|
||||||
|
|
||||||
@@ -68,12 +91,15 @@ public class TradaoService : ITradaoService
|
|||||||
return trades;
|
return trades;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task<List<Trader>> GetTraderDetails(TradaoList traders)
|
private async Task<List<Trader>> GetTraderDetails(TradaoList traders)
|
||||||
{
|
{
|
||||||
var result = new List<Trader>();
|
var result = new List<Trader>();
|
||||||
foreach (var trader in traders.row)
|
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)
|
if (response != null)
|
||||||
result.Add(Map(response, trader.user));
|
result.Add(Map(response, trader.user));
|
||||||
@@ -95,4 +121,94 @@ public class TradaoService : ITradaoService
|
|||||||
Roi = Convert.ToDecimal(response.summary.roi)
|
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