313 lines
12 KiB
C#
313 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using FinlyticCore.Dtos.Bot;
|
|
using FinlyticCore.Dtos.News;
|
|
using FinlyticCore.Dtos.TechnicalAnalysis;
|
|
using FinlyticCore.Dtos.Trading;
|
|
using FinlyticNotify.Services;
|
|
using Xunit;
|
|
|
|
namespace FinlyticNotify.Tests.Services;
|
|
|
|
public class NotificationFormatterTests
|
|
{
|
|
private readonly NotificationFormatter _formatter = new();
|
|
|
|
[Fact]
|
|
public void FormatProposalNotification_LongBuy_FormatsCorrectly()
|
|
{
|
|
// Arrange
|
|
var proposal = new TradeProposalDto(
|
|
ProposalId: Guid.NewGuid(),
|
|
UnderlyingIsin: "US67066G1040",
|
|
Symbol: "NVDA",
|
|
StrategyKey: "TrendPullbackFvg",
|
|
Direction: SignalDirection.Buy,
|
|
QualityScore: 88m,
|
|
CompositeScore: 84.5m,
|
|
CurrentPrice: 125.50m,
|
|
EntryPrice: 125.00m,
|
|
InvalidationPrice: 120.00m,
|
|
ExitPlan: new ExitPlan(
|
|
StrategyType: ExitStrategyType.FixedSingleTarget,
|
|
InitialStopLoss: 120.00m,
|
|
TakeProfitStages:
|
|
[
|
|
new TakeProfitStage(1, 135.00m, 1.0m, 2.0m, "TP1: 100% Exit at 135.00")
|
|
]
|
|
),
|
|
SelectedDerivative: new DerivativeSelectionDto(
|
|
DerivativeIsin: "DE000TEST123",
|
|
DerivativeWkn: null,
|
|
Issuer: "HSBC",
|
|
OptionType: "LONG",
|
|
Strike: 110.0m,
|
|
Barrier: 110.0m,
|
|
Leverage: 8.2m,
|
|
SafetyBufferPercent: 0.08m,
|
|
SpreadPercentage: 0.005m,
|
|
Size: 100m
|
|
),
|
|
AiValidation: new AiValidationResultDto(
|
|
IsApproved: true,
|
|
Confidence: 0.85m,
|
|
Source: ValidationSource.Ai,
|
|
ThesisSummary: "Bruch des lokalen Abwärtstrends mit starkem Volumen und positiver Halbleiter-Sektor-Dynamik.",
|
|
InvalidationReason: "",
|
|
KeyCatalysts: ["Earnings Momentum"],
|
|
IdentifiedRisks: ["Allgemeine Marktvolatilität"]
|
|
),
|
|
CreatedAtUtc: DateTime.UtcNow,
|
|
ExpiresAtUtc: DateTime.UtcNow.AddHours(4)
|
|
);
|
|
|
|
// Act
|
|
var notification = _formatter.FormatProposalNotification(proposal, "finlytic_broadcast");
|
|
|
|
// Assert
|
|
Assert.Equal("finlytic_broadcast", notification.Topic);
|
|
Assert.Contains("🟢 Neuer Trade-Vorschlag: NVDA (Long)", notification.Title);
|
|
Assert.Equal(4, notification.Priority); // Score >= 80 -> Priority 4
|
|
Assert.Contains("chart_with_upwards_trend", notification.Tags!);
|
|
Assert.Contains("moneybag", notification.Tags!);
|
|
Assert.Contains("**Strategie:** TrendPullbackFvg", notification.Message);
|
|
Assert.Contains("125,00", notification.Message.Replace('.', ','));
|
|
Assert.Contains("HSBC", notification.Message);
|
|
Assert.Contains("Bruch des lokalen Abwärtstrends", notification.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatProposalNotification_ShortSell_FormatsCorrectly()
|
|
{
|
|
// Arrange
|
|
var proposal = new TradeProposalDto(
|
|
ProposalId: Guid.NewGuid(),
|
|
UnderlyingIsin: "US88160R1014",
|
|
Symbol: "TSLA",
|
|
StrategyKey: "SmcLiquiditySweep",
|
|
Direction: SignalDirection.Sell,
|
|
QualityScore: 74m,
|
|
CompositeScore: 72.0m,
|
|
CurrentPrice: 210.00m,
|
|
EntryPrice: 209.50m,
|
|
InvalidationPrice: 216.00m,
|
|
ExitPlan: new ExitPlan(
|
|
StrategyType: ExitStrategyType.FixedSingleTarget,
|
|
InitialStopLoss: 216.00m,
|
|
TakeProfitStages: [new TakeProfitStage(1, 196.50m, 1.0m, 2.0m, "TP1")]
|
|
),
|
|
SelectedDerivative: null,
|
|
AiValidation: new AiValidationResultDto(
|
|
IsApproved: true,
|
|
Confidence: null,
|
|
Source: ValidationSource.RuleBased,
|
|
ThesisSummary: "Bärischer Liquidity Sweep über dem Vortagshoch mit starker Ablehnung.",
|
|
InvalidationReason: "",
|
|
KeyCatalysts: [],
|
|
IdentifiedRisks: []
|
|
),
|
|
CreatedAtUtc: DateTime.UtcNow,
|
|
ExpiresAtUtc: DateTime.UtcNow.AddHours(4)
|
|
);
|
|
|
|
// Act
|
|
var notification = _formatter.FormatProposalNotification(proposal, "finlytic_broadcast");
|
|
|
|
// Assert
|
|
Assert.Equal("finlytic_broadcast", notification.Topic);
|
|
Assert.Contains("🔴 Neuer Trade-Vorschlag: TSLA (Short)", notification.Title);
|
|
Assert.Equal(3, notification.Priority); // Score < 80 -> Priority 3
|
|
Assert.Contains("chart_with_downwards_trend", notification.Tags!);
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatTradeStatusNotification_Tp1Hit_FormatsCorrectlyForUser()
|
|
{
|
|
// Arrange
|
|
var trade = new ActiveTradeDto(
|
|
TradeId: Guid.NewGuid(),
|
|
ProposalId: Guid.NewGuid(),
|
|
UserId: Guid.NewGuid(),
|
|
UnderlyingIsin: "US0378331005",
|
|
Symbol: "AAPL",
|
|
DerivativeIsin: null,
|
|
DerivativeWkn: null,
|
|
ExecutionMode: ExecutionMode.ManualTradeRepublic,
|
|
InstrumentType: InstrumentCategoryType.Stock,
|
|
Direction: SignalDirection.Buy,
|
|
Status: TradeStatus.Tp1Hit,
|
|
AverageBuyIn: 150.00m,
|
|
TotalQuantity: 10m,
|
|
InitialStopLoss: 145.00m,
|
|
CurrentStopLoss: 151.00m,
|
|
CurrentPrice: 160.00m,
|
|
UnrealizedPnlEur: 100.00m,
|
|
UnrealizedPnlPercent: 6.67m,
|
|
RealizedPnlEur: 0m,
|
|
ExitPlan: new ExitPlan(ExitStrategyType.StagedScaleOutWithBreakEven, 145.00m, []),
|
|
Fills: [],
|
|
OpenedAtUtc: DateTime.UtcNow.AddDays(-1),
|
|
ClosedAtUtc: null
|
|
);
|
|
|
|
// Act
|
|
var notification = _formatter.FormatTradeStatusNotification(trade, "finlytic_lars");
|
|
|
|
// Assert
|
|
Assert.Equal("finlytic_lars", notification.Topic);
|
|
Assert.Contains("🎯 Teilgewinn erreicht (TP1): AAPL", notification.Title);
|
|
Assert.Equal(4, notification.Priority);
|
|
Assert.Contains("tada", notification.Tags!);
|
|
Assert.Contains("Break-Even gesichert", notification.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatTradeStatusNotification_StoppedOut_FormatsCorrectlyForUser()
|
|
{
|
|
// Arrange
|
|
var trade = new ActiveTradeDto(
|
|
TradeId: Guid.NewGuid(),
|
|
ProposalId: Guid.NewGuid(),
|
|
UserId: Guid.NewGuid(),
|
|
UnderlyingIsin: "US0378331005",
|
|
Symbol: "AAPL",
|
|
DerivativeIsin: null,
|
|
DerivativeWkn: null,
|
|
ExecutionMode: ExecutionMode.ManualTradeRepublic,
|
|
InstrumentType: InstrumentCategoryType.Stock,
|
|
Direction: SignalDirection.Buy,
|
|
Status: TradeStatus.StoppedOut,
|
|
AverageBuyIn: 150.00m,
|
|
TotalQuantity: 10m,
|
|
InitialStopLoss: 145.00m,
|
|
CurrentStopLoss: 145.00m,
|
|
CurrentPrice: 144.50m,
|
|
UnrealizedPnlEur: 0m,
|
|
UnrealizedPnlPercent: 0m,
|
|
RealizedPnlEur: -55.00m,
|
|
ExitPlan: new ExitPlan(ExitStrategyType.FixedSingleTarget, 145.00m, []),
|
|
Fills: [],
|
|
OpenedAtUtc: DateTime.UtcNow.AddDays(-1),
|
|
ClosedAtUtc: DateTime.UtcNow
|
|
);
|
|
|
|
// Act
|
|
var notification = _formatter.FormatTradeStatusNotification(trade, "finlytic_john");
|
|
|
|
// Assert
|
|
Assert.Equal("finlytic_john", notification.Topic);
|
|
Assert.Contains("🛑 Stop-Loss ausgelöst: AAPL", notification.Title);
|
|
Assert.Equal(4, notification.Priority);
|
|
Assert.Contains("warning", notification.Tags!);
|
|
Assert.Contains("risikokontrolliert geschlossen", notification.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatBotTradeNotification_Active_FormatsCorrectly()
|
|
{
|
|
// Arrange
|
|
var botTrade = new BotTradeOrderDto(
|
|
OrderId: Guid.NewGuid(),
|
|
ProposalId: Guid.NewGuid(),
|
|
Isin: "US5949181045",
|
|
Symbol: "MSFT",
|
|
Venue: BotExecutionVenue.AlpacaPaperTrading,
|
|
AlpacaOrderId: "alpaca_123",
|
|
ClientOrderId: "client_123",
|
|
Direction: SignalDirection.Buy,
|
|
RequestedQuantity: 5m,
|
|
FilledQuantity: 5m,
|
|
EntryPrice: 420.00m,
|
|
AverageBuyIn: 420.50m,
|
|
InitialStopLoss: 410.00m,
|
|
CurrentStopLoss: 410.00m,
|
|
TakeProfit1: 440.00m,
|
|
TakeProfit2: 460.00m,
|
|
CurrentPrice: 425.00m,
|
|
UnrealizedPnlEur: 22.50m,
|
|
RealizedPnlEur: 0m,
|
|
Status: BotPositionStatus.Active,
|
|
ExitPlan: new ExitPlan(ExitStrategyType.FixedSingleTarget, 410.00m, []),
|
|
CreatedAtUtc: DateTime.UtcNow,
|
|
FilledAtUtc: DateTime.UtcNow,
|
|
ClosedAtUtc: null
|
|
);
|
|
|
|
// Act
|
|
var notification = _formatter.FormatBotTradeNotification(botTrade, "finlytic_bot");
|
|
|
|
// Assert
|
|
Assert.Equal("finlytic_bot", notification.Topic);
|
|
Assert.Contains("🤖 Bot Trade [Active]: MSFT (Long)", notification.Title);
|
|
Assert.Contains("robot", notification.Tags!);
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatNewsNotification_PositiveSentiment_FormatsCorrectly()
|
|
{
|
|
// Arrange
|
|
var article = new NewsArticleDto
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Title = "NVIDIA Reports Record Q4 Revenue Driven by AI Chip Demand",
|
|
Summary = "NVIDIA exceeded analyst expectations across data center and gaming segments.",
|
|
PublishedAt = DateTime.UtcNow,
|
|
SourceUrl = "https://example.com/news/nvda-q4",
|
|
Sentiment = "POSITIVE",
|
|
SentimentScore = 0.88,
|
|
Confidence = 0.92,
|
|
MatchedAssets =
|
|
[
|
|
new MatchedAssetDto { Name = "NVIDIA Corp.", Isin = "US67066G1040" }
|
|
]
|
|
};
|
|
|
|
// Act
|
|
var notification = _formatter.FormatNewsNotification(article, "finlytic_news");
|
|
|
|
// Assert
|
|
Assert.Equal("finlytic_news", notification.Topic);
|
|
Assert.Contains("🟢 News (POSITIVE): NVIDIA Corp.", notification.Title);
|
|
Assert.Equal(4, notification.Priority); // High confidence + high positive score -> Priority 4
|
|
Assert.Contains("newspaper", notification.Tags!);
|
|
Assert.Contains("chart_with_upwards_trend", notification.Tags!);
|
|
Assert.Contains("NVIDIA Reports Record Q4 Revenue", notification.Message);
|
|
Assert.Contains("0.88", notification.Message);
|
|
Assert.Equal("https://example.com/news/nvda-q4", notification.ClickUrl);
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatNewsNotification_NegativeSentiment_FormatsCorrectly()
|
|
{
|
|
// Arrange
|
|
var article = new NewsArticleDto
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Title = "Tesla Faces Supply Chain Delays and Reduced Delivery Targets",
|
|
Summary = "Tesla lowers annual guidance following factory shutdowns.",
|
|
PublishedAt = DateTime.UtcNow,
|
|
SourceUrl = "https://example.com/news/tsla-delays",
|
|
Sentiment = "NEGATIVE",
|
|
SentimentScore = -0.75,
|
|
Confidence = 0.85,
|
|
MatchedAssets =
|
|
[
|
|
new MatchedAssetDto { Name = "Tesla Inc.", Isin = "US88160R1014" }
|
|
]
|
|
};
|
|
|
|
// Act
|
|
var notification = _formatter.FormatNewsNotification(article, "finlytic_news");
|
|
|
|
// Assert
|
|
Assert.Equal("finlytic_news", notification.Topic);
|
|
Assert.Contains("🔴 News (NEGATIVE): Tesla Inc.", notification.Title);
|
|
Assert.Equal(4, notification.Priority);
|
|
Assert.Contains("newspaper", notification.Tags!);
|
|
Assert.Contains("chart_with_downwards_trend", notification.Tags!);
|
|
Assert.Contains("Tesla Faces Supply Chain Delays", notification.Message);
|
|
Assert.Contains("-0.75", notification.Message);
|
|
Assert.Equal("https://example.com/news/tsla-delays", notification.ClickUrl);
|
|
}
|
|
}
|