Fix getbalance + fix get orders for bot

This commit is contained in:
2025-04-21 12:15:53 +02:00
parent 3113e5d278
commit 868c7bdb65
13 changed files with 140 additions and 90 deletions

View File

@@ -395,7 +395,7 @@ public class BotController : BaseController
Name = item.Name,
Signals = item.Signals.ToList(),
Positions = item.Positions,
Candles = item.Candles.ToList(),
Candles = item.Candles.DistinctBy(c => c.Date).ToList(),
WinRate = item.GetWinRate(),
ProfitAndLoss = item.GetProfitAndLoss(),
Timeframe = item.Timeframe,

View File

@@ -30,24 +30,20 @@ using OpenApiSecurityScheme = NSwag.OpenApiSecurityScheme;
// Builder
var builder = WebApplication.CreateBuilder(args);
// Set up Aspire telemetry and health checks when enabled, in all environments
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPIRE_ENABLED")))
{
// Add Service Defaults - using extension methods directly
builder.Services.AddServiceDiscovery();
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
// Add Service Defaults - using extension methods directly
builder.Services.AddServiceDiscovery();
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
var mongoConnectionString = builder.Configuration.GetSection(Constants.Databases.MongoDb)["ConnectionString"];
var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"];
var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"];
// Add specific health checks for databases and other services
builder.Services.AddHealthChecks()
.AddMongoDb(mongoConnectionString, name: "mongodb", tags: ["database"])
.AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
.AddUrlGroup(new Uri($"{web3ProxyUrl}/health"), name: "web3proxy", tags: ["api"]);
}
var mongoConnectionString = builder.Configuration.GetSection(Constants.Databases.MongoDb)["ConnectionString"];
var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"];
var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"];
// Add specific health checks for databases and other services
builder.Services.AddHealthChecks()
.AddMongoDb(mongoConnectionString, name: "mongodb", tags: ["database"])
.AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
.AddUrlGroup(new Uri($"{web3ProxyUrl}/health"), name: "web3proxy", tags: ["api"]);
builder.Configuration.SetBasePath(AppContext.BaseDirectory);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
@@ -162,7 +158,7 @@ builder.Services.AddSwaggerGen(options =>
});
builder.WebHost.SetupDiscordBot();
// builder.Services.AddHostedService<BotManagerWorker>();
builder.Services.AddHostedService<BotManagerWorker>();
// App
var app = builder.Build();
@@ -201,20 +197,16 @@ app.UseEndpoints(endpoints =>
endpoints.MapHub<BacktestHub>("/backtesthub");
endpoints.MapHub<CandleHub>("/candlehub");
// Always add health check endpoints when Aspire is enabled, regardless of environment
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPIRE_ENABLED")))
endpoints.MapHealthChecks("/health", new HealthCheckOptions
{
endpoints.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
endpoints.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
}
endpoints.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
});
app.Run();

View File

@@ -19,12 +19,12 @@ public interface IEvmManager
Task<List<EvmBalance>> GetBalances(Chain chain, int page, int pageSize, string publicAddress);
Task<List<EvmBalance>> GetAllBalancesOnAllChain(string publicAddress);
Task<List<Candle>> GetCandles(SubgraphProvider subgraphProvider, Ticker ticker, DateTime startDate,
Task<List<Candle>> GetCandles(Ticker ticker, DateTime startDate,
Timeframe interval);
decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker);
Task<List<Ticker>> GetAvailableTicker();
Task<Candle> GetCandle(SubgraphProvider subgraphProvider, Ticker ticker);
Task<Candle> GetCandle(Ticker ticker);
Task<bool> InitAddress(string publicAddress);
Task<bool> Send(Chain chain, Ticker ticker, decimal amount, string publicAddress, string privateKey,

View File

@@ -171,7 +171,7 @@ public class TradingBot : Bot, ITradingBot
{
Logger.LogInformation($"____________________{Name}____________________");
Logger.LogInformation(
$"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}");
$"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Last candle : {OptimizedCandles.Last().Date} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}");
}
var previousLastCandle = OptimizedCandles.LastOrDefault();
@@ -646,11 +646,10 @@ public class TradingBot : Bot, ITradingBot
{
try
{
var test = await ExchangeService.CancelOrder(Account, Ticker);
var openOrders = await ExchangeService.GetOpenOrders(Account, Ticker);
if (openOrders.Any())
{
// TODO: Check if position is open, do not cancel orders if position still open
Logger.LogInformation($"Canceling all orders for {Ticker}");
await ExchangeService.CancelOrder(Account, Ticker);
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker);

View File

@@ -249,6 +249,7 @@ public static class Enums
/// 1d
/// </summary>
OneDay,
OneMinute
}
public enum Ticker

View File

@@ -64,13 +64,13 @@ public class EvmProcessor : BaseProcessor
public override Candle GetCandle(Account account, Ticker ticker, DateTime date)
{
return _evmManager.GetCandle(SubgraphProvider.Gbc, ticker).Result;
return _evmManager.GetCandle(ticker).Result;
}
public override async Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate,
Timeframe interval)
{
return await _evmManager.GetCandles(SubgraphProvider.Gbc, ticker, startDate, interval);
return await _evmManager.GetCandles(ticker, startDate, interval);
}
public override decimal GetFee(Account account, bool isForPaperTrading = false)

