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:
@@ -48,6 +48,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
}
|
||||
|
||||
#region Setup
|
||||
|
||||
// The hosted service has started
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -85,7 +86,9 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
case Constants.DiscordSlashCommand.LeaderboardPosition:
|
||||
await SlashCommands.HandleLeadboardPositionCommand(_services, command);
|
||||
break;
|
||||
|
||||
case Constants.DiscordSlashCommand.FundingRates:
|
||||
await SlashCommands.HandleFundingRateCommand(_services, command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +120,10 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
noobiesboardCommand.WithDescription("Shows the last Noobies board");
|
||||
applicationCommandProperties.Add(noobiesboardCommand.Build());
|
||||
|
||||
var fundingRatesCommand = new SlashCommandBuilder();
|
||||
fundingRatesCommand.WithName(Constants.DiscordSlashCommand.FundingRates);
|
||||
fundingRatesCommand.WithDescription("Shows the last funding rates");
|
||||
applicationCommandProperties.Add(fundingRatesCommand.Build());
|
||||
|
||||
await _client.BulkOverwriteGlobalApplicationCommandsAsync(applicationCommandProperties.ToArray());
|
||||
}
|
||||
@@ -131,7 +138,6 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
{
|
||||
List<ApplicationCommandProperties> commands = new();
|
||||
|
||||
|
||||
|
||||
return commands;
|
||||
}
|
||||
@@ -139,10 +145,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
// logging
|
||||
private async Task Log(LogMessage arg)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
_logger.LogInformation(arg.ToString());
|
||||
});
|
||||
await Task.Run(() => { _logger.LogInformation(arg.ToString()); });
|
||||
}
|
||||
|
||||
private async Task CommandExecuted(Optional<CommandInfo> command, ICommandContext context, IResult result)
|
||||
@@ -160,6 +163,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
await Log(new LogMessage(LogSeverity.Error, nameof(CommandExecuted), $"Error: {result.ErrorReason}"));
|
||||
return;
|
||||
}
|
||||
|
||||
// react to message
|
||||
await context.Message.AddReactionAsync(new Emoji("🤖")); // robot emoji
|
||||
}
|
||||
@@ -177,13 +181,16 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
_client.ButtonExecuted -= ButtonHandler;
|
||||
_client.SlashCommandExecuted -= SlashCommandHandler;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client?.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region In
|
||||
|
||||
public async Task ButtonHandler(SocketMessageComponent component)
|
||||
{
|
||||
var parameters = component.Data.CustomId.Split(new[] { _separator }, StringSplitOptions.None);
|
||||
@@ -191,7 +198,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
|
||||
if (component.User.GlobalName != "crypto_saitama")
|
||||
{
|
||||
await component.Channel.SendMessageAsync("Sorry bro, this feature is not accessible for you.. Do not hesitate to send me approx. 456 121 $ and i give you full access");
|
||||
await component.Channel.SendMessageAsync(
|
||||
"Sorry bro, this feature is not accessible for you.. Do not hesitate to send me approx. 456 121 $ and i give you full access");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -219,7 +227,10 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
|
||||
var json = MiscExtensions.Base64Decode(parameters[1]);
|
||||
var trade = JsonConvert.DeserializeObject<CopyTradeData>(json);
|
||||
await OpenPosition(component, trade.AccountName, trade.MoneyManagementName, PositionInitiator.CopyTrading, trade.Ticker, trade.Direction, Timeframe.FifteenMinutes, DateTime.Now.AddMinutes(trade.ExpirationMinute), true, trade.Leverage); ;
|
||||
await OpenPosition(component, trade.AccountName, trade.MoneyManagementName, PositionInitiator.CopyTrading,
|
||||
trade.Ticker, trade.Direction, Timeframe.FifteenMinutes,
|
||||
DateTime.Now.AddMinutes(trade.ExpirationMinute), true, trade.Leverage);
|
||||
;
|
||||
}
|
||||
|
||||
public async Task OpenPosition(SocketMessageComponent component, string[] parameters)
|
||||
@@ -234,19 +245,24 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
var moneyManagementName = parameters[5];
|
||||
var expiration = DateTime.Parse(parameters[6]);
|
||||
|
||||
await OpenPosition(component, accountName, moneyManagementName, PositionInitiator.User, ticker, direction, timeframe, expiration, false);
|
||||
await OpenPosition(component, accountName, moneyManagementName, PositionInitiator.User, ticker, direction,
|
||||
timeframe, expiration, false);
|
||||
}
|
||||
|
||||
private async Task OpenPosition(SocketMessageComponent component, string accountName, string moneyManagement, PositionInitiator initiator, Ticker ticker, TradeDirection direction, Timeframe timeframe, DateTime expiration, bool ignoreSLTP, decimal? leverage = null)
|
||||
private async Task OpenPosition(SocketMessageComponent component, string accountName, string moneyManagement,
|
||||
PositionInitiator initiator, Ticker ticker, TradeDirection direction, Timeframe timeframe,
|
||||
DateTime expiration, bool ignoreSLTP, decimal? leverage = null)
|
||||
{
|
||||
if (DateTime.Now > expiration)
|
||||
{
|
||||
await component.Channel.SendMessageAsync("Sorry I can't open position because you tried to click on a expired button.");
|
||||
await component.Channel.SendMessageAsync(
|
||||
"Sorry I can't open position because you tried to click on a expired button.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var exchangeService = (IExchangeService)_services.GetService(typeof(IExchangeService));
|
||||
var moneyManagementService = (IMoneyManagementService)_services.GetService(typeof(IMoneyManagementService));
|
||||
var moneyManagementService =
|
||||
(IMoneyManagementService)_services.GetService(typeof(IMoneyManagementService));
|
||||
var accountService = (IAccountService)_services.GetService(typeof(IAccountService));
|
||||
var tradingService = (ITradingService)_services.GetService(typeof(ITradingService));
|
||||
|
||||
@@ -261,7 +277,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
|
||||
.Handle(tradeCommand);
|
||||
|
||||
var builder = new ComponentBuilder().WithButton("Close Position", $"{Constants.DiscordButtonAction.ClosePosition}{_separator}{position.Identifier}");
|
||||
var builder = new ComponentBuilder().WithButton("Close Position",
|
||||
$"{Constants.DiscordButtonAction.ClosePosition}{_separator}{position.Identifier}");
|
||||
|
||||
await component.Channel.SendMessageAsync(MessengerHelpers.GetPositionMessage(position),
|
||||
components: builder.Build());
|
||||
@@ -272,10 +289,10 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
private string GetClosingPositionMessage(Position position)
|
||||
{
|
||||
return $"Closing : {position.OriginDirection} {position.Open.Ticker} \n" +
|
||||
$"Open Price : {position.Open.Price} \n" +
|
||||
$"Closing Price : {position.Open.Price} \n" +
|
||||
$"Quantity :{position.Open.Quantity} \n" +
|
||||
$"PNL : {position.ProfitAndLoss.Net} $";
|
||||
$"Open Price : {position.Open.Price} \n" +
|
||||
$"Closing Price : {position.Open.Price} \n" +
|
||||
$"Quantity :{position.Open.Quantity} \n" +
|
||||
$"PNL : {position.ProfitAndLoss.Net} $";
|
||||
}
|
||||
|
||||
private async Task ClosePosition(SocketMessageComponent component, string[] parameters)
|
||||
@@ -287,42 +304,45 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
await component.RespondAsync("Alright, let met few seconds to close this position");
|
||||
var position = _tradingService.GetPositionByIdentifier(parameters[1]);
|
||||
var command = new ClosePositionCommand(position);
|
||||
var result = await new ClosePositionCommandHandler(exchangeService, accountService, tradingService).Handle(command);
|
||||
var result =
|
||||
await new ClosePositionCommandHandler(exchangeService, accountService, tradingService).Handle(command);
|
||||
var fields = new List<EmbedFieldBuilder>()
|
||||
{
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Direction",
|
||||
Value = position.OriginDirection,
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Open Price",
|
||||
Value = $"{position.Open.Price:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Quantity",
|
||||
Value = $"{position.Open.Quantity:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Pnl",
|
||||
Value = $"{position.ProfitAndLoss.Net:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Direction",
|
||||
Value = position.OriginDirection,
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Open Price",
|
||||
Value = $"{position.Open.Price:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Quantity",
|
||||
Value = $"{position.Open.Quantity:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Pnl",
|
||||
Value = $"{position.ProfitAndLoss.Net:#.##}",
|
||||
IsInline = true
|
||||
},
|
||||
};
|
||||
var embed = DiscordHelpers.GetEmbed(position.AccountName, $"Position status is now {result.Status}", fields, position.ProfitAndLoss.Net > 0 ? Color.Green : Color.Red);
|
||||
var embed = DiscordHelpers.GetEmbed(position.AccountName, $"Position status is now {result.Status}", fields,
|
||||
position.ProfitAndLoss.Net > 0 ? Color.Green : Color.Red);
|
||||
await component.Channel.SendMessageAsync("", embed: embed);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Out
|
||||
|
||||
public async Task SendSignal(string message)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
||||
@@ -330,18 +350,21 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
await channel.SendMessageAsync(message, components: builder.Build());
|
||||
}
|
||||
|
||||
public async Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe)
|
||||
public async Task SendSignal(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction,
|
||||
Timeframe timeframe)
|
||||
{
|
||||
var expirationDate = DateTime.Now.AddMinutes(_settings.ButtonExpirationMinutes).ToString("G");
|
||||
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
||||
var builder = new ComponentBuilder().WithButton("Open Position", $"{Constants.DiscordButtonAction.OpenPosition}{_separator}{exchange}{_separator}{ticker}{_separator}{direction}{_separator}{timeframe}{_separator}{expirationDate}");
|
||||
var builder = new ComponentBuilder().WithButton("Open Position",
|
||||
$"{Constants.DiscordButtonAction.OpenPosition}{_separator}{exchange}{_separator}{ticker}{_separator}{direction}{_separator}{timeframe}{_separator}{expirationDate}");
|
||||
await channel.SendMessageAsync(message, components: builder.Build());
|
||||
}
|
||||
|
||||
public async Task SendIncreasePosition(string address, Trade trade, string copyAccountName, Trade? oldTrade = null)
|
||||
public async Task SendIncreasePosition(string address, Trade trade, string copyAccountName,
|
||||
Trade? oldTrade = null)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel;
|
||||
|
||||
|
||||
|
||||
var fields = new List<EmbedFieldBuilder>()
|
||||
{
|
||||
@@ -373,7 +396,10 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
|
||||
if (oldTrade != null)
|
||||
{
|
||||
fields.Add(new EmbedFieldBuilder { Name = "Increasy by", Value = $"{(trade.Quantity - oldTrade.Quantity) / trade.Leverage:#.##} $" });
|
||||
fields.Add(new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Increasy by", Value = $"{(trade.Quantity - oldTrade.Quantity) / trade.Leverage:#.##} $"
|
||||
});
|
||||
}
|
||||
|
||||
var titlePrefix = oldTrade != null ? "Increase " : "";
|
||||
@@ -398,24 +424,29 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
|
||||
if (oldTrade == null)
|
||||
{
|
||||
builder.WithButton($"Copy with {mm.Name}", $"{Constants.DiscordButtonAction.CopyPosition}{_separator}{encodedData}");
|
||||
builder.WithButton($"Copy with {mm.Name}",
|
||||
$"{Constants.DiscordButtonAction.CopyPosition}{_separator}{encodedData}");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.WithButton($"Increase with {mm.Name}", $"{Constants.DiscordButtonAction.CopyPosition}{_separator}{encodedData}");
|
||||
builder.WithButton($"Increase with {mm.Name}",
|
||||
$"{Constants.DiscordButtonAction.CopyPosition}{_separator}{encodedData}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"{titlePrefix}{trade.Direction} {trade.Ticker}", fields, trade.Direction == TradeDirection.Long ? Color.Green : Color.Red);
|
||||
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"{titlePrefix}{trade.Direction} {trade.Ticker}", fields,
|
||||
trade.Direction == TradeDirection.Long ? Color.Green : Color.Red);
|
||||
await channel.SendMessageAsync("", components: builder.Build(), embed: embed);
|
||||
}
|
||||
|
||||
public async Task SendPosition(string message, TradingExchanges exchange, Ticker ticker, TradeDirection direction, Timeframe timeframe)
|
||||
public async Task SendPosition(string message, TradingExchanges exchange, Ticker ticker,
|
||||
TradeDirection direction, Timeframe timeframe)
|
||||
{
|
||||
var expirationDate = DateTime.Now.AddMinutes(_settings.ButtonExpirationMinutes).ToString("G");
|
||||
var channel = _client.GetChannel(_settings.SignalChannelId) as IMessageChannel;
|
||||
var builder = new ComponentBuilder().WithButton("Open Position", $"{Constants.DiscordButtonAction.OpenPosition}{_separator}{exchange}{_separator}{ticker}{_separator}{direction}{_separator}{timeframe}{_separator}{expirationDate}");
|
||||
var builder = new ComponentBuilder().WithButton("Open Position",
|
||||
$"{Constants.DiscordButtonAction.OpenPosition}{_separator}{exchange}{_separator}{ticker}{_separator}{direction}{_separator}{timeframe}{_separator}{expirationDate}");
|
||||
await channel.SendMessageAsync(message, components: builder.Build());
|
||||
}
|
||||
|
||||
@@ -433,7 +464,9 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
|
||||
public async Task SendTradeMessage(string message, bool isBadBehavior = false)
|
||||
{
|
||||
var channel = _client.GetChannel(isBadBehavior ? _settings.TroublesChannelId : _settings.TradesChannelId) as IMessageChannel;
|
||||
var channel =
|
||||
_client.GetChannel(isBadBehavior ? _settings.TroublesChannelId : _settings.TradesChannelId) as
|
||||
IMessageChannel;
|
||||
await channel.SendMessageAsync(message);
|
||||
}
|
||||
|
||||
@@ -467,7 +500,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
}
|
||||
};
|
||||
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"Closed {oldTrade.Direction} {oldTrade.Ticker}", fields, oldTrade.Direction == TradeDirection.Long ? Color.DarkGreen : Color.DarkRed);
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"Closed {oldTrade.Direction} {oldTrade.Ticker}", fields,
|
||||
oldTrade.Direction == TradeDirection.Long ? Color.DarkGreen : Color.DarkRed);
|
||||
var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync("", embed: embed);
|
||||
}
|
||||
@@ -508,7 +542,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
}
|
||||
};
|
||||
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"Decrease {trade.Direction} {trade.Ticker}", fields, Color.Blue);
|
||||
var embed = DiscordHelpers.GetEmbed(address, $"Decrease {trade.Direction} {trade.Ticker}", fields,
|
||||
Color.Blue);
|
||||
var channel = _client.GetChannel(_settings.CopyTradingChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync("", embed: embed);
|
||||
}
|
||||
@@ -517,7 +552,8 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
public async Task SendPosition(Position position)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.TradesChannelId) as IMessageChannel;
|
||||
var builder = new ComponentBuilder().WithButton("Close Position", $"{Constants.DiscordButtonAction.ClosePosition}{_separator}{position.Open.ExchangeOrderId}");
|
||||
var builder = new ComponentBuilder().WithButton("Close Position",
|
||||
$"{Constants.DiscordButtonAction.ClosePosition}{_separator}{position.Open.ExchangeOrderId}");
|
||||
await channel.SendMessageAsync(MessengerHelpers.GetPositionMessage(position), components: builder.Build());
|
||||
}
|
||||
|
||||
@@ -525,7 +561,6 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.LeaderboardChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync("", embed: DiscordHelpers.GetTradersEmbed(traders, "Leaderboard"));
|
||||
|
||||
}
|
||||
|
||||
public async Task SendBadTraders(List<Trader> traders)
|
||||
@@ -534,24 +569,37 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
await channel.SendMessageAsync("", embed: DiscordHelpers.GetTradersEmbed(traders, "Noobiesboard"));
|
||||
}
|
||||
|
||||
public async Task SendDowngradedFundingRate(FundingRate fundingRate)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.FundingRateChannelId) as IMessageChannel;
|
||||
await channel.SendMessageAsync("",
|
||||
embed: DiscordHelpers.GetFundingRateEmbed(fundingRate, "Funding rate new opportunity"));
|
||||
}
|
||||
|
||||
public Task SendNewTopFundingRate(FundingRate newRate)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.FundingRateChannelId) as IMessageChannel;
|
||||
return channel.SendMessageAsync("",
|
||||
embed: DiscordHelpers.GetFundingRateEmbed(newRate, "Funding rate new opportunity"));
|
||||
}
|
||||
|
||||
public Task SendFundingRateUpdate(FundingRate oldRate, FundingRate newRate)
|
||||
{
|
||||
var channel = _client.GetChannel(_settings.FundingRateChannelId) as IMessageChannel;
|
||||
return channel.SendMessageAsync("",
|
||||
embed: DiscordHelpers.GetFundingRateEmbed(newRate, "Funding rate new opportunity", oldRate));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class CopyTradeData
|
||||
{
|
||||
[JsonProperty(PropertyName = "D")]
|
||||
public TradeDirection Direction { get; set; }
|
||||
[JsonProperty(PropertyName = "T")]
|
||||
public Ticker Ticker { get; set; }
|
||||
[JsonProperty(PropertyName = "A")]
|
||||
public string AccountName { get; set; }
|
||||
[JsonProperty(PropertyName = "E")]
|
||||
public int ExpirationMinute { get; set; }
|
||||
[JsonProperty(PropertyName = "L")]
|
||||
public decimal Leverage { get; set; }
|
||||
[JsonProperty(PropertyName = "M")]
|
||||
public string MoneyManagementName { get; internal set; }
|
||||
[JsonProperty(PropertyName = "D")] public TradeDirection Direction { get; set; }
|
||||
[JsonProperty(PropertyName = "T")] public Ticker Ticker { get; set; }
|
||||
[JsonProperty(PropertyName = "A")] public string AccountName { get; set; }
|
||||
[JsonProperty(PropertyName = "E")] public int ExpirationMinute { get; set; }
|
||||
[JsonProperty(PropertyName = "L")] public decimal Leverage { get; set; }
|
||||
[JsonProperty(PropertyName = "M")] public string MoneyManagementName { get; internal set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user