WIP components (as built)
This commit is contained in:
110 files changed
+3975
-273
No files matched your search
@@ -1,3 +1,233 @@
|
||||
package main
|
||||
|
||||
var marketsPages = []page{}
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/egoist/mygo/ui"
|
||||
mujica "mujicaui"
|
||||
)
|
||||
|
||||
var marketsPages = []page{
|
||||
{ID: "181", Name: "OptionChain", ZhName: "期权链", Category: "Markets", Demo: marketsOptionChainDemo, Code: `var state mujica.OptionChainState
|
||||
rows := []mujica.OptionStrike{{Strike: 100, Call: mujica.OptionQuote{Bid: 3, Ask: 3.2, Last: 3.1}, Put: mujica.OptionQuote{Bid: 2, Ask: 2.2, Last: 2.1}}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.OptionChain(c, &state, []string{"Oct 17", "Nov 21"}, rows, mujica.OptionChainOptions{Spot: 100.4})
|
||||
if r.Changed || r.Submitted || r.ExpiryChanged {
|
||||
fmt.Println(state.Expiry, state.Strike, state.Side)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 切换到期日或列表;上下、Home/End 选择行;左右切换认购认沽;Enter 确认。"},
|
||||
{ID: "182", Name: "GreeksTable", ZhName: "希腊值表", Category: "Markets", Demo: marketsGreeksTableDemo, Code: `rows := []mujica.GreeksRow{{Label: "AAPL 100 C", Greeks: mujica.OptionGreeks{Delta: 0.5, Gamma: 0.03, Theta: -0.1, Vega: 0.2, Rho: 0.05}}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.GreeksTable(c, rows, mujica.GreeksTableOptions{Notes: true})
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "展示组件;表头提供说明和悬停提示。"},
|
||||
{ID: "183", Name: "PayoffDiagram", ZhName: "到期损益图", Category: "Markets", Demo: marketsPayoffDiagramDemo, Code: `legs := []mujica.OptionLeg{{Kind: mujica.LegCall, Strike: 100, Premium: 3, Quantity: 1}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.PayoffDiagram(c, legs, mujica.PayoffDiagramOptions{Frame: mujica.ChartFrame{Label: "Call payoff"}, Spot: 103})
|
||||
if r.Changed() || r.Retried() {
|
||||
fmt.Println(r.Point)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 聚焦;方向键、Home/End 逐点读数;Escape 清除。"},
|
||||
{ID: "184", Name: "Watchlist", ZhName: "自选列表", Category: "Markets", Demo: marketsWatchlistDemo, Code: `var state mujica.WatchlistState
|
||||
groups := []mujica.WatchGroup{{Name: "Technology", Items: []mujica.WatchItem{{Symbol: "AAPL", Name: "Apple", Price: 227.5, Change: 2.7, ChangePercent: 1.2}}}, {Name: "Energy", Items: []mujica.WatchItem{{Symbol: "XOM", Price: 117.4}}}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.Watchlist(c, &state, groups, mujica.WatchlistOptions{})
|
||||
if r.Changed || r.Submitted || r.Moved {
|
||||
fmt.Println(state.Selected, r.Rows, r.To)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 切换分组或列菜单;上下、Home/End 选择;Enter 确认;菜单 Space 开关列。"},
|
||||
{ID: "185", Name: "SymbolSearch", ZhName: "代码搜索", Category: "Markets", Demo: marketsSymbolSearchDemo, Code: `var state mujica.SymbolSearchState
|
||||
rows := []mujica.MarketSymbol{{Code: "AAPL", Name: "Apple", Market: "NASDAQ", Type: "Stock"}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.SymbolSearch(c, &state, rows, mujica.SymbolSearchOptions{Markets: []string{"NASDAQ", "NYSE"}, Recent: rows})
|
||||
if r.Changed || r.Picked || r.Retry {
|
||||
fmt.Println(state.Query, state.Market, r.Chosen.Code)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 聚焦输入与市场;Down 进入结果;Tab 到达结果按钮,Enter 选择。"},
|
||||
{ID: "186", Name: "SymbolBadge", ZhName: "代码徽标", Category: "Markets", Demo: marketsSymbolBadgeDemo, Code: `symbol := mujica.MarketSymbol{Code: "AAPL", Name: "Apple", Market: "NASDAQ", Type: "Stock"}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.SymbolBadge(c, symbol, mujica.SymbolBadgeOptions{Clickable: true})
|
||||
if r.Changed() {
|
||||
fmt.Println(symbol.Code)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Clickable 开启时 Tab 聚焦,Enter/Space 激活。"},
|
||||
{ID: "187", Name: "MarketHeatmap", ZhName: "市场热力图", Category: "Markets", Demo: marketsMarketHeatmapDemo, Code: `var path []string
|
||||
root := mujica.HeatNode{Name: "Market", Children: []mujica.HeatNode{{Name: "Technology", Children: []mujica.HeatNode{{Name: "AAPL", Cap: 3000, Change: 1.2}, {Name: "MSFT", Cap: 2800, Change: -0.6}}}, {Name: "Energy", Cap: 450, Change: -2.1}}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.MarketHeatmap(c, &path, root, mujica.MarketHeatmapOptions{Frame: mujica.ChartFrame{Label: "Market"}})
|
||||
if r.Changed() || r.ValueChanged() || r.Retried() {
|
||||
fmt.Println(path, r.Point)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 聚焦图;方向键逐块读数;Enter 下钻;Backspace 返回;路径按钮可用 Tab。"},
|
||||
{ID: "188", Name: "Screener", ZhName: "选股器", Category: "Markets", Demo: marketsScreenerDemo, Code: `var state mujica.ScreenerState
|
||||
fields := []mujica.ScreenerField{{ID: "pe", Label: "P/E", Decimals: 1}}
|
||||
rows := []mujica.ScreenerRow{{Symbol: "AAPL", Values: map[string]float64{"pe": 34}}, {Symbol: "XOM", Values: map[string]float64{"pe": 13}}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.Screener(c, &state, fields, rows, mujica.ScreenerOptions{})
|
||||
if r.Changed || r.Save || r.Table.Changed() || r.Table.Submitted() || r.Table.SortChanged() || r.Table.Retried() {
|
||||
fmt.Println(state.Conditions)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 到达字段、比较、数值和保存;Select 方向键选择;NumberInput 方向键步进;表格方向键选择。"},
|
||||
{ID: "189", Name: "MarketOverview", ZhName: "市场概览", Category: "Markets", Demo: marketsMarketOverviewDemo, Code: `indices := []mujica.IndexQuote{{Name: "S&P 500", Price: 5751.07, Change: 51.13, ChangePercent: 0.9, Series: []float64{5700, 5730, 5720, 5751}}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.MarketOverview(c, indices, mujica.MarketOverviewOptions{Breadth: mujica.MarketBreadth{Advancing: 2100, Declining: 1400, Unchanged: 300}})
|
||||
if r.Chosen >= 0 {
|
||||
fmt.Println(indices[r.Chosen].Name)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 到达指数卡片;Enter/Space 选择。"},
|
||||
{ID: "190", Name: "IndexCard", ZhName: "指数卡片", Category: "Markets", Demo: marketsIndexCardDemo, Code: `quote := mujica.IndexQuote{Name: "S&P 500", Price: 5751.07, Change: 51.13, ChangePercent: 0.9, Series: []float64{5700, 5730, 5720, 5751}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.IndexCard(c, quote, mujica.IndexCardOptions{Clickable: true})
|
||||
if r.Changed() {
|
||||
fmt.Println(quote.Name)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 聚焦卡片或迷你图;Enter/Space 激活卡片,图表方向键读数。"},
|
||||
{ID: "191", Name: "MarketStatus", ZhName: "市场状态", Category: "Markets", Demo: marketsMarketStatusDemo, Code: `now := time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC)
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.MarketStatus(c, mujica.MarketOpen, mujica.MarketStatusOptions{Market: "NYSE", Now: now, Next: now.Add(5 * time.Hour), NextPhase: mujica.MarketAfterHours})
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "展示组件。"},
|
||||
{ID: "192", Name: "TradingSessionClock", ZhName: "交易时段时钟", Category: "Markets", Demo: marketsTradingSessionClockDemo, Code: `now := time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC)
|
||||
markets := []mujica.SessionMarket{{Name: "NYSE", Location: time.FixedZone("EDT", -4*3600), Sessions: []mujica.TradingSession{{Phase: mujica.MarketOpen, Start: 570, End: 960}}}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.TradingSessionClock(c, markets, mujica.TradingSessionClockOptions{Now: now})
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "展示组件。"},
|
||||
{ID: "193", Name: "EconomicCalendar", ZhName: "经济日历", Category: "Markets", Demo: marketsEconomicCalendarDemo, Code: `var state mujica.EconomicCalendarState
|
||||
rows := []mujica.EconomicEvent{{ID: "jobs", Country: "US", Title: "Payrolls", Time: time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC), Importance: 3, Forecast: "150K", Previous: "142K", Actual: "254K"}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.EconomicCalendar(c, &state, rows, mujica.EconomicCalendarOptions{})
|
||||
if r.Changed() || r.Submitted() || r.SortChanged() || r.Retried() {
|
||||
fmt.Println(state.Table.Selected())
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 到达国家过滤、重要性和表格;方向键选择;Enter 提交行。"},
|
||||
{ID: "194", Name: "EarningsCalendar", ZhName: "财报日历", Category: "Markets", Demo: marketsEarningsCalendarDemo, Code: `var state mujica.DataTableState[string]
|
||||
rows := []mujica.EarningsEvent{{ID: "a", Symbol: "AAPL", Name: "Apple", Date: mujica.Date{Year: 2026, Month: 10, Day: 30}, Timing: mujica.EarningsAfterClose, EPSEstimate: 1.6, EPSActual: 1.64}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.EarningsCalendar(c, &state, rows, mujica.EarningsCalendarOptions{})
|
||||
if r.Changed() || r.Submitted() || r.SortChanged() || r.Retried() {
|
||||
fmt.Println(state.Selected())
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 聚焦表格;方向键及 Home/End 选择;Enter 提交;表头支持键盘排序。"},
|
||||
{ID: "195", Name: "NewsFeed", ZhName: "新闻流", Category: "Markets", Demo: marketsNewsFeedDemo, Code: `var state mujica.NewsFeedState
|
||||
rows := []mujica.NewsItem{{ID: "a", Title: "Apple unveils new chips", Source: "Wire", Time: time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC), Symbols: []string{"AAPL"}}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.NewsFeed(c, &state, rows, mujica.NewsFeedOptions{})
|
||||
if r.Opened != "" || r.Symbol != "" || r.Retry {
|
||||
fmt.Println(r.Opened, r.Symbol)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 聚焦列表或相关代码;上下选择;Enter 打开并标记已读。"},
|
||||
{ID: "196", Name: "SentimentGauge", ZhName: "情绪仪表", Category: "Markets", Demo: marketsSentimentGaugeDemo, Code: `value := 72.0
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.SentimentGauge(c, value, mujica.SentimentGaugeOptions{Label: "Fear & Greed"})
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "展示组件,数值与文字同时呈现。"},
|
||||
{ID: "197", Name: "PortfolioSummary", ZhName: "资产总览", Category: "Markets", Demo: marketsPortfolioSummaryDemo, Code: `account := ""
|
||||
accounts := []mujica.PortfolioAccount{{ID: "a", Name: "Brokerage", Total: 120000, DayPnL: 1250, TotalReturn: 20000, Cost: 100000}, {ID: "b", Name: "Retirement", Total: 80000, DayPnL: -300, TotalReturn: 5000, Cost: 75000}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.PortfolioSummary(c, &account, accounts, mujica.PortfolioSummaryOptions{})
|
||||
if r.Changed() {
|
||||
fmt.Println(account)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 聚焦账户组;方向键切换账户。"},
|
||||
{ID: "198", Name: "AssetAllocationChart", ZhName: "资产配置图", Category: "Markets", Demo: marketsAssetAllocationChartDemo, Code: `items := []mujica.AllocationItem{{Name: "Stocks", Value: 60, Target: 50}, {Name: "Bonds", Value: 30, Target: 40}, {Name: "Cash", Value: 10, Target: 10}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.AssetAllocationChart(c, items, mujica.AssetAllocationChartOptions{Frame: mujica.ChartFrame{Label: "Allocation"}})
|
||||
if r.Changed() || r.Retried() {
|
||||
fmt.Println(r.Point)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 聚焦环形图;方向键逐项读数;Home/End 到首尾。"},
|
||||
{ID: "199", Name: "PerformanceChart", ZhName: "收益曲线", Category: "Markets", Demo: marketsPerformanceChartDemo, Code: `values := []float64{100, 110, 95, 120}
|
||||
benchmark := []float64{100, 105, 108, 112}
|
||||
times := []time.Time{time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC), time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC), time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC)}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.PerformanceChart(c, values, mujica.PerformanceChartOptions{Frame: mujica.ChartFrame{Label: "Performance"}, Times: times, Benchmark: benchmark})
|
||||
if r.Changed() || r.Retried() {
|
||||
fmt.Println(r.Point)
|
||||
}
|
||||
if name, ok := r.Toggled(); ok {
|
||||
fmt.Println(name)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 聚焦图和图例;方向键读数;Space 切换序列。"},
|
||||
{ID: "200", Name: "CurrencyConverter", ZhName: "货币换算", Category: "Markets", Demo: marketsCurrencyConverterDemo, Code: `state := mujica.CurrencyConverterState{Amount: 100, From: "USD", To: "JPY"}
|
||||
currencies := []mujica.Currency{{Code: "USD", Name: "Dollar", Decimals: 2}, {Code: "JPY", Name: "Yen", Decimals: 0}}
|
||||
rate := 149.37
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.CurrencyConverter(c, &state, currencies, mujica.CurrencyConverterOptions{Rate: rate})
|
||||
if r.PairChanged {
|
||||
rate = 0
|
||||
fmt.Println(state.From, state.To)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 960, 440, 1)
|
||||
`, Keys: "Tab 到达金额、币种和互换;方向键步进、选择;Enter/Space 互换。"},
|
||||
}
|
||||
|
||||
func marketsDataState(c *ui.Context, id string, log func(string)) (mujica.DataStatus, bool, string) {
|
||||
state := ui.Local(c.Root(), id+".status", func() int { return 0 })
|
||||
if mujica.RadioGroup(c, state, []mujica.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
|
||||
log(fmt.Sprint("status ", *state))
|
||||
}
|
||||
switch *state {
|
||||
case 1:
|
||||
return mujica.DataLoading, false, ""
|
||||
case 2:
|
||||
return mujica.DataReady, true, ""
|
||||
case 3:
|
||||
return mujica.DataFailed, false, "Sample failed"
|
||||
}
|
||||
return mujica.DataReady, false, ""
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/egoist/mygo/ui"
|
||||
mujica "mujicaui"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func marketsOptionChainDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "181.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
state := ui.Local(c.Root(), "181.state", func() mujica.OptionChainState { return mujica.OptionChainState{} })
|
||||
rows := *ui.Local(c.Root(), "markets.OptionChain.rows.pages_markets_1", func() []mujica.OptionStrike {
|
||||
return []mujica.OptionStrike{{Strike: 100, Call: mujica.OptionQuote{Bid: 3, Ask: 3.2, Last: 3.1}, Put: mujica.OptionQuote{Bid: 2, Ask: 2.2, Last: 2.1}}}
|
||||
})
|
||||
|
||||
precision := ui.Local(c.Root(), "markets.OptionChain.precision", func() float64 { return 2 })
|
||||
if mujica.Slider(c, precision, mujica.SliderOptions{Min: 1, Max: 8, Step: 1, Label: "Decimals", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprint("decimals ", *precision))
|
||||
}
|
||||
spot := ui.Local(c.Root(), "181.spot", func() float64 { return 100.4 })
|
||||
if mujica.Slider(c, spot, mujica.SliderOptions{Min: 95, Max: 105, Step: 0.1, Label: "Spot", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprint("spot ", *spot))
|
||||
}
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.OptionChain(c, state, []string{"Oct 17", "Nov 21"}, rows, mujica.OptionChainOptions{Decimals: int(*precision), Spot: *spot})
|
||||
if r.Changed || r.Submitted || r.ExpiryChanged {
|
||||
log(fmt.Sprint(state.Expiry, state.Strike, state.Side))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func marketsGreeksTableDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "182.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
rows := *ui.Local(c.Root(), "markets.GreeksTable.rows.pages_markets_1", func() []mujica.GreeksRow {
|
||||
return []mujica.GreeksRow{{Label: "AAPL 100 C", Greeks: mujica.OptionGreeks{Delta: 0.5, Gamma: 0.03, Theta: -0.1, Vega: 0.2, Rho: 0.05}}}
|
||||
})
|
||||
|
||||
precision := ui.Local(c.Root(), "markets.GreeksTable.precision", func() float64 { return 2 })
|
||||
if mujica.Slider(c, precision, mujica.SliderOptions{Min: 1, Max: 8, Step: 1, Label: "Decimals", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprint("decimals ", *precision))
|
||||
}
|
||||
notes := ui.Local(c.Root(), "182.notes", func() bool { return true })
|
||||
if mujica.Checkbox(c, notes, "Notes", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("notes ", *notes))
|
||||
}
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
mujica.GreeksTable(c, rows, mujica.GreeksTableOptions{Decimals: int(*precision), Notes: *notes})
|
||||
})
|
||||
}
|
||||
|
||||
func marketsPayoffDiagramDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "183.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
legs := *ui.Local(c.Root(), "markets.PayoffDiagram.legs.pages_markets_1", func() []mujica.OptionLeg {
|
||||
return []mujica.OptionLeg{{Kind: mujica.LegCall, Strike: 100, Premium: 3, Quantity: 1}}
|
||||
})
|
||||
|
||||
status := ui.Local(c.Root(), "183.status", func() int { return 0 })
|
||||
if mujica.RadioGroup(c, status, []mujica.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
|
||||
log(fmt.Sprint("status ", *status))
|
||||
}
|
||||
frame := mujica.ChartFrame{Label: "PayoffDiagram"}
|
||||
if *status == 1 {
|
||||
frame.Status = mujica.DataLoading
|
||||
}
|
||||
if *status == 3 {
|
||||
frame.Status = mujica.DataFailed
|
||||
frame.Error = "Sample failed"
|
||||
}
|
||||
if *status == 2 {
|
||||
legs = nil
|
||||
}
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.PayoffDiagram(c, legs, mujica.PayoffDiagramOptions{Frame: frame, Spot: 103})
|
||||
if r.Changed() || r.Retried() {
|
||||
log(fmt.Sprint(r.Point))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func marketsWatchlistDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "184.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
state := ui.Local(c.Root(), "184.state", func() mujica.WatchlistState { return mujica.WatchlistState{} })
|
||||
groups := *ui.Local(c.Root(), "markets.Watchlist.groups.pages_markets_1", func() []mujica.WatchGroup {
|
||||
return []mujica.WatchGroup{{Name: "Technology", Items: []mujica.WatchItem{{Symbol: "AAPL", Name: "Apple", Price: 227.5, Change: 2.7, ChangePercent: 1.2}, {Symbol: "MSFT", Name: "Microsoft", Price: 418.2, Change: -2.5, ChangePercent: -0.6}}}, {Name: "Energy", Items: []mujica.WatchItem{{Symbol: "XOM", Price: 117.4}}}}
|
||||
})
|
||||
|
||||
precision := ui.Local(c.Root(), "markets.Watchlist.precision", func() float64 { return 2 })
|
||||
if mujica.Slider(c, precision, mujica.SliderOptions{Min: 1, Max: 8, Step: 1, Label: "Decimals", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprint("decimals ", *precision))
|
||||
}
|
||||
if mujica.Button(c, "Refresh quotes", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
|
||||
groups[0].Items = slices.Clone(groups[0].Items)
|
||||
groups[0].Items[0].Price += 0.1
|
||||
log("quotes refreshed")
|
||||
}
|
||||
beforeGroup, beforeColumns := state.Group, fmt.Sprint(state.Hidden)
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.Watchlist(c, state, groups, mujica.WatchlistOptions{Decimals: int(*precision)})
|
||||
if r.Changed || r.Submitted || r.Moved {
|
||||
log(fmt.Sprint(state.Selected, r.Rows, r.To))
|
||||
}
|
||||
if r.Moved {
|
||||
items := groups[state.Group].Items
|
||||
var moving []mujica.WatchItem
|
||||
var rest []mujica.WatchItem
|
||||
at := r.To
|
||||
for j, item := range items {
|
||||
if slices.Contains(r.Rows, j) {
|
||||
moving = append(moving, item)
|
||||
if j < r.To {
|
||||
at--
|
||||
}
|
||||
} else {
|
||||
rest = append(rest, item)
|
||||
}
|
||||
}
|
||||
groups[state.Group].Items = slices.Insert(rest, at, moving...)
|
||||
}
|
||||
})
|
||||
if state.Group != beforeGroup || fmt.Sprint(state.Hidden) != beforeColumns {
|
||||
log(fmt.Sprint("group/columns ", state.Group, state.Hidden))
|
||||
}
|
||||
}
|
||||
|
||||
func marketsSymbolSearchDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "185.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
state := ui.Local(c.Root(), "185.state", func() mujica.SymbolSearchState { return mujica.SymbolSearchState{} })
|
||||
rows := *ui.Local(c.Root(), "markets.SymbolSearch.rows.pages_markets_1", func() []mujica.MarketSymbol {
|
||||
return []mujica.MarketSymbol{{Code: "AAPL", Name: "Apple", Market: "NASDAQ", Type: "Stock"}}
|
||||
})
|
||||
|
||||
status, empty, err := marketsDataState(c, "185", log)
|
||||
if empty || status != mujica.DataReady {
|
||||
rows = nil
|
||||
}
|
||||
results := []mujica.MarketSymbol{}
|
||||
for _, row := range rows {
|
||||
if (state.Market == "" || state.Market == row.Market) && strings.Contains(strings.ToUpper(row.Code+" "+row.Name), strings.ToUpper(state.Query)) {
|
||||
results = append(results, row)
|
||||
}
|
||||
}
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.SymbolSearch(c, state, results, mujica.SymbolSearchOptions{Loading: status == mujica.DataLoading, Error: err, Markets: []string{"NASDAQ", "NYSE"}, Recent: rows})
|
||||
if r.Changed || r.Picked || r.Retry {
|
||||
log(fmt.Sprint(state.Query, state.Market, r.Chosen.Code))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/egoist/mygo/ui"
|
||||
mujica "mujicaui"
|
||||
)
|
||||
|
||||
func marketsSymbolBadgeDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "186.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
symbol := *ui.Local(c.Root(), "markets.SymbolBadge.symbol.pages_markets_2", func() mujica.MarketSymbol {
|
||||
return mujica.MarketSymbol{Code: "AAPL", Name: "Apple", Market: "NASDAQ", Type: "Stock"}
|
||||
})
|
||||
|
||||
kind := ui.Local(c.Root(), "186.kind", func() string { return "Stock" })
|
||||
if mujica.RadioGroup(c, kind, []mujica.RadioItem[string]{{Value: "Stock", Label: "Stock"}, {Value: "ETF", Label: "ETF"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
|
||||
log(*kind)
|
||||
}
|
||||
symbol.Type = *kind
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.SymbolBadge(c, symbol, mujica.SymbolBadgeOptions{Clickable: true})
|
||||
if r.Changed() {
|
||||
log(fmt.Sprint(symbol.Code))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func marketsMarketHeatmapDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "187.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
path := ui.Local(c.Root(), "187.path", func() []string { return []string{} })
|
||||
root := *ui.Local(c.Root(), "markets.MarketHeatmap.root.pages_markets_2", func() mujica.HeatNode {
|
||||
return mujica.HeatNode{Name: "Market", Children: []mujica.HeatNode{{Name: "Technology", Children: []mujica.HeatNode{{Name: "AAPL", Cap: 3000, Change: 1.2}, {Name: "MSFT", Cap: 2800, Change: -0.6}}}, {Name: "Energy", Cap: 450, Change: -2.1}}}
|
||||
})
|
||||
|
||||
status := ui.Local(c.Root(), "187.status", func() int { return 0 })
|
||||
if mujica.RadioGroup(c, status, []mujica.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
|
||||
log(fmt.Sprint("status ", *status))
|
||||
}
|
||||
frame := mujica.ChartFrame{Label: "MarketHeatmap"}
|
||||
if *status == 1 {
|
||||
frame.Status = mujica.DataLoading
|
||||
}
|
||||
if *status == 3 {
|
||||
frame.Status = mujica.DataFailed
|
||||
frame.Error = "Sample failed"
|
||||
}
|
||||
if *status == 2 {
|
||||
root.Children = nil
|
||||
if len(*path) > 0 {
|
||||
*path = nil
|
||||
log("path reset: empty data")
|
||||
}
|
||||
}
|
||||
scale := ui.Local(c.Root(), "187.scale", func() float64 { return 3 })
|
||||
if mujica.Slider(c, scale, mujica.SliderOptions{Min: 1, Max: 10, Step: 1, Label: "Color scale", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprint(*scale))
|
||||
}
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.MarketHeatmap(c, path, root, mujica.MarketHeatmapOptions{Frame: frame, Scale: *scale})
|
||||
if r.Changed() || r.ValueChanged() || r.Retried() {
|
||||
log(fmt.Sprint(path, r.Point))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func marketsScreenerDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "188.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
state := ui.Local(c.Root(), "188.state", func() mujica.ScreenerState { return mujica.ScreenerState{} })
|
||||
fields := *ui.Local(c.Root(), "markets.Screener.fields.pages_markets_2", func() []mujica.ScreenerField { return []mujica.ScreenerField{{ID: "pe", Label: "P/E", Decimals: 1}} })
|
||||
rows := *ui.Local(c.Root(), "markets.Screener.rows.pages_markets_2", func() []mujica.ScreenerRow {
|
||||
return []mujica.ScreenerRow{{Symbol: "AAPL", Values: map[string]float64{"pe": 34}}, {Symbol: "XOM", Values: map[string]float64{"pe": 13}}}
|
||||
})
|
||||
|
||||
status, empty, err := marketsDataState(c, "188", log)
|
||||
if empty || status != mujica.DataReady {
|
||||
rows = nil
|
||||
}
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.Screener(c, state, fields, rows, mujica.ScreenerOptions{Status: status, Error: err})
|
||||
if r.Changed || r.Save || r.Table.Changed() || r.Table.Submitted() || r.Table.SortChanged() || r.Table.Retried() {
|
||||
log(fmt.Sprint(state.Conditions))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func marketsMarketOverviewDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "189.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
indices := *ui.Local(c.Root(), "markets.MarketOverview.indices.pages_markets_2", func() []mujica.IndexQuote {
|
||||
return []mujica.IndexQuote{{Name: "S&P 500", Price: 5751.07, Change: 51.13, ChangePercent: 0.9, Series: []float64{5700, 5730, 5720, 5751}}}
|
||||
})
|
||||
|
||||
advancing := ui.Local(c.Root(), "189.advancing", func() float64 { return 2100 })
|
||||
if mujica.Slider(c, advancing, mujica.SliderOptions{Min: 0, Max: 4000, Step: 100, Label: "Advancing", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprint(*advancing))
|
||||
}
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.MarketOverview(c, indices, mujica.MarketOverviewOptions{Breadth: mujica.MarketBreadth{Advancing: int(*advancing), Declining: 1400, Unchanged: 300}})
|
||||
if r.Chosen >= 0 {
|
||||
log(fmt.Sprint(indices[r.Chosen].Name))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func marketsIndexCardDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "190.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
quote := *ui.Local(c.Root(), "markets.IndexCard.quote.pages_markets_2", func() mujica.IndexQuote {
|
||||
return mujica.IndexQuote{Name: "S&P 500", Price: 5751.07, Change: 51.13, ChangePercent: 0.9, Series: []float64{5700, 5730, 5720, 5751}}
|
||||
})
|
||||
|
||||
precision := ui.Local(c.Root(), "markets.IndexCard.precision", func() float64 { return 2 })
|
||||
if mujica.Slider(c, precision, mujica.SliderOptions{Min: 1, Max: 8, Step: 1, Label: "Decimals", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprint("decimals ", *precision))
|
||||
}
|
||||
price := ui.Local(c.Root(), "190.price", func() float64 { return 5751.07 })
|
||||
if mujica.NumberInput(c, price, mujica.NumberInputOptions{Min: 1, Max: 10000, Step: 0.01, Precision: 2, Label: "Price"}).Changed() {
|
||||
log(fmt.Sprint(*price))
|
||||
}
|
||||
quote.Price = *price
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.IndexCard(c, quote, mujica.IndexCardOptions{Decimals: int(*precision), Clickable: true})
|
||||
if r.Changed() {
|
||||
log(fmt.Sprint(quote.Name))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/egoist/mygo/ui"
|
||||
mujica "mujicaui"
|
||||
"time"
|
||||
)
|
||||
|
||||
func marketsMarketStatusDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "191.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
now := time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC)
|
||||
|
||||
phase := ui.Local(c.Root(), "191.phase", func() mujica.MarketPhase { return mujica.MarketOpen })
|
||||
if mujica.RadioGroup(c, phase, []mujica.RadioItem[mujica.MarketPhase]{{Value: mujica.MarketClosed, Label: "Closed"}, {Value: mujica.MarketPreMarket, Label: "Pre-market"}, {Value: mujica.MarketOpen, Label: "Open"}, {Value: mujica.MarketAfterHours, Label: "After hours"}, {Value: mujica.MarketHoliday, Label: "Holiday"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
|
||||
log(fmt.Sprint(*phase))
|
||||
}
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
mujica.MarketStatus(c, *phase, mujica.MarketStatusOptions{Market: "NYSE", Now: now, Next: now.Add(5 * time.Hour), NextPhase: mujica.MarketAfterHours})
|
||||
})
|
||||
}
|
||||
|
||||
func marketsTradingSessionClockDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "192.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
now := time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC)
|
||||
markets := *ui.Local(c.Root(), "markets.TradingSessionClock.markets.pages_markets_3", func() []mujica.SessionMarket {
|
||||
return []mujica.SessionMarket{{Name: "NYSE", Location: time.FixedZone("EDT", -4*3600), Sessions: []mujica.TradingSession{{Phase: mujica.MarketOpen, Start: 570, End: 960}}}}
|
||||
})
|
||||
|
||||
holiday := ui.Local(c.Root(), "192.holiday", func() bool { return false })
|
||||
if mujica.Checkbox(c, holiday, "Holiday", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint(*holiday))
|
||||
}
|
||||
markets[0].Holiday = *holiday
|
||||
minute := ui.Local(c.Root(), "192.minute", func() float64 { return 870 })
|
||||
if mujica.Slider(c, minute, mujica.SliderOptions{Min: 0, Max: 1439, Step: 1, Label: "UTC minute", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprint(*minute))
|
||||
}
|
||||
now = now.Truncate(24 * time.Hour).Add(time.Duration(*minute) * time.Minute)
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
mujica.TradingSessionClock(c, markets, mujica.TradingSessionClockOptions{Now: now})
|
||||
})
|
||||
}
|
||||
|
||||
func marketsEconomicCalendarDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "193.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
state := ui.Local(c.Root(), "193.state", func() mujica.EconomicCalendarState { return mujica.EconomicCalendarState{} })
|
||||
rows := *ui.Local(c.Root(), "markets.EconomicCalendar.rows.pages_markets_3", func() []mujica.EconomicEvent {
|
||||
return []mujica.EconomicEvent{{ID: "jobs", Country: "US", Title: "Payrolls", Time: time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC), Importance: 3, Forecast: "150K", Previous: "142K", Actual: "254K"}}
|
||||
})
|
||||
|
||||
status, empty, err := marketsDataState(c, "193", log)
|
||||
if empty || status != mujica.DataReady {
|
||||
rows = nil
|
||||
}
|
||||
beforeFilter := fmt.Sprint(state.Countries, state.MinImportance)
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.EconomicCalendar(c, state, rows, mujica.EconomicCalendarOptions{Status: status, Error: err})
|
||||
if r.Changed() || r.Submitted() || r.SortChanged() || r.Retried() {
|
||||
log(fmt.Sprint(state.Table.Selected()))
|
||||
}
|
||||
})
|
||||
if fmt.Sprint(state.Countries, state.MinImportance) != beforeFilter {
|
||||
log(fmt.Sprint("filter ", state.Countries, state.MinImportance))
|
||||
}
|
||||
}
|
||||
|
||||
func marketsEarningsCalendarDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "194.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
state := ui.Local(c.Root(), "194.state", func() mujica.DataTableState[string] { return mujica.DataTableState[string]{} })
|
||||
rows := *ui.Local(c.Root(), "markets.EarningsCalendar.rows.pages_markets_3", func() []mujica.EarningsEvent {
|
||||
return []mujica.EarningsEvent{{ID: "a", Symbol: "AAPL", Name: "Apple", Date: mujica.Date{Year: 2026, Month: 10, Day: 30}, Timing: mujica.EarningsAfterClose, EPSEstimate: 1.6, EPSActual: 1.64}}
|
||||
})
|
||||
|
||||
status, empty, err := marketsDataState(c, "194", log)
|
||||
if empty || status != mujica.DataReady {
|
||||
rows = nil
|
||||
}
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.EarningsCalendar(c, state, rows, mujica.EarningsCalendarOptions{Status: status, Error: err})
|
||||
if r.Changed() || r.Submitted() || r.SortChanged() || r.Retried() {
|
||||
log(fmt.Sprint(state.Selected()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func marketsNewsFeedDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "195.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
state := ui.Local(c.Root(), "195.state", func() mujica.NewsFeedState { return mujica.NewsFeedState{} })
|
||||
rows := *ui.Local(c.Root(), "markets.NewsFeed.rows.pages_markets_3", func() []mujica.NewsItem {
|
||||
return []mujica.NewsItem{{ID: "a", Title: "Apple unveils new chips", Source: "Wire", Time: time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC), Symbols: []string{"AAPL"}}}
|
||||
})
|
||||
|
||||
status, empty, err := marketsDataState(c, "195", log)
|
||||
if empty || status != mujica.DataReady {
|
||||
rows = nil
|
||||
}
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.NewsFeed(c, state, rows, mujica.NewsFeedOptions{Status: status, Error: err})
|
||||
if r.Opened != "" || r.Symbol != "" || r.Retry || r.ReadChanged {
|
||||
log(fmt.Sprint(r.Opened, r.Symbol))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/egoist/mygo/ui"
|
||||
mujica "mujicaui"
|
||||
"time"
|
||||
)
|
||||
|
||||
func marketsSentimentGaugeDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "196.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
value := 72.0
|
||||
|
||||
level := ui.Local(c.Root(), "196.value", func() float64 { return 72 })
|
||||
if mujica.Slider(c, level, mujica.SliderOptions{Min: 0, Max: 100, Step: 1, Label: "Sentiment", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprint(*level))
|
||||
}
|
||||
value = *level
|
||||
status, empty, err := marketsDataState(c, "196", log)
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
mujica.SentimentGauge(c, value, mujica.SentimentGaugeOptions{Label: "Fear & Greed", Status: status, Empty: empty, Error: err, Retry: func() { log("retry requested") }})
|
||||
})
|
||||
}
|
||||
|
||||
func marketsPortfolioSummaryDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "197.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
account := ui.Local(c.Root(), "197.account", func() string { return "" })
|
||||
accounts := *ui.Local(c.Root(), "markets.PortfolioSummary.accounts.pages_markets_4", func() []mujica.PortfolioAccount {
|
||||
return []mujica.PortfolioAccount{{ID: "a", Name: "Brokerage", Total: 120000, DayPnL: 1250, TotalReturn: 20000, Cost: 100000}, {ID: "b", Name: "Retirement", Total: 80000, DayPnL: -300, TotalReturn: 5000, Cost: 75000}}
|
||||
})
|
||||
|
||||
assets := ui.Local(c.Root(), "197.assets", func() float64 { return 120000 })
|
||||
if mujica.NumberInput(c, assets, mujica.NumberInputOptions{Min: 0, Max: 1000000, Step: 100, Precision: 2, Label: "Brokerage assets"}).Changed() {
|
||||
log(fmt.Sprint(*assets))
|
||||
}
|
||||
accounts[0].Total = *assets
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.PortfolioSummary(c, account, accounts, mujica.PortfolioSummaryOptions{})
|
||||
if r.Changed() {
|
||||
log(fmt.Sprint(account))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func marketsAssetAllocationChartDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "198.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
items := *ui.Local(c.Root(), "markets.AssetAllocationChart.items.pages_markets_4", func() []mujica.AllocationItem {
|
||||
return []mujica.AllocationItem{{Name: "Stocks", Value: 60, Target: 50}, {Name: "Bonds", Value: 30, Target: 40}, {Name: "Cash", Value: 10, Target: 10}}
|
||||
})
|
||||
|
||||
status := ui.Local(c.Root(), "198.status", func() int { return 0 })
|
||||
if mujica.RadioGroup(c, status, []mujica.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
|
||||
log(fmt.Sprint("status ", *status))
|
||||
}
|
||||
frame := mujica.ChartFrame{Label: "AssetAllocationChart"}
|
||||
if *status == 1 {
|
||||
frame.Status = mujica.DataLoading
|
||||
}
|
||||
if *status == 3 {
|
||||
frame.Status = mujica.DataFailed
|
||||
frame.Error = "Sample failed"
|
||||
}
|
||||
if *status == 2 {
|
||||
items = nil
|
||||
}
|
||||
tolerance := ui.Local(c.Root(), "198.tolerance", func() float64 { return 5 })
|
||||
if mujica.Slider(c, tolerance, mujica.SliderOptions{Min: 1, Max: 15, Step: 1, Label: "Drift tolerance", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprint(*tolerance))
|
||||
}
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.AssetAllocationChart(c, items, mujica.AssetAllocationChartOptions{Frame: frame, Tolerance: *tolerance})
|
||||
if r.Changed() || r.Retried() {
|
||||
log(fmt.Sprint(r.Point))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func marketsPerformanceChartDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "199.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
values := *ui.Local(c.Root(), "markets.PerformanceChart.values.pages_markets_4", func() []float64 { return []float64{100, 110, 95, 120} })
|
||||
benchmark := *ui.Local(c.Root(), "markets.PerformanceChart.benchmark.pages_markets_4", func() []float64 { return []float64{100, 105, 108, 112} })
|
||||
times := *ui.Local(c.Root(), "markets.PerformanceChart.times.pages_markets_4", func() []time.Time {
|
||||
return []time.Time{time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC), time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC), time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC)}
|
||||
})
|
||||
|
||||
status := ui.Local(c.Root(), "199.status", func() int { return 0 })
|
||||
if mujica.RadioGroup(c, status, []mujica.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
|
||||
log(fmt.Sprint("status ", *status))
|
||||
}
|
||||
frame := mujica.ChartFrame{Label: "PerformanceChart"}
|
||||
if *status == 1 {
|
||||
frame.Status = mujica.DataLoading
|
||||
}
|
||||
if *status == 3 {
|
||||
frame.Status = mujica.DataFailed
|
||||
frame.Error = "Sample failed"
|
||||
}
|
||||
if *status == 2 {
|
||||
values = nil
|
||||
benchmark = nil
|
||||
times = nil
|
||||
}
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.PerformanceChart(c, values, mujica.PerformanceChartOptions{Frame: frame, Times: times, Benchmark: benchmark})
|
||||
if r.Changed() || r.Retried() {
|
||||
log(fmt.Sprint(r.Point))
|
||||
}
|
||||
if name, ok := r.Toggled(); ok {
|
||||
log(fmt.Sprint(name))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func marketsCurrencyConverterDemo(c *ui.Context, log func(string)) {
|
||||
disabled := ui.Local(c.Root(), "200.disabled", func() bool { return false })
|
||||
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprint("disabled ", *disabled))
|
||||
}
|
||||
state := ui.Local(c.Root(), "200.state", func() mujica.CurrencyConverterState {
|
||||
return mujica.CurrencyConverterState{Amount: 100, From: "USD", To: "JPY"}
|
||||
})
|
||||
currencies := *ui.Local(c.Root(), "markets.CurrencyConverter.currencies.pages_markets_4", func() []mujica.Currency {
|
||||
return []mujica.Currency{{Code: "USD", Name: "Dollar", Decimals: 2}, {Code: "JPY", Name: "Yen", Decimals: 0}}
|
||||
})
|
||||
rate := ui.Local(c.Root(), "200.rate", func() float64 { return 149.37 })
|
||||
|
||||
if mujica.NumberInput(c, rate, mujica.NumberInputOptions{Min: 0, Max: 1000, Step: 0.0001, Precision: 4, Label: "Caller rate"}).Changed() {
|
||||
log(fmt.Sprint("rate ", *rate))
|
||||
}
|
||||
if mujica.Button(c, "Load pair rate", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
|
||||
*rate = 149.37
|
||||
if state.From == "JPY" {
|
||||
*rate = 1 / 149.37
|
||||
}
|
||||
log("pair rate loaded")
|
||||
}
|
||||
beforeAmount := state.Amount
|
||||
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
|
||||
r := mujica.CurrencyConverter(c, state, currencies, mujica.CurrencyConverterOptions{Rate: *rate, RateFrom: state.From, RateTo: state.To})
|
||||
if r.PairChanged {
|
||||
*rate = 0
|
||||
log(fmt.Sprint(state.From, state.To))
|
||||
}
|
||||
})
|
||||
if state.Amount != beforeAmount {
|
||||
log(fmt.Sprint("amount ", state.Amount))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/egoist/mygo/ui"
|
||||
mujica "mujicaui"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func marketsDemo(t *testing.T, id string) (*ui.Tester, *[]string) {
|
||||
t.Helper()
|
||||
i := slices.IndexFunc(marketsPages, func(p page) bool { return p.ID == id })
|
||||
if i < 0 {
|
||||
t.Fatalf("missing page %s", id)
|
||||
}
|
||||
events := &[]string{}
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
ui.Scroll(c).Fill().Padding(24).Children(func() { marketsPages[i].Demo(c, func(s string) { *events = append(*events, s) }) })
|
||||
}, 1000, 800)
|
||||
tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1})
|
||||
tt.Frame()
|
||||
return tt, events
|
||||
}
|
||||
|
||||
func TestMarketsGalleryControls(t *testing.T) {
|
||||
if len(marketsPages) != 20 {
|
||||
t.Fatalf("pages %d", len(marketsPages))
|
||||
}
|
||||
for _, p := range marketsPages {
|
||||
t.Run(p.ID, func(t *testing.T) {
|
||||
if p.Category != "Markets" || p.Code == "" || p.Keys == "" {
|
||||
t.Fatalf("incomplete page %+v", p)
|
||||
}
|
||||
tt, events := marketsDemo(t, p.ID)
|
||||
if err := tt.Click("Disabled"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(*events) == 0 || !strings.Contains((*events)[len(*events)-1], "disabled true") {
|
||||
t.Fatalf("toggle events %v", *events)
|
||||
}
|
||||
_ = tt.Click("Disabled")
|
||||
if tt.HasText("Error") {
|
||||
_ = tt.Click("Error")
|
||||
if !tt.HasText("Sample failed") {
|
||||
t.Fatalf("error state %q", tt.Texts())
|
||||
}
|
||||
_ = tt.Click("Empty")
|
||||
_ = tt.Click("Ready")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarketsGalleryInteractionLogs(t *testing.T) {
|
||||
for _, tc := range []struct{ id, label string }{{"186", "AAPL"}, {"188", "Add condition"}, {"189", "S&P 500"}, {"190", "S&P 500"}, {"195", "Mark all read"}, {"197", "Retirement"}, {"200", "Swap currencies"}} {
|
||||
tt, events := marketsDemo(t, tc.id)
|
||||
before := len(*events)
|
||||
if err := tt.Click(tc.label); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(*events) == before {
|
||||
t.Fatalf("%s %s not logged", tc.id, tc.label)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user