Files
MujicaUI/payoff_diagram_test.go
T

109 lines
3.4 KiB
Go

package mujica
import (
"mujicaui/theme"
"slices"
"strings"
"testing"
"github.com/egoist/mygo/ui"
)
func bullSpread() []OptionLeg {
return []OptionLeg{{Kind: LegCall, Strike: 100, Premium: 3, Quantity: 1}, {Kind: LegCall, Strike: 110, Premium: 1, Quantity: -1}}
}
func TestPayoffMath(t *testing.T) {
legs := bullSpread()
for _, tc := range []struct{ price, want float64 }{{90, -2}, {100, -2}, {102, 0}, {110, 8}, {130, 8}} {
if got := PayoffAt(legs, tc.price); got != tc.want {
t.Errorf("payoff at %v = %v, want %v", tc.price, got, tc.want)
}
}
if got := Breakevens(legs, 70, 140); !slices.Equal(got, []float64{102}) {
t.Fatalf("breakevens %v", got)
}
straddle := []OptionLeg{{Kind: LegCall, Strike: 100, Premium: 4, Quantity: 1}, {Kind: LegPut, Strike: 100, Premium: 3, Quantity: 1}}
if got := Breakevens(straddle, 50, 150); !slices.Equal(got, []float64{93, 107}) {
t.Fatalf("straddle breakevens %v", got)
}
covered := []OptionLeg{{Kind: LegStock, Premium: 50, Quantity: 100}, {Kind: LegCall, Strike: 55, Premium: 2, Quantity: -100}}
if got := PayoffAt(covered, 60); got != 700 {
t.Fatalf("covered call at 60 = %v", got)
}
}
func TestPayoffDiagramKeysAndText(t *testing.T) {
var last ChartResult
tt := chatTester(640, 380, func(c *ui.Context) {
last = PayoffDiagram(c, bullSpread(), PayoffDiagramOptions{Frame: ChartFrame{Label: "Spread"}, Spot: 103, Low: 90, High: 120, Points: 31})
})
if !tt.HasText("Breakeven: 102") {
t.Fatalf("texts %q", tt.Texts())
}
tt.Key(0, ui.KeyTab)
tt.Key(0, ui.KeyEnd)
if last.Point != 30 || !tt.HasText("Price 120 · profit 8") {
t.Fatalf("End: point %d texts %q", last.Point, tt.Texts())
}
tt.Key(0, ui.KeyHome)
if !tt.HasText("Price 90 · loss -2") {
t.Fatalf("Home: %q", tt.Texts())
}
ann := strings.Join(tt.Announcements(), "|")
if !strings.Contains(ann, "Price 90") {
t.Fatalf("announcements %q", ann)
}
}
func TestPayoffDiagramEmptyAndBadRange(t *testing.T) {
tt := chatTester(640, 380, func(c *ui.Context) {
PayoffDiagram(c, nil, PayoffDiagramOptions{Frame: ChartFrame{Label: "Empty"}})
})
if !tt.HasText("No data") {
t.Fatalf("texts %q", tt.Texts())
}
defer func() {
if recover() == nil {
t.Fatal("no panic")
}
}()
ui.Render(func(c *ui.Context) {
Use(c, Settings{})
PayoffDiagram(c, bullSpread(), PayoffDiagramOptions{Frame: ChartFrame{Label: "x"}, Low: 10, High: 5})
}, 100, 100, 1)
}
func TestPayoffDiagramSnapshot(t *testing.T) {
snapshot(t, "payoff_diagram", 640, 360, func(c *ui.Context) {
ui.Column(c).Padding(16).Children(func() {
PayoffDiagram(c, bullSpread(), PayoffDiagramOptions{Frame: ChartFrame{Label: "Bull call spread"}, Spot: 103})
})
})
}
func TestPayoffDiagramSparseGridKeepsPeak(t *testing.T) {
legs := []OptionLeg{{Kind: LegCall, Strike: 90, Premium: 1, Quantity: 1}, {Kind: LegCall, Strike: 100, Quantity: -2}, {Kind: LegCall, Strike: 110, Quantity: 1}}
tt := chatTester(640, 380, func(c *ui.Context) {
PayoffDiagram(c, legs, PayoffDiagramOptions{Frame: ChartFrame{Label: "Butterfly"}, Low: 80, High: 120, Points: 2})
})
r, ok := tt.Find("Butterfly")
if !ok {
t.Fatal("plot missing")
}
x := int(r.X + 56 + (r.W-68)/2)
found := false
ink := theme.Light().AccentText
for y := int(r.Y + 8); y < int(r.Y+r.H/2); y++ {
for dx := -2; dx <= 2; dx++ {
pixel := tt.Image().RGBAAt(x+dx, y)
if pixel.R == ink.R && pixel.G == ink.G && pixel.B == ink.B {
found = true
}
}
}
if !found {
t.Fatal("profit peak falls outside the plot")
}
}