Trading: components 161-180
This commit is contained in:
105 files changed
+6042
-1
No files matched your search
@@ -0,0 +1,107 @@
|
||||
package mujica
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"log/slog"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
)
|
||||
|
||||
// PositionRow is one holding; ID must be stable across updates.
|
||||
type PositionRow struct {
|
||||
ID, Symbol string
|
||||
Quantity float64
|
||||
AvgCost float64
|
||||
Price float64
|
||||
}
|
||||
|
||||
// MarketValue is the quantity at the last price.
|
||||
func (p PositionRow) MarketValue() float64 { return p.Quantity * p.Price }
|
||||
|
||||
// Unrealized is the floating profit against the average cost.
|
||||
func (p PositionRow) Unrealized() float64 { return (p.Price - p.AvgCost) * p.Quantity }
|
||||
|
||||
// UnrealizedPercent is the floating profit as a percent of cost.
|
||||
func (p PositionRow) UnrealizedPercent() float64 {
|
||||
if p.AvgCost == 0 {
|
||||
return 0
|
||||
}
|
||||
return (p.Price - p.AvgCost) / p.AvgCost * 100
|
||||
}
|
||||
|
||||
// PositionTableOptions configure a PositionTable.
|
||||
type PositionTableOptions struct {
|
||||
// Decimals of prices and amounts; zero means 2.
|
||||
Decimals int
|
||||
// Version must change when rows change in place.
|
||||
Version int
|
||||
Status DataStatus
|
||||
Error string
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
// TableActionResult is a trading table and its acted row.
|
||||
type TableActionResult struct {
|
||||
Element *ui.Element
|
||||
Table DataTableView
|
||||
id string
|
||||
}
|
||||
|
||||
// Acted returns the ID of the row acted on.
|
||||
func (r TableActionResult) Acted() (string, bool) { return r.id, r.id != "" }
|
||||
|
||||
// PositionTable lists holdings with floating profit and Close.
|
||||
func PositionTable(c *ui.Context, s *DataTableState[string], rows []PositionRow, opts PositionTableOptions) TableActionResult {
|
||||
dec := priceDecimals("PositionTable", opts.Decimals)
|
||||
var r TableActionResult
|
||||
num := func(id, key string, v func(PositionRow) float64) DataColumn[PositionRow] {
|
||||
return DataColumn[PositionRow]{ID: id, Title: msg(c, key), Align: ui.End, Sortable: true,
|
||||
Compare: func(a, b PositionRow) int { return cmp.Compare(v(a), v(b)) },
|
||||
Text: func(p PositionRow) string { return statisticFormat(v(p), dec) }}
|
||||
}
|
||||
pnl := num("pnl", "position_table.pnl", PositionRow.Unrealized)
|
||||
pnl.Cell = func(p PositionRow) { tradingSigned(c, p.Unrealized(), statisticSigned(p.Unrealized(), dec)) }
|
||||
pct := num("pct", "position_table.pnl_percent", PositionRow.UnrealizedPercent)
|
||||
pct.Cell = func(p PositionRow) {
|
||||
tradingSigned(c, p.UnrealizedPercent(), statisticSigned(p.UnrealizedPercent(), 2)+"%")
|
||||
}
|
||||
cols := []DataColumn[PositionRow]{
|
||||
{ID: "symbol", Title: msg(c, "position_table.symbol"), Sortable: true,
|
||||
Compare: func(a, b PositionRow) int { return cmp.Compare(a.Symbol, b.Symbol) },
|
||||
Text: func(p PositionRow) string { return p.Symbol }},
|
||||
num("qty", "position_table.quantity", func(p PositionRow) float64 { return p.Quantity }),
|
||||
num("cost", "position_table.cost", func(p PositionRow) float64 { return p.AvgCost }),
|
||||
num("price", "position_table.price", func(p PositionRow) float64 { return p.Price }),
|
||||
num("value", "position_table.value", PositionRow.MarketValue),
|
||||
pnl, pct,
|
||||
{ID: "action", Title: msg(c, "position_table.action"), Width: 96,
|
||||
Text: func(PositionRow) string { return "" },
|
||||
Cell: func(p PositionRow) {
|
||||
if Button(c, msg(c, "position_table.close"), ButtonOptions{Variant: Secondary}).Label(msg(c, "position_table.close") + " " + p.Symbol).Clicked() {
|
||||
r.id = p.ID
|
||||
}
|
||||
}},
|
||||
}
|
||||
r.Element = ui.Column(c).Disabled(opts.Disabled).Grow(1).FillWidth().MinHeight(0)
|
||||
r.Element.Children(func() {
|
||||
r.Table = DataTable(c, s, rows, DataTableOptions[PositionRow, string]{Columns: cols, Key: func(p PositionRow) string { return p.ID },
|
||||
Version: opts.Version, Status: opts.Status, Error: opts.Error, Empty: msg(c, "position_table.empty")})
|
||||
r.Table.Element.Grow(1)
|
||||
})
|
||||
if r.Element.IsDisabled() {
|
||||
r.id = ""
|
||||
}
|
||||
if r.id != "" {
|
||||
slog.Debug("mujica: position_table close")
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// tradingSigned shows a signed amount with its arrow.
|
||||
func tradingSigned(c *ui.Context, v float64, text string) {
|
||||
sign := trendSign(v)
|
||||
ui.Row(c).Gap(2).AlignItems(ui.Center).Justify(ui.End).FillWidth().TextColor(trendColor(c, sign)).Children(func() {
|
||||
ui.Icon(c, trendIcon(sign)).Size(12, 12)
|
||||
ui.Text(c, text).FontFeatures("tnum").FontWeight(600).SingleLine()
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user