docker files fixes from liaqat
This commit is contained in:
142
src/Managing.Infrastructure.Messengers/Discord/CommandHandler.cs
Normal file
142
src/Managing.Infrastructure.Messengers/Discord/CommandHandler.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Managing.Infrastructure.Messengers.Discord
|
||||
{
|
||||
public class CommandHandler
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly InteractionService _commands;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public CommandHandler(DiscordSocketClient client, InteractionService commands, IServiceProvider services)
|
||||
{
|
||||
_client = client;
|
||||
_commands = commands;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// add the public modules that inherit InteractionModuleBase<T> to the InteractionService
|
||||
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
|
||||
|
||||
// process the InteractionCreated payloads to execute Interactions commands
|
||||
_client.InteractionCreated += HandleInteraction;
|
||||
|
||||
// process the command execution results
|
||||
_commands.SlashCommandExecuted += SlashCommandExecuted;
|
||||
_commands.ContextCommandExecuted += ContextCommandExecuted;
|
||||
_commands.ComponentCommandExecuted += ComponentCommandExecuted;
|
||||
}
|
||||
|
||||
private Task ComponentCommandExecuted(ComponentCommandInfo arg1, IInteractionContext arg2, IResult arg3)
|
||||
{
|
||||
if (!arg3.IsSuccess)
|
||||
{
|
||||
switch (arg3.Error)
|
||||
{
|
||||
case InteractionCommandError.UnmetPrecondition:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.UnknownCommand:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.BadArgs:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.Exception:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.Unsuccessful:
|
||||
// implement
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task ContextCommandExecuted(ContextCommandInfo arg1, IInteractionContext arg2, IResult arg3)
|
||||
{
|
||||
if (!arg3.IsSuccess)
|
||||
{
|
||||
switch (arg3.Error)
|
||||
{
|
||||
case InteractionCommandError.UnmetPrecondition:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.UnknownCommand:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.BadArgs:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.Exception:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.Unsuccessful:
|
||||
// implement
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task SlashCommandExecuted(SlashCommandInfo arg1, IInteractionContext arg2, IResult arg3)
|
||||
{
|
||||
if (!arg3.IsSuccess)
|
||||
{
|
||||
switch (arg3.Error)
|
||||
{
|
||||
case InteractionCommandError.UnmetPrecondition:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.UnknownCommand:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.BadArgs:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.Exception:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.Unsuccessful:
|
||||
// implement
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task HandleInteraction(SocketInteraction arg)
|
||||
{
|
||||
try
|
||||
{
|
||||
// create an execution context that matches the generic type parameter of your InteractionModuleBase<T> modules
|
||||
var ctx = new SocketInteractionContext(_client, arg);
|
||||
await _commands.ExecuteCommandAsync(ctx, _services);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
// if a Slash Command execution fails it is most likely that the original interaction acknowledgement will persist. It is a good idea to delete the original
|
||||
// response, or at least let the user know that something went wrong during the command execution.
|
||||
if (arg.Type == InteractionType.ApplicationCommand)
|
||||
{
|
||||
await arg.GetOriginalResponseAsync().ContinueWith(async (msg) => await msg.Result.DeleteAsync());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Trading.Commands;
|
||||
using Managing.Common;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Trades;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Infrastructure.Messengers.Discord
|
||||
{
|
||||
public class DiscordCommands : ModuleBase
|
||||
{
|
||||
private readonly DiscordSettings _settings;
|
||||
private readonly IBacktester _backtester;
|
||||
private readonly string[] _separator = new[] { " " };
|
||||
private readonly string _messagePrefix = "Bro, ";
|
||||
private readonly ICommandHandler<OpenPositionRequest, Position> _openTradeCommandHandler;
|
||||
|
||||
public DiscordCommands(DiscordSettings settings,
|
||||
IBacktester backtester,
|
||||
ICommandHandler<OpenPositionRequest, Position> openTradeCommandHandler)
|
||||
{
|
||||
_settings = settings;
|
||||
_backtester = backtester;
|
||||
_openTradeCommandHandler = openTradeCommandHandler;
|
||||
}
|
||||
|
||||
|
||||
[Command("openposition")]
|
||||
public async Task OpenPosition([Remainder] string rest)
|
||||
{
|
||||
Typing();
|
||||
|
||||
var parameters = rest.Split(_separator, StringSplitOptions.None);
|
||||
|
||||
var exchange = MiscExtensions.ParseEnum<TradingExchanges>(parameters[0]);
|
||||
var ticker = parameters[1];
|
||||
var direction = MiscExtensions.ParseEnum<TradeDirection>(parameters[2]);
|
||||
var timeframe = MiscExtensions.ParseEnum<Timeframe>(parameters[3]);
|
||||
var expirationDate = DateTime.Now.AddMinutes(_settings.ButtonExpirationMinutes).ToString("G");
|
||||
|
||||
var builder = new ComponentBuilder().WithButton("Open Position",
|
||||
$"{Constants.DiscordButtonAction.OpenPosition}-{exchange}-{ticker}-{direction}-{timeframe}-{expirationDate}");
|
||||
await Context.Channel.SendMessageAsync(_messagePrefix + $"you can now click on this button to open a position on {ticker}"
|
||||
, components: builder.Build());
|
||||
}
|
||||
|
||||
[Command("open")]
|
||||
public async Task Open([Remainder] string rest)
|
||||
{
|
||||
await Context.Channel.SendMessageAsync("Let met few seconds to open a position");
|
||||
|
||||
Typing();
|
||||
try
|
||||
{
|
||||
var parameters = rest.Split(_separator, StringSplitOptions.None);
|
||||
var accountName = parameters[0];
|
||||
var ticker = MiscExtensions.ParseEnum<Ticker>(parameters[1]);
|
||||
var direction = MiscExtensions.ParseEnum<TradeDirection>(parameters[2]);
|
||||
var riskLevel = MiscExtensions.ParseEnum<RiskLevel>(parameters[3]);
|
||||
var timeframe = MiscExtensions.ParseEnum<Timeframe>(parameters[4]);
|
||||
decimal? stopLoss = parameters[5] != null ? Convert.ToDecimal(parameters[5]) : null;
|
||||
decimal? takeProfit = parameters[6] != null ? Convert.ToDecimal(parameters[6]) : null;
|
||||
|
||||
var moneymanagement = new MoneyManagement
|
||||
{
|
||||
BalanceAtRisk = 5,
|
||||
StopLoss = stopLoss.GetValueOrDefault(),
|
||||
TakeProfit = takeProfit.GetValueOrDefault(),
|
||||
|
||||
};
|
||||
var tradeCommand = new OpenPositionRequest(accountName, moneymanagement, direction, ticker, PositionInitiator.User, DateTime.UtcNow);
|
||||
var result = await _openTradeCommandHandler.Handle(tradeCommand);
|
||||
var builder = new ComponentBuilder().WithButton("Close Position", $"{Constants.DiscordButtonAction.ClosePosition}|{result.Open.ExchangeOrderId}");
|
||||
await Context.Channel.SendMessageAsync(MessengerHelpers.GetPositionMessage(result),
|
||||
components: builder.Build());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Context.Channel.SendMessageAsync($"Something weird happen bro, {ex.Message}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Command("bot")]
|
||||
public async Task Bot([Remainder] string rest)
|
||||
{
|
||||
Typing();
|
||||
|
||||
if (string.Equals(rest, "enable", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_settings.BotEnabled = true;
|
||||
}
|
||||
if (string.Equals(rest, "disable", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_settings.BotEnabled = false;
|
||||
}
|
||||
await Context.Channel.SendMessageAsync("Bot is "
|
||||
+ (_settings.BotEnabled ? "enabled" : "disabled"));
|
||||
}
|
||||
|
||||
//[Command("backtest")]
|
||||
//public async Task Backtest([Remainder] string rest)
|
||||
//{
|
||||
// Typing();
|
||||
|
||||
// var parameters = rest.Split(_separator, StringSplitOptions.None);
|
||||
// var ticker = MiscExtensions.ParseEnum<Ticker>(parameters[0]);
|
||||
// var exchange = MiscExtensions.ParseEnum<TradingExchanges>(parameters[1]);
|
||||
// var timeframe = MiscExtensions.ParseEnum<Timeframe>(parameters[2]);
|
||||
// var days = parameters[3];
|
||||
// var scenario = new Scenario("ScalpingScenario");
|
||||
// var strategy = ScenarioHelpers.BuildStrategy(StrategyType.RsiDivergence, timeframe, "RsiDiv", period: 14);
|
||||
// scenario.AddStrategy(strategy);
|
||||
// var account = new Account() { Exchange = exchange };
|
||||
|
||||
// var backtestResult = _backtester.RunScalpingBotBacktest(account, ticker, scenario, timeframe, Convert.ToDouble(days), 1000);
|
||||
// await Context.Channel.SendMessageAsync(backtestResult.GetStringReport());
|
||||
//}
|
||||
|
||||
[Command("run")]
|
||||
public async Task Run([Remainder] string rest)
|
||||
{
|
||||
Typing();
|
||||
var parameters = rest.Split(_separator, StringSplitOptions.None);
|
||||
var botType = MiscExtensions.ParseEnum<BotType>(parameters[0]);
|
||||
var accountName = MiscExtensions.ParseEnum<string>(parameters[1]);
|
||||
var botName = parameters[2];
|
||||
var ticker = MiscExtensions.ParseEnum<Ticker>(parameters[3]);
|
||||
var timeframe = MiscExtensions.ParseEnum<Timeframe>(parameters[4]);
|
||||
|
||||
var message = _messagePrefix;
|
||||
|
||||
// TODO : Fix remove mediator
|
||||
//var result = await _mediator.Send(new StartBotCommand(botType,
|
||||
// botName,
|
||||
// ticker,
|
||||
// "scenario",
|
||||
// timeframe,
|
||||
// accountName,
|
||||
// true));
|
||||
|
||||
var result = "down";
|
||||
message += $"I tried to start the bot called {botName}, his status is now {result}. ";
|
||||
|
||||
if (result == "Up")
|
||||
{
|
||||
await Context.Message.AddReactionAsync(new Emoji("👌"));
|
||||
message += $"this bot gonna watch {ticker} on {timeframe} timeframe.";
|
||||
}
|
||||
else
|
||||
{
|
||||
await Context.Message.AddReactionAsync(new Emoji("😒"));
|
||||
message += $"something wrong happen, the bot status is down";
|
||||
}
|
||||
|
||||
await Context.Channel.SendMessageAsync(message);
|
||||
}
|
||||
|
||||
[Command("stop")]
|
||||
public async Task Stop([Remainder] string rest)
|
||||
{
|
||||
Typing();
|
||||
var parameters = rest.Split(_separator, StringSplitOptions.None);
|
||||
var botType = MiscExtensions.ParseEnum<BotType>(parameters[0]);
|
||||
var botName = parameters[1];
|
||||
var result = "down";
|
||||
//var result = await _mediator.Send(new StopBotCommand(botType, botName));
|
||||
|
||||
await Context.Channel.SendMessageAsync(_messagePrefix + $"the {botType} called {botName} is now {result}");
|
||||
}
|
||||
|
||||
[Command("hi")]
|
||||
public async Task Hi([Remainder] string rest)
|
||||
{
|
||||
Typing();
|
||||
await Context.Channel.SendMessageAsync("I don't understand yet : " + rest);
|
||||
}
|
||||
|
||||
[Command("help")]
|
||||
public async Task Help([Remainder] string rest)
|
||||
{
|
||||
Typing();
|
||||
|
||||
var message = _messagePrefix;
|
||||
switch (rest)
|
||||
{
|
||||
case "backtest":
|
||||
message += "to run a backtest you should use this pattern : !trader backtest ticker exchange timeframe startFromDays";
|
||||
break;
|
||||
case "run":
|
||||
message += "to run a bot you should use this pattern : !trader run botType botName ticker timeframe";
|
||||
break;
|
||||
case "stop":
|
||||
message += "to stop a bot you should use this pattern : !trader stop botType botName";
|
||||
break;
|
||||
case "botType":
|
||||
message += $"the bot type available are : {BotType.FlippingBot}, {BotType.ScalpingBot}";
|
||||
break;
|
||||
case "timeframe":
|
||||
message += $"the bot can currently handle only those timeframes : " +
|
||||
$"{Timeframe.FifteenMinutes}, {Timeframe.ThirtyMinutes}, {Timeframe.OneDay}";
|
||||
break;
|
||||
case "risklevel":
|
||||
message += $"the bot can currently handle only those riskLevel : " +
|
||||
$"{RiskLevel.High}";
|
||||
break;
|
||||
default:
|
||||
message += "I don't no the command";
|
||||
break;
|
||||
}
|
||||
|
||||
await Context.Channel.SendMessageAsync(message);
|
||||
}
|
||||
|
||||
private async void Typing()
|
||||
{
|
||||
await Context.Channel.TriggerTypingAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using Discord;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
|
||||
namespace Managing.Infrastructure.Messengers.Discord;
|
||||
|
||||
public static class DiscordHelpers
|
||||
{
|
||||
public static Embed GetTradersEmbed(List<Trader> traders, string title)
|
||||
{
|
||||
var fields = new List<EmbedFieldBuilder>();
|
||||
|
||||
traders = traders.OrderByDescending(t => t.Winrate).ToList();
|
||||
foreach (var trader in traders)
|
||||
{
|
||||
fields.Add(new EmbedFieldBuilder
|
||||
{
|
||||
Name = $"{GetExplorerUrl(trader.Address)}",
|
||||
Value = $"Avg Win / Avg Loss / Winrate / ROI \n {trader.AverageWin:#.##}$ / {trader.AverageLoss:#.##}$ / {trader.Winrate}% / {Convert.ToDecimal(trader.Roi) * 100:#.##}%",
|
||||
});
|
||||
}
|
||||
|
||||
var embed = new EmbedBuilder
|
||||
{
|
||||
Author = new EmbedAuthorBuilder() { Name = "GMX" },
|
||||
Title = $"{title} {DateTime.UtcNow:d}",
|
||||
Color = Color.Gold,
|
||||
Fields = fields,
|
||||
}.Build();
|
||||
|
||||
return embed;
|
||||
}
|
||||
|
||||
public static Embed GetEmbed(string address, string title, List<EmbedFieldBuilder> fields, Color color)
|
||||
{
|
||||
return new EmbedBuilder
|
||||
{
|
||||
Author = new EmbedAuthorBuilder() { Name = address },
|
||||
Title = title,
|
||||
Color = color,
|
||||
Fields = fields,
|
||||
Url = GetExplorerUrl(address)
|
||||
}.Build();
|
||||
}
|
||||
|
||||
private static string GetExplorerUrl(string key)
|
||||
{
|
||||
return $"https://www.tradao.xyz/#/user/{key}/1";
|
||||
}
|
||||
|
||||
internal static Embed GetTradesEmbed(List<Trade> trades, string title)
|
||||
{
|
||||
var fields = new List<EmbedFieldBuilder>();
|
||||
|
||||
foreach (var trade in trades)
|
||||
{
|
||||
fields.Add(new EmbedFieldBuilder
|
||||
{
|
||||
Name = $"{GetExplorerUrl(trade.ExchangeOrderId)}",
|
||||
Value = $"Side / Ticker / Open / Qty / Leverage / LiqPrice \n {trade.Direction} / {trade.Ticker} / {trade.Price:#.##}$ / {trade.Quantity:#.##}$ / x{trade.Leverage:#.##} / {Convert.ToDecimal(trade.Message):#.##}$",
|
||||
});
|
||||
}
|
||||
|
||||
fields.Add(new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Summary",
|
||||
Value = $"Long / Short / \n {trades.Count(t => t.Direction == Common.Enums.TradeDirection.Long)} / {trades.Count(t => t.Direction == Common.Enums.TradeDirection.Short)}"
|
||||
});
|
||||
|
||||
var embed = new EmbedBuilder
|
||||
{
|
||||
Author = new EmbedAuthorBuilder() { Name = "GMX" },
|
||||
Title = $"{title} {DateTime.UtcNow:d}",
|
||||
Color = Color.DarkBlue,
|
||||
Fields = fields,
|
||||
}.Build();
|
||||
|
||||
return embed;
|
||||
}
|
||||
}
|
||||
557
src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs
Normal file
557
src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs
Normal file
@@ -0,0 +1,557 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.Net;
|
||||
using Discord.WebSocket;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Trading;
|
||||
using Managing.Application.Trading.Commands;
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
using Managing.Common;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Infrastructure.Messengers.Discord
|
||||
{
|
||||
public class DiscordService : IHostedService, IDisposable, IDiscordService
|
||||
{
|
||||
private const string _separator = "|";
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly CommandService _commandService;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IExchangeService _exchangeService;
|
||||
private readonly IMoneyManagementService _moneyManagementService;
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly ITradingService _tradingService;
|
||||
private readonly IStatisticService _statisticService;
|
||||
|
||||
private readonly DiscordSettings _settings;
|
||||
private readonly ILogger<DiscordService> _logger;
|
||||
|
||||
private readonly string _explorerUrl = "";
|
||||
|
||||
public DiscordService(DiscordSocketClient client,
|
||||
CommandService commandService,
|
||||
IServiceProvider services,
|
||||
DiscordSettings settings, ILogger<DiscordService> logger)
|
||||
{
|
||||
_client = client;
|
||||
_commandService = commandService;
|
||||
_services = services;
|
||||
_settings = settings;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
#region Setup
|
||||
// The hosted service has started
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_settings.HandleUserAction)
|
||||
{
|
||||
_client.ButtonExecuted += ButtonHandler;
|
||||
_commandService.CommandExecuted += CommandExecuted;
|
||||
_client.SlashCommandExecuted += SlashCommandHandler;
|
||||
}
|
||||
|
||||
_client.Ready += ClientReady;
|
||||
_client.Log += Log;
|
||||
_commandService.Log += Log;
|
||||
// look for classes implementing ModuleBase to load commands from
|
||||
await _commandService.AddModulesAsync(GetType().Assembly, _services);
|
||||
// log in to Discord, using the provided token
|
||||
await _client.LoginAsync(TokenType.Bot, _settings.Token);
|
||||
// start bot
|
||||
await _client.StartAsync();
|
||||
}
|
||||
|
||||
private async Task SlashCommandHandler(SocketSlashCommand command)
|
||||
{
|
||||
await command.DeferAsync();
|
||||
|
||||
// Let's add a switch statement for the command name so we can handle multiple commands in one event.
|
||||
switch (command.Data.Name)
|
||||
{
|
||||
case Constants.DiscordSlashCommand.Leaderboard:
|
||||
await SlashCommands.HandleLeaderboardCommand(_services, command);
|
||||
break;
|
||||
case Constants.DiscordSlashCommand.Noobiesboard:
|
||||
await SlashCommands.HandleNoobiesboardCommand(_services, command);
|
||||
break;
|
||||
case Constants.DiscordSlashCommand.LeaderboardPosition:
|
||||
await SlashCommands.HandleLeadboardPositionCommand(_services, command);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClientReady()
|
||||
{
|
||||
// set status to online
|
||||
await _client.SetStatusAsync(UserStatus.Online);
|
||||
// Discord started as a game chat service, so it has the option to show what games you are playing
|
||||
// Here the bot will display "Playing dead" while listening
|
||||
await _client.SetGameAsync(_settings.BotActivity, "https://moon.com", ActivityType.Playing);
|
||||
|
||||
if (!_settings.HandleUserAction) return;
|
||||
|
||||
List<ApplicationCommandProperties> applicationCommandProperties = new();
|
||||
try
|
||||
{
|
||||
var leaderBoardCommand = new SlashCommandBuilder();
|
||||
leaderBoardCommand.WithName(Constants.DiscordSlashCommand.Leaderboard);
|
||||
leaderBoardCommand.WithDescription("Shows the last leaderboard");
|
||||
applicationCommandProperties.Add(leaderBoardCommand.Build());
|
||||
|
||||
var leadboardPositionsCommand = new SlashCommandBuilder();
|
||||
leadboardPositionsCommand.WithName(Constants.DiscordSlashCommand.LeaderboardPosition);
|
||||
leadboardPositionsCommand.WithDescription("Shows the opened position of the leaderboard");
|
||||
applicationCommandProperties.Add(leadboardPositionsCommand.Build());
|
||||
|
||||
var noobiesboardCommand = new SlashCommandBuilder();
|
||||
noobiesboardCommand.WithName(Constants.DiscordSlashCommand.Noobiesboard);
|
||||
noobiesboardCommand.WithDescription("Shows the last Noobies board");
|
||||
applicationCommandProperties.Add(noobiesboardCommand.Build());
|
||||
|
||||
|
||||
await _client.BulkOverwriteGlobalApplicationCommandsAsync(applicationCommandProperties.ToArray());
|
||||
}
|
||||
catch (ApplicationCommandException exception)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(exception, Formatting.Indented);
|
||||
Console.WriteLine(json);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ApplicationCommandProperties> GetSlashCommands()
|
||||
{
|
||||
List<ApplicationCommandProperties> commands = new();
|
||||
|
||||
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
// logging
|
||||
private async Task Log(LogMessage arg)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
_logger.LogInformation(arg.ToString());
|
||||
});
|
||||
}
|
||||
|
||||
private async Task CommandExecuted(Optional<CommandInfo> command, ICommandContext context, IResult result)
|
||||
{
|
||||
// if a command isn't found
|
||||
if (!command.IsSpecified)
|
||||
{
|
||||
await context.Message.AddReactionAsync(new Emoji("🤨")); // eyebrow raised emoji
|
||||
return;
|
||||
}
|
||||
|
||||
// log failure to the console
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
await Log(new LogMessage(LogSeverity.Error, nameof(CommandExecuted), $"Error: {result.ErrorReason}"));
|
||||
return;
|
||||
}
|
||||
// react to message
|
||||
await context.Message.AddReactionAsync(new Emoji("🤖")); // robot emoji
|
||||
}
|
||||
|
||||
// the hosted service is stopping
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _client.SetGameAsync(null);
|
||||
await _client.SetStatusAsync(UserStatus.Offline);
|
||||
await _client.StopAsync();
|
||||
_client.Log -= Log;
|
||||
_client.Ready -= ClientReady;
|
||||
_commandService.Log -= Log;
|
||||
_commandService.CommandExecuted -= CommandExecuted;
|
||||
_client.ButtonExecuted -= ButtonHandler;
|
||||
_client.SlashCommandExecuted -= SlashCommandHandler;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_client?.Dispose();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region In
|
||||
public async Task ButtonHandler(SocketMessageComponent component)
|
||||
{
|
||||
var parameters = component.Data.CustomId.Split(new[] { _separator }, StringSplitOptions.None);
|
||||
var command = parameters[0];
|
||||
|
||||
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");
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case Constants.DiscordButtonAction.OpenPosition:
|
||||
await OpenPosition(component, parameters);
|
||||
break;
|
||||
case Constants.DiscordButtonAction.ClosePosition:
|
||||
await ClosePosition(component, parameters);
|
||||
break;
|
||||
case Constants.DiscordButtonAction.CopyPosition:
|
||||
await CopyPosition(component, parameters);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CopyPosition(SocketMessageComponent component, string[] parameters)
|
||||
{
|
||||
await component.Channel.SendMessageAsync("Let met few seconds to copy this position");
|
||||
await component.Channel.TriggerTypingAsync();
|
||||
|
||||
var json = MiscExtensions.Base64Decode(parameters[1]);
|
||||
var trade = JsonConvert.DeserializeObject<CopyTradeData>(json);
|
||||
await OpenPosition(component, trade.AccountName, trade.MoneyManagementName, PositionInitiator.CopyTrading, trade.Ticker, trade.Direction, Timeframe.FifteenMinutes, DateTime.Now.AddMinutes(trade.ExpirationMinute), true, trade.Leverage); ;
|
||||
}
|
||||
|
||||
public async Task OpenPosition(SocketMessageComponent component, string[] parameters)
|
||||
{
|
||||
await component.Channel.SendMessageAsync("Let met few seconds to open a position");
|
||||
await component.Channel.TriggerTypingAsync();
|
||||
|
||||
var accountName = MiscExtensions.ParseEnum<string>(parameters[1]);
|
||||
var ticker = MiscExtensions.ParseEnum<Ticker>(parameters[2]);
|
||||
var direction = MiscExtensions.ParseEnum<TradeDirection>(parameters[3]);
|
||||
var timeframe = MiscExtensions.ParseEnum<Timeframe>(parameters[4]);
|
||||
var moneyManagementName = parameters[5];
|
||||
var expiration = DateTime.Parse(parameters[6]);
|
||||
|
||||
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)
|
||||
{
|
||||
if (DateTime.Now > expiration)
|
||||
{
|
||||
await component.Channel.SendMessageAsync("Sorry I can't open position because you tried to click on a expired button.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var exchangeService = (IExchangeService)_services.GetService(typeof(IExchangeService));
|
||||
var moneyManagementService = (IMoneyManagementService)_services.GetService(typeof(IMoneyManagementService));
|
||||
var accountService = (IAccountService)_services.GetService(typeof(IAccountService));
|
||||
var tradingService = (ITradingService)_services.GetService(typeof(ITradingService));
|
||||
|
||||
var tradeCommand = new OpenPositionRequest(
|
||||
accountName,
|
||||
await moneyManagementService.GetMoneyMangement(moneyManagement),
|
||||
direction,
|
||||
ticker,
|
||||
initiator,
|
||||
DateTime.UtcNow,
|
||||
ignoreSLTP: ignoreSLTP);
|
||||
var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
|
||||
.Handle(tradeCommand);
|
||||
|
||||
var builder = new ComponentBuilder().WithButton("Close Position", $"{Constants.DiscordButtonAction.ClosePosition}{_separator}{position.Identifier}");
|
||||
|
||||
await component.Channel.SendMessageAsync(MessengerHelpers.GetPositionMessage(position),
|
||||
components: builder.Build());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private string GetClosingPositionMessage(Position position)
|
||||
{
|
||||
return $"Closing : {position.OriginDirection} {position.Open.Ticker} \n" +
|
||||
$"Open Price : {position.Open.Price} \n" +
|
||||
$"Closing Price : {position.Open.Price} \n" +
|
||||
$"Quantity :{position.Open.Quantity} \n" +
|
||||
$"PNL : {position.ProfitAndLoss.Net} $";
|
||||
}
|
||||
|
||||
private async Task ClosePosition(SocketMessageComponent component, string[] parameters)
|
||||
{
|
||||
var exchangeService = (IExchangeService)_services.GetService(typeof(IExchangeService));
|
||||
var accountService = (IAccountService)_services.GetService(typeof(IAccountService));
|
||||
var tradingService = (ITradingService)_services.GetService(typeof(ITradingService));
|
||||
|
||||
await component.RespondAsync("Alright, let met few seconds to close this position");
|
||||
var position = _tradingService.GetPositionByIdentifier(parameters[1]);
|
||||
var command = new ClosePositionCommand(position);
|
||||
var result = await new ClosePositionCommandHandler(exchangeService, accountService, tradingService).Handle(command);
|
||||
var fields = new List<EmbedFieldBuilder>()
|
||||
{
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Direction",
|
||||
Value = position.OriginDirection,
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Open Price",
|
||||
Value = $"{position.Open.Price:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Quantity",
|
||||
Value = $"{position.Open.Quantity:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Pnl",
|
||||
Value = $"{position.ProfitAndLoss.Net:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
};
|
||||
var embed = DiscordHelpers.GetEmbed(position.AccountName, $"Position status is now {result.Status}", fields, position.ProfitAndLoss.Net > 0 ? Color.Green : Color.Red);
|
||||
await component.Channel.SendMessageAsync("", embed: embed);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Out
|
||||
public async Task SendSignal(string message)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
||||
var builder = new ComponentBuilder().WithButton("Open Position", $"openposition{_separator}");
|
||||
await channel.SendMessageAsync(message, components: builder.Build());
|
||||
}
|
||||
|
||||
public async Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe)
|
||||
{
|
||||
var expirationDate = DateTime.Now.AddMinutes(_settings.ButtonExpirationMinutes).ToString("G");
|
||||
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
||||
var builder = new ComponentBuilder().WithButton("Open Position", $"{Constants.DiscordButtonAction.OpenPosition}{_separator}{exchange}{_separator}{ticker}{_separator}{direction}{_separator}{timeframe}{_separator}{expirationDate}");
|
||||
await channel.SendMessageAsync(message, components: builder.Build());
|
||||
}
|
||||
|
||||
public async Task SendIncreasePosition(string address, Trade trade, string copyAccountName, Trade? oldTrade = null)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel;
|
||||
|
||||
|
||||
var fields = new List<EmbedFieldBuilder>()
|
||||
{
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Last size update",
|
||||
Value = $"{trade.Date:s}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Entry Price",
|
||||
Value = $"{trade.Price:#.##} $",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Quantity",
|
||||
Value = $"{trade.Quantity / trade.Leverage:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Leverage",
|
||||
Value = $"x{trade.Leverage:#.##}",
|
||||
IsInline = true
|
||||
}
|
||||
};
|
||||
|
||||
if (oldTrade != null)
|
||||
{
|
||||
fields.Add(new EmbedFieldBuilder { Name = "Increasy by", Value = $"{(trade.Quantity - oldTrade.Quantity) / trade.Leverage:#.##} $" });
|
||||
}
|
||||
|
||||
var titlePrefix = oldTrade != null ? "Increase " : "";
|
||||
|
||||
var builder = new ComponentBuilder();
|
||||
|
||||
var moneyManagementService = (IMoneyManagementService)_services.GetService(typeof(IMoneyManagementService));
|
||||
var moneyManagements = moneyManagementService.GetMoneyMangements();
|
||||
|
||||
foreach (var mm in moneyManagements)
|
||||
{
|
||||
var data = new CopyTradeData
|
||||
{
|
||||
Direction = trade.Direction,
|
||||
Ticker = trade.Ticker,
|
||||
AccountName = copyAccountName,
|
||||
ExpirationMinute = 10,
|
||||
Leverage = trade.Leverage,
|
||||
};
|
||||
data.MoneyManagementName = mm.Name;
|
||||
var encodedData = MiscExtensions.Base64Encode(JsonConvert.SerializeObject(data));
|
||||
|
||||
if (oldTrade == null)
|
||||
{
|
||||
builder.WithButton($"Copy with {mm.Name}", $"{Constants.DiscordButtonAction.CopyPosition}{_separator}{encodedData}");
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
await channel.SendMessageAsync("", components: builder.Build(), embed: embed);
|
||||
}
|
||||
|
||||
public async Task SendPosition(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe)
|
||||
{
|
||||
var expirationDate = DateTime.Now.AddMinutes(_settings.ButtonExpirationMinutes).ToString("G");
|
||||
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
||||
var builder = new ComponentBuilder().WithButton("Open Position", $"{Constants.DiscordButtonAction.OpenPosition}{_separator}{exchange}{_separator}{ticker}{_separator}{direction}{_separator}{timeframe}{_separator}{expirationDate}");
|
||||
await channel.SendMessageAsync(message, components: builder.Build());
|
||||
}
|
||||
|
||||
public async Task SendMessage(string message)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.TradesChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync(message);
|
||||
}
|
||||
|
||||
public async Task SendClosingPosition(Position position)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.TradesChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync(GetClosingPositionMessage(position));
|
||||
}
|
||||
|
||||
public async Task SendTradeMessage(string message, bool isBadBehavior = false)
|
||||
{
|
||||
var channel = _client.GetChannel(isBadBehavior ? _settings.TroublesChannelId : _settings.TradesChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync(message);
|
||||
}
|
||||
|
||||
public async Task SendClosedPosition(string address, Trade oldTrade)
|
||||
{
|
||||
var fields = new List<EmbedFieldBuilder>()
|
||||
{
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Last size update",
|
||||
Value = $"{oldTrade.Date:s}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Entry Price",
|
||||
Value = $"{oldTrade.Price:#.##} $",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Quantity",
|
||||
Value = $"{oldTrade.Quantity / oldTrade.Leverage:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Leverage",
|
||||
Value = $"x{oldTrade.Leverage:#.##}",
|
||||
IsInline = true
|
||||
}
|
||||
};
|
||||
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"Closed {oldTrade.Direction} {oldTrade.Ticker}", fields, oldTrade.Direction == TradeDirection.Long ? Color.DarkGreen : Color.DarkRed);
|
||||
var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync("", embed: embed);
|
||||
}
|
||||
|
||||
|
||||
public async Task SendDecreasePosition(string address, Trade trade, decimal decreaseAmount)
|
||||
{
|
||||
var fields = new List<EmbedFieldBuilder>()
|
||||
{
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Last size update",
|
||||
Value = $"{trade.Date:s}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Entry Price",
|
||||
Value = $"{trade.Price:#.##} $",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Quantity",
|
||||
Value = $"{trade.Quantity / trade.Leverage:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Leverage",
|
||||
Value = $"x{trade.Leverage:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Decrease amount",
|
||||
Value = $"{decreaseAmount:#.##} $"
|
||||
}
|
||||
};
|
||||
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"Decrease {trade.Direction} {trade.Ticker}", fields, Color.Blue);
|
||||
var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync("", embed: embed);
|
||||
}
|
||||
|
||||
|
||||
public async Task SendPosition(Position position)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.TradesChannelId) as IMessageChannel;
|
||||
var builder = new ComponentBuilder().WithButton("Close Position", $"{Constants.DiscordButtonAction.ClosePosition}{_separator}{position.Open.ExchangeOrderId}");
|
||||
await channel.SendMessageAsync(MessengerHelpers.GetPositionMessage(position), components: builder.Build());
|
||||
}
|
||||
|
||||
public async Task SendBestTraders(List<Trader> traders)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.LeaderboardChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync("", embed: DiscordHelpers.GetTradersEmbed(traders, "Leaderboard"));
|
||||
|
||||
}
|
||||
|
||||
public async Task SendBadTraders(List<Trader> traders)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.NoobiesboardChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync("", embed: DiscordHelpers.GetTradersEmbed(traders, "Noobiesboard"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class CopyTradeData
|
||||
{
|
||||
[JsonProperty(PropertyName = "D")]
|
||||
public TradeDirection Direction { get; set; }
|
||||
[JsonProperty(PropertyName = "T")]
|
||||
public Ticker Ticker { get; set; }
|
||||
[JsonProperty(PropertyName = "A")]
|
||||
public string AccountName { get; set; }
|
||||
[JsonProperty(PropertyName = "E")]
|
||||
public int ExpirationMinute { get; set; }
|
||||
[JsonProperty(PropertyName = "L")]
|
||||
public decimal Leverage { get; set; }
|
||||
[JsonProperty(PropertyName = "M")]
|
||||
public string MoneyManagementName { get; internal set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Managing.Infrastructure.Messengers.Discord
|
||||
{
|
||||
public class DiscordSettings
|
||||
{
|
||||
public DiscordSettings(IConfiguration config)
|
||||
{
|
||||
Token = config.GetValue<string>("Discord:TokenId");
|
||||
SignalChannelId = config.GetValue<ulong>("Discord:SignalChannelId");
|
||||
CopyTradingChannelId = config.GetValue<ulong>("Discord:CopyTradingChannelId");
|
||||
TradesChannelId = config.GetValue<ulong>("Discord:TradesChannelId");
|
||||
TroublesChannelId = config.GetValue<ulong>("Discord:TroublesChannelId");
|
||||
RequestsChannelId = config.GetValue<ulong>("Discord:RequestsChannelId");
|
||||
LeaderboardChannelId = config.GetValue<ulong>("Discord:LeaderboardChannelId");
|
||||
NoobiesboardChannelId = config.GetValue<ulong>("Discord:NoobiesboardChannelId");
|
||||
ButtonExpirationMinutes = config.GetValue<int>("Discord:ButtonExpirationMinutes");
|
||||
HandleUserAction = config.GetValue<bool>("Discord:HandleUserAction");
|
||||
BotActivity = config.GetValue<string>("Discord:BotActivity");
|
||||
BotEnabled = true;
|
||||
}
|
||||
|
||||
public int ButtonExpirationMinutes { get; set; }
|
||||
public bool HandleUserAction { get; }
|
||||
public string BotActivity { get; }
|
||||
public string Token { get; }
|
||||
public ulong SignalChannelId { get; }
|
||||
public ulong CopyTradingChannelId { get; }
|
||||
public ulong TradesChannelId { get; }
|
||||
public ulong TroublesChannelId { get; }
|
||||
public ulong RequestsChannelId { get; }
|
||||
public bool BotEnabled { get; set; }
|
||||
public ulong LeaderboardChannelId { get; set; }
|
||||
public ulong NoobiesboardChannelId { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Managing.Domain.Trades;
|
||||
|
||||
namespace Managing.Infrastructure.Messengers.Discord;
|
||||
|
||||
public class MessengerHelpers
|
||||
{
|
||||
public static string GetPositionMessage(Position position)
|
||||
{
|
||||
return $"Position : {position.OriginDirection} {position.Open.Ticker} \n" +
|
||||
$"Open Price : {position.Open.Price} \n" +
|
||||
$"Quantity : {position.Open.Quantity}. \n" +
|
||||
$"SL : {position.StopLoss.Price} \n" +
|
||||
$"TP : {position.TakeProfit1.Price}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Discord.WebSocket;
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
|
||||
namespace Managing.Infrastructure.Messengers.Discord;
|
||||
|
||||
public static class SlashCommands
|
||||
{
|
||||
public static async Task HandleLeaderboardCommand(IServiceProvider service, SocketSlashCommand command)
|
||||
{
|
||||
var statisticService = (IStatisticService)service.GetService(typeof(IStatisticService));
|
||||
var traders = statisticService.GetBestTraders();
|
||||
await command.FollowupAsync(embed: DiscordHelpers.GetTradersEmbed(traders, "Leaderboard"), ephemeral: true);
|
||||
}
|
||||
|
||||
public static async Task HandleNoobiesboardCommand(IServiceProvider service, SocketSlashCommand command)
|
||||
{
|
||||
var statisticService = (IStatisticService)service.GetService(typeof(IStatisticService));
|
||||
var traders = statisticService.GetBadTraders();
|
||||
await command.FollowupAsync(embed: DiscordHelpers.GetTradersEmbed(traders, "Noobiesboard"), ephemeral: true);
|
||||
}
|
||||
|
||||
public static async Task HandleLeadboardPositionCommand(IServiceProvider service, SocketSlashCommand command)
|
||||
{
|
||||
var statisticService = (IStatisticService)service.GetService(typeof(IStatisticService));
|
||||
var trades = await statisticService.GetLeadboardPositons();
|
||||
await command.FollowupAsync(embed: DiscordHelpers.GetTradesEmbed(trades, "Leaderboard Open position"), ephemeral: true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" Version="3.12.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Managing.Application\Managing.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user