View File

@@ -1,5 +1,6 @@
using System.Net.Http.Json;
using System.Numerics;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Common;
using Managing.Core;
@@ -29,6 +30,7 @@ using BalanceOfOutputDTO = Nethereum.Contracts.Standards.ERC20.ContractDefinitio
using Chain = Managing.Domain.Evm.Chain;
using TransferEventDTO = Nethereum.Contracts.Standards.ERC721.ContractDefinition.TransferEventDTO;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Managing.Infrastructure.Evm;
@@ -38,9 +40,9 @@ public class EvmManager : IEvmManager
private readonly HttpClient _httpClient;
private readonly string _password = "!StrongPassword94";
private readonly IEnumerable<ISubgraphPrices> _subgraphs;
private Dictionary<string, Dictionary<string, decimal>> _geckoPrices;
private readonly GmxV2Service _gmxV2Service;
private readonly IWeb3ProxyService _web3ProxyService;
private readonly ICacheService _cacheService;
private readonly List<Ticker> _eligibleTickers = new List<Ticker>()
{
@@ -49,44 +51,15 @@ public class EvmManager : IEvmManager
};
public EvmManager(IEnumerable<ISubgraphPrices> subgraphs,
IWeb3ProxyService web3ProxyService)
IWeb3ProxyService web3ProxyService, ICacheService cacheService)
{
var defaultChain = ChainService.GetEthereum();
_web3 = new Web3(defaultChain.RpcUrl);
_httpClient = new HttpClient();
_subgraphs = subgraphs;
_web3ProxyService = web3ProxyService;
_geckoPrices = _geckoPrices != null && _geckoPrices.Any()
? _geckoPrices
: new Dictionary<string, Dictionary<string, decimal>>();
_gmxV2Service = new GmxV2Service();
SetupPrices();
}
public void SetupPrices()
{
try
{
var geckoIds = new List<string>();
foreach (var ticker in Enum.GetValues<Ticker>())
{
var geckoId = TokenService.GetGeckoToken(ticker.ToString())?.Id;
if (geckoId != null)
{
geckoIds.Add(geckoId);
}
}
if (geckoIds != null && geckoIds.Count > 0 && !_geckoPrices.Any())
{
_geckoPrices = GetPrices(geckoIds).Result;
}
}
catch (Exception ex)
{
// TODO : Handle error
}
_cacheService = cacheService;
}
public async Task<decimal> GetAddressBalance(string address)
@@ -208,16 +181,15 @@ public class EvmManager : IEvmManager
{
var web3 = new Web3(chain.RpcUrl);
var etherBalance = Web3.Convert.FromWei(await web3.Eth.GetBalance.SendRequestAsync(account));
var etherPrice = (await GetPrices(new List<string> { "ethereum" }))["ethereum"]["usd"];
var lastCandle = await GetCandle(Ticker.ETH);
return new EvmBalance()
{ Balance = etherBalance, Price = etherPrice, TokenName = "ETH", Value = etherBalance * etherPrice };
{ Balance = etherBalance, Price = lastCandle.Close, TokenName = "ETH", Value = etherBalance * lastCandle.Close };
}
public async Task<List<EvmBalance>> GetAllBalances(Chain chain, string publicAddress)
{
var balances = new List<EvmBalance>();
SetupPrices();
foreach (var ticker in Enum.GetValues<Ticker>())
{
try
@@ -259,12 +231,8 @@ public class EvmManager : IEvmManager
.QueryAsync<BigInteger>(contractAddress, balanceOfMessage)
.ConfigureAwait(false);
var geckoId = TokenService.GetGeckoToken(ticker.ToString())?.Id;
if (geckoId == null)
return null;
var tokenUsdPrice = _geckoPrices[geckoId][Constants.Stablecoins.Usd.ToLowerInvariant()];
var lastCandle = await GetCandle(ticker);
var tokenUsdPrice = lastCandle.Close;
var tokenDecimal = TokenService.GetDecimal(ticker);
var balanceFromWei = Web3.Convert.FromWei(balance, tokenDecimal);
@@ -374,7 +342,7 @@ public class EvmManager : IEvmManager
return chainBalances;
}
public async Task<List<Candle>> GetCandles(SubgraphProvider subgraphProvider, Ticker ticker, DateTime startDate,
public async Task<List<Candle>> GetCandles(Ticker ticker, DateTime startDate,
Timeframe timeframe)
{
string gmxTimeframe = GmxHelpers.GeTimeframe(timeframe);
@@ -423,11 +391,21 @@ public class EvmManager : IEvmManager
return GmxV2Mappers.Map(tokenList).Where(t => _eligibleTickers.Contains(t)).ToList();
}
public async Task<Candle> GetCandle(SubgraphProvider subgraphProvider, Ticker ticker)
public async Task<Candle> GetCandle(Ticker ticker)
{
var lastPrices = await GetCandles(subgraphProvider, ticker, DateTime.UtcNow.AddMinutes(-15),
Timeframe.FiveMinutes);
return lastPrices.Last();
var key = $"lastcandle-{ticker}";
var cachedCandle = _cacheService.GetValue<Candle>(key);
if (cachedCandle == null)
{
var lastCandles = await GetCandles(ticker, DateTime.UtcNow.AddMinutes(-5),
Timeframe.OneMinute);
cachedCandle = lastCandles.Last();
_cacheService.SaveValue(key, cachedCandle, TimeSpan.FromMinutes(5));
}
return cachedCandle;
}
public async Task<bool> InitAddress(string publicAddress)
@@ -776,10 +754,10 @@ public class EvmManager : IEvmManager
{
if (account.IsPrivyWallet)
{
var orders = await _web3ProxyService.CallGmxServiceAsync<List<Trade>>("/get-orders",
new { address = account.Key, walletId = account.Secret, ticker = ticker.ToString() });
var result = await _web3ProxyService.GetGmxServiceAsync<GetGmxTradesResponse>("/trades",
new { account = account.Key, ticker = ticker.ToString() });
return orders;
return result.Trades;
}
else
{

View File

@@ -24,6 +24,7 @@
<ItemGroup>
<ProjectReference Include="..\Managing.ABI.GmxV2\Managing.ABI.GmxV2.csproj"/>
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj"/>
<ProjectReference Include="..\Managing.Application\Managing.Application.csproj" />
<ProjectReference Include="..\Managing.Tools.ABI\Managing.Tools.ABI.csproj"/>
</ItemGroup>

View File

@@ -0,0 +1,10 @@
using Managing.Domain.Trades;
using Newtonsoft.Json;
namespace Managing.Infrastructure.Evm.Models.Proxy;
public class GetGmxTradesResponse : Web3ProxyBaseResponse
{
[JsonProperty("trades")]
public List<Trade> Trades { get; set; }
}

View File

@@ -106,6 +106,7 @@ public static class GmxHelpers
{
return timeframe switch
{
Timeframe.OneMinute => "1m",
Timeframe.FiveMinutes => "5m",
Timeframe.FifteenMinutes => "15m",
Timeframe.ThirtyMinutes => "30m",

View File

@@ -118,7 +118,7 @@ namespace Managing.Infrastructure.Evm.Services
endpoint = $"/{endpoint}";
}
var url = $"{_settings.BaseUrl}gmx{endpoint}";
var url = $"{_settings.BaseUrl}/api/gmx{endpoint}";
if (payload != null)
{

View File

@@ -130,7 +130,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
})
// Define route to get a trade
fastify.get('/trade', {
fastify.get('/trades', {
schema: {
querystring: Type.Object({
account: Type.String(),

View File

@@ -0,0 +1,68 @@
import { TradeChart, CardPositionItem } from '..'
import { Backtest, MoneyManagement, TradingBot } from '../../../generated/ManagingApi'
import { CardPosition, CardText } from '../../mollecules'
interface IBotRowDetailsProps {
bot: TradingBot;
}
const BotRowDetails: React.FC<IBotRowDetailsProps> = ({
bot
}) => {
if (!bot) {
return <div>No bot data available</div>;
}
const {
candles,
positions,
signals,
moneyManagement
} = bot;
return (
<>
<div className="grid grid-flow-row">
<div className="grid grid-cols-4 p-5">
<CardPosition
positivePosition={true}
positions={positions.filter((p) => {
const realized = p.profitAndLoss?.realized ?? 0
return realized > 0 ? p : null
})}
></CardPosition>
<CardPosition
positivePosition={false}
positions={positions.filter((p) => {
const realized = p.profitAndLoss?.realized ?? 0
return realized <= 0 ? p : null
})}
></CardPosition>
<CardPositionItem positions={positions}></CardPositionItem>
<CardText
title="Money Management"
content={
"SL: " +(moneyManagement?.stopLoss * 100).toFixed(2) + "% TP: " +
(moneyManagement?.takeProfit * 100).toFixed(2) + "%"
}
></CardText>
</div>
<div>
<figure>
<TradeChart
width={1400}
height={1100}
candles={candles}
positions={positions}
signals={signals}
></TradeChart>
</figure>
</div>
</div>
</>
)
}
export default BotRowDetails