109 lines
3.4 KiB
Go
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")
|
|
}
|
|
}
|