Files
aiaa-notification-server/internal/subscriber/tradesignal/transform_test.go
T
ryan 6f846a0a3c feat: subscribe to RabbitMQ trade signals and notify by rules
Consume configurable queues, format signals (including period), share NotifyService with HTTP, and drop duplicate bodies within 1h.
2026-08-15 17:34:49 +08:00

221 lines
4.9 KiB
Go

package tradesignal
import (
"math"
"testing"
"aiaa-notification-service/internal/config"
)
func TestApplyQuantityMultiplierAndLeverage(t *testing.T) {
leverage := 20
override := &config.StrategyOverride{
QuantityMultipliers: config.QuantityMultipliers{
Open: 2,
Add: 1.5,
Reduce: 0.5,
Close: 3,
},
Leverage: &leverage,
}
tests := []struct {
action string
quantity float64
wantQty float64
}{
{"OPEN", 1, 2},
{"ADD", 2, 3},
{"REDUCE", 4, 2},
{"CLOSE", 1, 3},
}
for _, tt := range tests {
signal := &Signal{
Action: tt.action,
Quantity: ptr(tt.quantity),
Leverage: 10,
}
out := Apply(signal, override)
if out.Quantity == nil || *out.Quantity != tt.wantQty {
t.Fatalf("action=%s quantity=%v want %v", tt.action, out.Quantity, tt.wantQty)
}
if out.Leverage != 20 {
t.Fatalf("action=%s leverage=%d want 20", tt.action, out.Leverage)
}
}
}
func TestApplyKeepsOriginalWhenNoOverride(t *testing.T) {
signal := &Signal{
Action: "OPEN",
Quantity: ptr(1.5),
Leverage: 8,
}
out := Apply(signal, nil)
if out != signal {
t.Fatalf("expected same signal pointer when override is nil")
}
}
func TestApplyDoesNotChangeMarginRatioOnlySignals(t *testing.T) {
marginRatio := 0.2
signal := &Signal{
Action: "OPEN",
AmountMarginRatio: &marginRatio,
Leverage: 5,
}
override := &config.StrategyOverride{
QuantityMultipliers: config.QuantityMultipliers{Open: 2},
}
out := Apply(signal, override)
if out.Quantity != nil {
t.Fatalf("expected quantity unchanged when only margin ratio is set")
}
if out.Leverage != 5 {
t.Fatalf("expected leverage unchanged, got %d", out.Leverage)
}
}
func TestAvgPriceOpenAndAdd(t *testing.T) {
tr := NewTracker()
open := &Signal{
SignalID: "s1",
StrategyCode: "BLONG",
Symbol: "BTCUSDT",
Side: "LONG",
Action: "OPEN",
Quantity: ptr(2),
Price: 100,
}
snap := tr.Apply(open)
if !snap.HasAvg || snap.AvgPrice != 100 {
t.Fatalf("open avg=%v has=%v", snap.AvgPrice, snap.HasAvg)
}
add := &Signal{
SignalID: "s2",
StrategyCode: "BLONG",
Symbol: "BTCUSDT",
Side: "LONG",
Action: "ADD",
Quantity: ptr(2),
Price: 200,
}
snap = tr.Apply(add)
if !snap.HasAvg || math.Abs(snap.AvgPrice-150) > 1e-9 {
t.Fatalf("expected avg 150, got %v", snap.AvgPrice)
}
}
func TestAvgPriceWithMarginRatio(t *testing.T) {
tr := NewTracker()
open := &Signal{
SignalID: "r1",
StrategyCode: "BLONG",
Symbol: "ETHUSDT",
Side: "LONG",
Action: "OPEN",
AmountMarginRatio: ptr(0.1),
Price: 100,
}
tr.Apply(open)
add := &Signal{
SignalID: "r2",
StrategyCode: "BLONG",
Symbol: "ETHUSDT",
Side: "LONG",
Action: "ADD",
AmountMarginRatio: ptr(0.1),
Price: 200,
}
snap := tr.Apply(add)
if !snap.HasAvg || math.Abs(snap.AvgPrice-150) > 1e-9 {
t.Fatalf("expected weighted avg 150, got %v", snap.AvgPrice)
}
}
func TestCloseKeepsEntryAvgInSnapshot(t *testing.T) {
tr := NewTracker()
tr.Apply(&Signal{
SignalID: "c1",
StrategyCode: "BLONG",
Symbol: "BTCUSDT",
Side: "LONG",
Action: "OPEN",
Quantity: ptr(1),
Price: 64000,
})
snap := tr.Apply(&Signal{
SignalID: "c2",
StrategyCode: "BLONG",
Symbol: "BTCUSDT",
Side: "LONG",
Action: "CLOSE",
Price: 65000,
})
if !snap.HasAvg || snap.AvgPrice != 64000 {
t.Fatalf("close should report entry avg 64000, got %v", snap.AvgPrice)
}
snap = tr.Apply(&Signal{
SignalID: "c3",
StrategyCode: "BLONG",
Symbol: "BTCUSDT",
Side: "LONG",
Action: "ADD",
Quantity: ptr(1),
Price: 70000,
})
if !snap.HasAvg || snap.AvgPrice != 70000 {
t.Fatalf("after close, add should reopen at 70000, got %v", snap.AvgPrice)
}
}
func TestSignalIDIdempotent(t *testing.T) {
tr := NewTracker()
sig := &Signal{
SignalID: "dup",
StrategyCode: "BLONG",
Symbol: "BTCUSDT",
Side: "LONG",
Action: "OPEN",
Quantity: ptr(1),
Price: 100,
}
tr.Apply(sig)
tr.Apply(sig)
snap := tr.Apply(&Signal{
SignalID: "dup2",
StrategyCode: "BLONG",
Symbol: "BTCUSDT",
Side: "LONG",
Action: "ADD",
Quantity: ptr(1),
Price: 200,
})
if math.Abs(snap.AvgPrice-150) > 1e-9 {
t.Fatalf("duplicate open should not double size, avg=%v", snap.AvgPrice)
}
}
func TestDifferentSideIsolated(t *testing.T) {
tr := NewTracker()
tr.Apply(&Signal{
SignalID: "l1", StrategyCode: "BLONG", Symbol: "BTCUSDT", Side: "LONG",
Action: "OPEN", Quantity: ptr(1), Price: 100,
})
snap := tr.Apply(&Signal{
SignalID: "s1", StrategyCode: "BLONG", Symbol: "BTCUSDT", Side: "SHORT",
Action: "OPEN", Quantity: ptr(1), Price: 200,
})
if snap.AvgPrice != 200 {
t.Fatalf("short should be isolated, got %v", snap.AvgPrice)
}
}