Trading: components 161-180
This commit is contained in:
105 files changed
+6042
-1
No files matched your search
@@ -1,3 +1,276 @@
|
||||
package main
|
||||
|
||||
var tradingPages = []page{}
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
mujica "mujicaui"
|
||||
)
|
||||
|
||||
const tradingCategory = "Trading"
|
||||
|
||||
// tradingTrend shows and applies the rise-color switch.
|
||||
func tradingTrend(c *ui.Context, redUp *bool, log func(string)) {
|
||||
if mujica.Checkbox(c, redUp, "Red means up (China)", mujica.CheckboxOptions{}).Changed() {
|
||||
log(fmt.Sprintf("red up %v", *redUp))
|
||||
}
|
||||
t := mujica.GreenUp
|
||||
if *redUp {
|
||||
t = mujica.RedUp
|
||||
}
|
||||
mujica.UseTrend(c, t)
|
||||
}
|
||||
|
||||
// tradingAction is a demo button at its natural width.
|
||||
func tradingAction(c *ui.Context, label string) bool {
|
||||
clicked := false
|
||||
ui.Row(c).Children(func() {
|
||||
clicked = mujica.Button(c, label, mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked()
|
||||
})
|
||||
return clicked
|
||||
}
|
||||
|
||||
// tradingBook is a deterministic book around mid, moved by step.
|
||||
func tradingBook(mid float64, step int) mujica.OrderBookData {
|
||||
var b mujica.OrderBookData
|
||||
for i := range 12 {
|
||||
w := float64((i*7+step*3)%11 + 1)
|
||||
b.Bids = append(b.Bids, mujica.BookLevel{Price: math.Round((mid-0.01*float64(i+1))*100) / 100, Size: w * 10})
|
||||
b.Asks = append(b.Asks, mujica.BookLevel{Price: math.Round((mid+0.01*float64(i))*100) / 100, Size: float64((i*5+step)%9+1) * 10})
|
||||
}
|
||||
b.Version = step
|
||||
return b
|
||||
}
|
||||
|
||||
var tradingPages = []page{
|
||||
{ID: "161", Name: "TickerTape", ZhName: "行情滚动条", Category: tradingCategory, Code: tradingCode["TickerTape"], Demo: tradingTickerDemo,
|
||||
Keys: "Tab focuses the tape, which pauses it. With reduced motion, Left and Right turn pages."},
|
||||
{ID: "162", Name: "QuoteCard", ZhName: "报价卡片", Category: tradingCategory, Code: tradingCode["QuoteCard"], Demo: tradingQuoteDemo,
|
||||
Keys: "The card takes no focus; controls in its chart slot do."},
|
||||
{ID: "163", Name: "PriceText", ZhName: "价格文本", Category: tradingCategory, Code: tradingCode["PriceText"], Demo: tradingPriceDemo,
|
||||
Keys: "Display only."},
|
||||
{ID: "164", Name: "PriceChangeBadge", ZhName: "涨跌徽标", Category: tradingCategory, Code: tradingCode["PriceChangeBadge"], Demo: tradingBadgeDemo,
|
||||
Keys: "Display only; the name reads the direction and values."},
|
||||
{ID: "165", Name: "OrderBook", ZhName: "盘口", Category: tradingCategory, Code: tradingCode["OrderBook"], Demo: tradingBookDemo,
|
||||
Keys: "Tab into the levels, Up and Down move, Enter picks a price."},
|
||||
{ID: "166", Name: "Level2Quotes", ZhName: "多档行情", Category: tradingCategory, Code: tradingCode["Level2Quotes"], Demo: tradingLevel2Demo,
|
||||
Keys: "Display only; each row reads its level, price, size and orders."},
|
||||
{ID: "167", Name: "TimeAndSales", ZhName: "逐笔成交", Category: tradingCategory, Code: tradingCode["TimeAndSales"], Demo: tradingTapeDemo,
|
||||
Keys: "Tab to Pause and the list; Up and Down move in the list."},
|
||||
{ID: "168", Name: "DepthLadder", ZhName: "价格阶梯", Category: tradingCategory, Code: tradingCode["DepthLadder"], Demo: tradingLadderDemo,
|
||||
Keys: "Arrows move between cells, Enter orders, Page Up and Page Down scroll, the wheel steps a tick."},
|
||||
{ID: "169", Name: "SpreadIndicator", ZhName: "价差指示", Category: tradingCategory, Code: tradingCode["SpreadIndicator"], Demo: tradingSpreadDemo,
|
||||
Keys: "Display only."},
|
||||
{ID: "170", Name: "BidAskBar", ZhName: "买卖力量条", Category: tradingCategory, Code: tradingCode["BidAskBar"], Demo: tradingBarDemo,
|
||||
Keys: "Display only; read as a meter."},
|
||||
{ID: "171", Name: "OrderEntry", ZhName: "下单面板", Category: tradingCategory, Code: tradingCode["OrderEntry"], Demo: tradingEntryDemo,
|
||||
Keys: "Tab through side, type and fields; Up and Down step numbers; a failed submit focuses the first error."},
|
||||
{ID: "172", Name: "QuickTradeButtons", ZhName: "快速交易按钮", Category: tradingCategory, Code: tradingCode["QuickTradeButtons"], Demo: tradingQuickDemo,
|
||||
Keys: "Enter or Space trades; with Confirm, Enter in the popup cancels and Escape closes it."},
|
||||
{ID: "173", Name: "PositionTable", ZhName: "持仓表", Category: tradingCategory, Code: tradingCode["PositionTable"], Demo: tradingPositionsDemo,
|
||||
Keys: "DataTable keys; Tab reaches each Close button."},
|
||||
{ID: "174", Name: "OrderTable", ZhName: "委托表", Category: tradingCategory, Code: tradingCode["OrderTable"], Demo: tradingOrdersDemo,
|
||||
Keys: "Arrows switch the status filter; DataTable keys; Tab reaches Cancel."},
|
||||
{ID: "175", Name: "TradeHistoryTable", ZhName: "成交记录表", Category: tradingCategory, Code: tradingCode["TradeHistoryTable"], Demo: tradingHistoryDemo,
|
||||
Keys: "Tab to the range picker, Export and the table."},
|
||||
{ID: "176", Name: "PnLDisplay", ZhName: "盈亏显示", Category: tradingCategory, Code: tradingCode["PnLDisplay"], Demo: tradingPnLDemo,
|
||||
Keys: "Tab to the eye button; Enter or Space masks the amounts."},
|
||||
{ID: "177", Name: "LeverageSlider", ZhName: "杠杆滑块", Category: tradingCategory, Code: tradingCode["LeverageSlider"], Demo: tradingLeverageDemo,
|
||||
Keys: "Arrows step levels, Home and End jump; Apply sets the value."},
|
||||
{ID: "178", Name: "MarginIndicator", ZhName: "保证金率指示", Category: tradingCategory, Code: tradingCode["MarginIndicator"], Demo: tradingMarginDemo,
|
||||
Keys: "Display only; read as a meter with its lines."},
|
||||
{ID: "179", Name: "RiskMeter", ZhName: "风险等级", Category: tradingCategory, Code: tradingCode["RiskMeter"], Demo: tradingRiskDemo,
|
||||
Keys: "Display only."},
|
||||
{ID: "180", Name: "OrderConfirmDialog", ZhName: "下单确认对话框", Category: tradingCategory, Code: tradingCode["OrderConfirmDialog"], Demo: tradingConfirmDemo,
|
||||
Keys: "Cancel has the focus; Escape cancels; the backdrop does not close it."},
|
||||
}
|
||||
|
||||
type tradingTickerState struct {
|
||||
redUp bool
|
||||
items []mujica.TickerItem
|
||||
}
|
||||
|
||||
func tradingTickerDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "161", func() tradingTickerState {
|
||||
return tradingTickerState{items: []mujica.TickerItem{{Symbol: "MUJC", Price: 412.08, ChangePercent: -1.35}, {Symbol: "AVE", Price: 88.2, ChangePercent: 2.1}, {Symbol: "OBLV", Price: 1204.5, ChangePercent: 0.4},
|
||||
{Symbol: "DOLO", Price: 7.25, ChangePercent: -0.8}, {Symbol: "MORT", Price: 33.1, ChangePercent: 1.2}, {Symbol: "TIMR", Price: 61.9, ChangePercent: -2.3}}}
|
||||
})
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
if tradingAction(c, "Move prices") {
|
||||
for i := range d.items {
|
||||
d.items[i].ChangePercent = -d.items[i].ChangePercent
|
||||
}
|
||||
log("prices moved")
|
||||
}
|
||||
ui.Box(c).Width(560).Children(func() { mujica.TickerTape(c, d.items, mujica.TickerTapeOptions{}) })
|
||||
formStates(c, func() {
|
||||
ui.Box(c).Width(280).Children(func() { mujica.TickerTape(c, d.items[:2], mujica.TickerTapeOptions{Speed: 15}) })
|
||||
})
|
||||
}
|
||||
|
||||
type tradingQuoteState struct {
|
||||
redUp bool
|
||||
price float64
|
||||
}
|
||||
|
||||
func tradingQuoteDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "162", func() tradingQuoteState { return tradingQuoteState{price: 412.08} })
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
if mujica.Slider(c, &d.price, mujica.SliderOptions{Min: 380, Max: 440, Step: 0.01, Label: "Price", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprintf("price %.2f", d.price))
|
||||
}
|
||||
change := d.price - 417.7
|
||||
ui.Row(c).Gap(16).Wrap().Children(func() {
|
||||
mujica.QuoteCard(c, mujica.Quote{Symbol: "MUJC", Name: "Ave Mujica Holdings", Price: d.price, Change: change, ChangePercent: change / 417.7 * 100},
|
||||
mujica.QuoteCardOptions{Chart: func() {
|
||||
k := mujica.Tokens(c)
|
||||
ui.Box(c).Fill().Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
p.Line(r.X, r.Y+r.H*0.7, r.X+r.W/2, r.Y+r.H*0.3, 1.5, k.Info)
|
||||
p.Line(r.X+r.W/2, r.Y+r.H*0.3, r.X+r.W, r.Y+r.H*(0.5-float32(change)/40), 1.5, k.Info)
|
||||
})
|
||||
}})
|
||||
mujica.QuoteCard(c, mujica.Quote{Symbol: "AVE", Price: 88.2}, mujica.QuoteCardOptions{})
|
||||
})
|
||||
}
|
||||
|
||||
type tradingPriceState struct {
|
||||
redUp, noFlash, grouping bool
|
||||
price float64
|
||||
}
|
||||
|
||||
func tradingPriceDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "163", func() tradingPriceState { return tradingPriceState{price: 18234.5, grouping: true} })
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
formToggles(c, []string{"Thousands separators", "No flash"}, &d.grouping, &d.noFlash)
|
||||
ui.Row(c).Gap(8).Children(func() {
|
||||
if mujica.Button(c, "Tick up", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
|
||||
d.price += 12.5
|
||||
log(fmt.Sprintf("price %.2f", d.price))
|
||||
}
|
||||
if mujica.Button(c, "Tick down", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
|
||||
d.price -= 12.5
|
||||
log(fmt.Sprintf("price %.2f", d.price))
|
||||
}
|
||||
})
|
||||
mujica.PriceText(c, d.price, mujica.PriceTextOptions{Size: 28, Compare: true, Reference: 18234.5, NoFlash: d.noFlash, NoGrouping: !d.grouping})
|
||||
formStates(c, func() {
|
||||
mujica.PriceText(c, 0.004521, mujica.PriceTextOptions{Decimals: 6})
|
||||
mujica.PriceText(c, 100, mujica.PriceTextOptions{Compare: true, Reference: 100})
|
||||
})
|
||||
}
|
||||
|
||||
type tradingBadgeState struct {
|
||||
redUp, hidePercent bool
|
||||
change float64
|
||||
}
|
||||
|
||||
func tradingBadgeDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "164", func() tradingBadgeState { return tradingBadgeState{change: 1.25} })
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
formToggles(c, []string{"Hide percent"}, &d.hidePercent)
|
||||
if mujica.Slider(c, &d.change, mujica.SliderOptions{Min: -5, Max: 5, Step: 0.05, Label: "Change", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprintf("change %.2f", d.change))
|
||||
}
|
||||
mujica.PriceChangeBadge(c, d.change, d.change/1.47, mujica.PriceChangeBadgeOptions{HidePercent: d.hidePercent})
|
||||
formStates(c, func() {
|
||||
mujica.PriceChangeBadge(c, 2.4, 1.2, mujica.PriceChangeBadgeOptions{})
|
||||
mujica.PriceChangeBadge(c, -2.4, -1.2, mujica.PriceChangeBadgeOptions{})
|
||||
mujica.PriceChangeBadge(c, 0, 0, mujica.PriceChangeBadgeOptions{})
|
||||
})
|
||||
}
|
||||
|
||||
type tradingBookState struct {
|
||||
redUp, disabled bool
|
||||
step int
|
||||
book mujica.OrderBookState
|
||||
}
|
||||
|
||||
func tradingBookDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "165", func() tradingBookState { return tradingBookState{} })
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
formToggles(c, []string{"Disabled"}, &d.disabled)
|
||||
if tradingAction(c, "Next update") {
|
||||
d.step++
|
||||
log(fmt.Sprintf("book update %d", d.step))
|
||||
}
|
||||
ui.Box(c).Width(320).Children(func() {
|
||||
r := mujica.OrderBook(c, &d.book, tradingBook(100, d.step), mujica.OrderBookOptions{Depth: 6, Groups: []float64{0.01, 0.05, 0.1}, Disabled: d.disabled})
|
||||
if price, side, ok := r.Clicked(); ok {
|
||||
log(fmt.Sprintf("level %v at %.2f", side, price))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type tradingLevel2State struct {
|
||||
redUp bool
|
||||
step int
|
||||
}
|
||||
|
||||
func tradingLevel2Demo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "166", func() tradingLevel2State { return tradingLevel2State{} })
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
if tradingAction(c, "Next update") {
|
||||
d.step++
|
||||
log(fmt.Sprintf("level 2 update %d", d.step))
|
||||
}
|
||||
book := tradingBook(100, d.step)
|
||||
var data mujica.Level2Data
|
||||
for i := range 5 {
|
||||
data.Bids = append(data.Bids, mujica.L2Level{Price: book.Bids[i].Price, Size: book.Bids[i].Size, Orders: int(book.Bids[i].Size / 10)})
|
||||
data.Asks = append(data.Asks, mujica.L2Level{Price: book.Asks[i].Price, Size: book.Asks[i].Size, Orders: int(book.Asks[i].Size / 10)})
|
||||
}
|
||||
ui.Box(c).Width(620).Children(func() { mujica.Level2Quotes(c, data, mujica.Level2QuotesOptions{}) })
|
||||
}
|
||||
|
||||
type tradingTapeState struct {
|
||||
redUp bool
|
||||
trades []mujica.TradePrint
|
||||
tape mujica.TimeAndSalesState
|
||||
}
|
||||
|
||||
func tradingTapeDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "167", func() tradingTapeState { return tradingTapeState{} })
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
if tradingAction(c, "Add 20 trades") {
|
||||
at := time.Date(2026, 10, 5, 9, 30, 0, 0, time.Local)
|
||||
for range 20 {
|
||||
n := len(d.trades)
|
||||
side := mujica.SideBuy
|
||||
if n%3 == 1 {
|
||||
side = mujica.SideSell
|
||||
}
|
||||
d.trades = append(d.trades, mujica.TradePrint{ID: fmt.Sprint(n), Time: at.Add(time.Duration(n) * time.Second),
|
||||
Price: 100 + float64(n%17)/100, Size: float64((n*37)%1500 + 1), Side: side})
|
||||
}
|
||||
log(fmt.Sprintf("%d trades", len(d.trades)))
|
||||
}
|
||||
ui.Box(c).Width(460).Children(func() {
|
||||
if mujica.TimeAndSales(c, &d.tape, d.trades, mujica.TimeAndSalesOptions{LargeSize: 1000}).Changed() {
|
||||
log(fmt.Sprintf("paused %v", d.tape.Paused))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type tradingLadderState struct {
|
||||
redUp, disabled bool
|
||||
last float64
|
||||
ladder mujica.DepthLadderState
|
||||
}
|
||||
|
||||
func tradingLadderDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "168", func() tradingLadderState { return tradingLadderState{last: 100} })
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
formToggles(c, []string{"Disabled"}, &d.disabled)
|
||||
if mujica.Slider(c, &d.last, mujica.SliderOptions{Min: 99.9, Max: 100.1, Step: 0.01, Label: "Last price", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprintf("last %.2f", d.last))
|
||||
}
|
||||
ui.Box(c).Width(360).Children(func() {
|
||||
r := mujica.DepthLadder(c, &d.ladder, d.last, tradingBook(100, 0), mujica.DepthLadderOptions{TickSize: 0.01, Rows: 11, Disabled: d.disabled})
|
||||
if price, side, ok := r.Clicked(); ok {
|
||||
log(fmt.Sprintf("order %v at %.2f", side, price))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package main
|
||||
|
||||
// tradingCode holds each page's copyable snippet, the same as its doc and Example.
|
||||
var tradingCode = map[string]string{
|
||||
"TickerTape": `items := []mujica.TickerItem{{Symbol: "MUJC", Price: 412.08, ChangePercent: -1.35}, {Symbol: "AVE", Price: 88.2, ChangePercent: 2.1}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.TickerTape(c, items, mujica.TickerTapeOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"QuoteCard": `quote := mujica.Quote{Symbol: "MUJC", Name: "Ave Mujica Holdings", Price: 412.08, Change: -5.62, ChangePercent: -1.35}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.QuoteCard(c, quote, mujica.QuoteCardOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"PriceText": `view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.UseTrend(c, mujica.RedUp)
|
||||
mujica.PriceText(c, 18234.5, mujica.PriceTextOptions{Compare: true, Reference: 18000})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"PriceChangeBadge": `view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.PriceChangeBadge(c, -5.62, -1.35, mujica.PriceChangeBadgeOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"OrderBook": `var state mujica.OrderBookState
|
||||
book := mujica.OrderBookData{
|
||||
Bids: []mujica.BookLevel{{Price: 100.00, Size: 5}, {Price: 99.99, Size: 3}},
|
||||
Asks: []mujica.BookLevel{{Price: 100.01, Size: 1}, {Price: 100.02, Size: 2}},
|
||||
}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if price, side, ok := mujica.OrderBook(c, &state, book, mujica.OrderBookOptions{Groups: []float64{0.01, 0.05}}).Clicked(); ok {
|
||||
fmt.Println("order at", price, side)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"Level2Quotes": `data := mujica.Level2Data{
|
||||
Bids: []mujica.L2Level{{Price: 100.00, Size: 500, Orders: 12}},
|
||||
Asks: []mujica.L2Level{{Price: 100.01, Size: 200, Orders: 3}},
|
||||
}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.Level2Quotes(c, data, mujica.Level2QuotesOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"TimeAndSales": `var state mujica.TimeAndSalesState
|
||||
trades := []mujica.TradePrint{{ID: "1", Time: time.Now(), Price: 100.01, Size: 300, Side: mujica.SideBuy}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.TimeAndSales(c, &state, trades, mujica.TimeAndSalesOptions{LargeSize: 1000})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"DepthLadder": `var state mujica.DepthLadderState
|
||||
book := mujica.OrderBookData{Bids: []mujica.BookLevel{{Price: 100, Size: 5}}, Asks: []mujica.BookLevel{{Price: 100.01, Size: 2}}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if price, side, ok := mujica.DepthLadder(c, &state, 100, book, mujica.DepthLadderOptions{TickSize: 0.01}).Clicked(); ok {
|
||||
fmt.Println("place", side, "at", price)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"SpreadIndicator": `view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.SpreadIndicator(c, 99.95, 100.05, mujica.SpreadIndicatorOptions{WarnPercent: 0.5})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"BidAskBar": `view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.BidAskBar(c, 6234, 3820, mujica.BidAskBarOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"OrderEntry": `var state mujica.OrderEntryState
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if order, ok := mujica.OrderEntry(c, &state, mujica.OrderEntryOptions{Symbol: "MUJC", Last: 100.2}).Submitted(); ok {
|
||||
fmt.Println("place", order.Quantity)
|
||||
state.EndSubmit("")
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"QuickTradeButtons": `quantity := 10.0
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if side, q, ok := mujica.QuickTradeButtons(c, &quantity, mujica.QuickTradeButtonsOptions{Symbol: "MUJC", Confirm: true}).Traded(); ok {
|
||||
fmt.Println(side, q)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"PositionTable": `var state mujica.DataTableState[string]
|
||||
rows := []mujica.PositionRow{{ID: "p1", Symbol: "MUJC", Quantity: 100, AvgCost: 400, Price: 412.08}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if id, ok := mujica.PositionTable(c, &state, rows, mujica.PositionTableOptions{}).Acted(); ok {
|
||||
fmt.Println("close", id)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"OrderTable": `var state mujica.OrderTableState
|
||||
rows := []mujica.OrderRow{{ID: "o1", Symbol: "MUJC", Side: mujica.SideBuy, Price: 99.5, Quantity: 100, Filled: 40, Status: mujica.OrderPartial}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if id, ok := mujica.OrderTable(c, &state, rows, mujica.OrderTableOptions{}).Acted(); ok {
|
||||
fmt.Println("cancel", id)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"TradeHistoryTable": `var state mujica.TradeHistoryTableState
|
||||
rows := []mujica.FillRow{{ID: "f1", Time: time.Now(), Symbol: "MUJC", Side: mujica.SideBuy, Price: 400, Quantity: 10, Fee: 1.2}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if fills, ok := mujica.TradeHistoryTable(c, &state, rows, mujica.TradeHistoryTableOptions{}).Exported(); ok {
|
||||
fmt.Println("export", len(fills))
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"PnLDisplay": `hidden := false
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.PnLDisplay(c, mujica.PnL{Realized: 1200.5, Unrealized: -340.2, Today: 88}, &hidden, mujica.PnLDisplayOptions{Currency: "$"})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"LeverageSlider": `leverage := 5.0
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if mujica.LeverageSlider(c, &leverage, mujica.LeverageSliderOptions{Warning: 10, Danger: 50}).Changed() {
|
||||
fmt.Println("leverage", leverage)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"MarginIndicator": `view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.MarginIndicator(c, 0.86, mujica.MarginIndicatorOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"RiskMeter": `view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.RiskMeter(c, 4, mujica.RiskMeterOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
"OrderConfirmDialog": `open := true
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.OrderConfirmDialog(c, &open, mujica.OrderConfirmDialogOptions{
|
||||
Lines: []mujica.OrderSummaryLine{{Label: "Symbol", Value: "MUJC"}, {Label: "Quantity", Value: "100"}},
|
||||
Risk: "Leveraged products can lose more than the deposit.",
|
||||
Acknowledge: "I understand the risk",
|
||||
})
|
||||
if r.Confirmed {
|
||||
fmt.Println("send order")
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`,
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
mujica "mujicaui"
|
||||
)
|
||||
|
||||
type tradingSpreadState struct {
|
||||
ask, warn float64
|
||||
}
|
||||
|
||||
func tradingSpreadDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "169", func() tradingSpreadState { return tradingSpreadState{ask: 100.05, warn: 0.5} })
|
||||
if mujica.Slider(c, &d.ask, mujica.SliderOptions{Min: 99.5, Max: 101, Step: 0.01, Label: "Ask (bid 99.95)", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprintf("ask %.2f", d.ask))
|
||||
}
|
||||
if mujica.Slider(c, &d.warn, mujica.SliderOptions{Min: 0, Max: 2, Step: 0.1, Label: "Warn at percent", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprintf("warn %.1f%%", d.warn))
|
||||
}
|
||||
mujica.SpreadIndicator(c, 99.95, d.ask, mujica.SpreadIndicatorOptions{WarnPercent: d.warn})
|
||||
formStates(c, func() {
|
||||
mujica.SpreadIndicator(c, 99.95, 100.05, mujica.SpreadIndicatorOptions{})
|
||||
mujica.SpreadIndicator(c, 99, 101, mujica.SpreadIndicatorOptions{WarnPercent: 0.5})
|
||||
mujica.SpreadIndicator(c, 100.1, 100, mujica.SpreadIndicatorOptions{})
|
||||
})
|
||||
}
|
||||
|
||||
type tradingBarState struct {
|
||||
redUp bool
|
||||
bid, ask float64
|
||||
}
|
||||
|
||||
func tradingBarDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "170", func() tradingBarState { return tradingBarState{bid: 6234, ask: 3820} })
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
for _, v := range []struct {
|
||||
name string
|
||||
p *float64
|
||||
}{{"Bid volume", &d.bid}, {"Ask volume", &d.ask}} {
|
||||
if mujica.Slider(c, v.p, mujica.SliderOptions{Min: 0, Max: 10000, Step: 1, Label: v.name, ShowValue: true}).Changed() {
|
||||
log(fmt.Sprintf("%s %.0f", v.name, *v.p))
|
||||
}
|
||||
}
|
||||
ui.Box(c).Width(360).Children(func() { mujica.BidAskBar(c, d.bid, d.ask, mujica.BidAskBarOptions{}) })
|
||||
}
|
||||
|
||||
type tradingEntryState struct {
|
||||
redUp, disabled, reject bool
|
||||
entry mujica.OrderEntryState
|
||||
}
|
||||
|
||||
func tradingEntryDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "171", func() tradingEntryState { return tradingEntryState{} })
|
||||
checks := *ui.Local(c.Root(), "171.checks", func() chan [2]any { return make(chan [2]any, 8) })
|
||||
placed := *ui.Local(c.Root(), "171.placed", func() chan string { return make(chan string, 1) })
|
||||
for drained := false; !drained; {
|
||||
select {
|
||||
case r := <-checks:
|
||||
v, msg := r[0].(uint64), r[1].(string)
|
||||
log(fmt.Sprintf("buying power check v%d: %q, kept %v", v, msg, d.entry.Resolve(v, msg)))
|
||||
case e := <-placed:
|
||||
d.entry.EndSubmit(e)
|
||||
log(fmt.Sprintf("order placed, error %q", e))
|
||||
default:
|
||||
drained = true
|
||||
}
|
||||
}
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
formToggles(c, []string{"Disabled", "Broker rejects"}, &d.disabled, &d.reject)
|
||||
r := mujica.OrderEntry(c, &d.entry, mujica.OrderEntryOptions{Symbol: "MUJC", Last: 100.2, MaxQuantity: 5000, Disabled: d.disabled,
|
||||
Check: func(o mujica.OrderDraft, v uint64) {
|
||||
log(fmt.Sprintf("check started v%d", v))
|
||||
go func() {
|
||||
time.Sleep(600 * time.Millisecond)
|
||||
msg := ""
|
||||
if o.Quantity*100 > 250000 {
|
||||
msg = "Not enough buying power."
|
||||
}
|
||||
checks <- [2]any{v, msg}
|
||||
c.Invalidate()
|
||||
}()
|
||||
}})
|
||||
if o, ok := r.Submitted(); ok {
|
||||
log(fmt.Sprintf("submitted %+v", o))
|
||||
fail := ""
|
||||
if d.reject {
|
||||
fail = "The broker rejected the order. Your entry is kept."
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(800 * time.Millisecond)
|
||||
placed <- fail
|
||||
c.Invalidate()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
type tradingQuickState struct {
|
||||
redUp, confirm, disabled bool
|
||||
qty float64
|
||||
}
|
||||
|
||||
func tradingQuickDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "172", func() tradingQuickState { return tradingQuickState{confirm: true, qty: 10} })
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
formToggles(c, []string{"Confirm first", "Disabled"}, &d.confirm, &d.disabled)
|
||||
ui.Box(c).Width(400).Children(func() {
|
||||
if side, q, ok := mujica.QuickTradeButtons(c, &d.qty, mujica.QuickTradeButtonsOptions{Symbol: "MUJC", Confirm: d.confirm, Disabled: d.disabled}).Traded(); ok {
|
||||
log(fmt.Sprintf("trade %v %v", side, q))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type tradingPositionsState struct {
|
||||
redUp, disabled bool
|
||||
rows []mujica.PositionRow
|
||||
table mujica.DataTableState[string]
|
||||
}
|
||||
|
||||
func tradingPositionsDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "173", func() tradingPositionsState {
|
||||
return tradingPositionsState{rows: []mujica.PositionRow{
|
||||
{ID: "p1", Symbol: "MUJC", Quantity: 100, AvgCost: 400, Price: 412.08},
|
||||
{ID: "p2", Symbol: "AVE", Quantity: 50, AvgCost: 90, Price: 88.2},
|
||||
{ID: "p3", Symbol: "OBLV", Quantity: 8, AvgCost: 1180, Price: 1204.5},
|
||||
}}
|
||||
})
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
formToggles(c, []string{"Disabled"}, &d.disabled)
|
||||
ui.Box(c).Width(1000).Height(260).Children(func() {
|
||||
r := mujica.PositionTable(c, &d.table, d.rows, mujica.PositionTableOptions{Disabled: d.disabled})
|
||||
if id, ok := r.Acted(); ok {
|
||||
for i, p := range d.rows {
|
||||
if p.ID == id {
|
||||
d.rows = append(d.rows[:i:i], d.rows[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
log("close " + id)
|
||||
}
|
||||
if r.Table.Changed() {
|
||||
id, _ := d.table.Selected()
|
||||
log("select " + id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type tradingOrdersState struct {
|
||||
disabled bool
|
||||
rows []mujica.OrderRow
|
||||
table mujica.OrderTableState
|
||||
version int
|
||||
}
|
||||
|
||||
func tradingOrdersDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "174", func() tradingOrdersState {
|
||||
return tradingOrdersState{rows: []mujica.OrderRow{
|
||||
{ID: "o1", Symbol: "MUJC", Side: mujica.SideBuy, Type: mujica.OrderLimit, Price: 99.5, Quantity: 100, Filled: 40, Status: mujica.OrderPartial},
|
||||
{ID: "o2", Symbol: "AVE", Side: mujica.SideSell, Type: mujica.OrderMarket, Quantity: 10, Filled: 10, Status: mujica.OrderFilled},
|
||||
{ID: "o3", Symbol: "OBLV", Side: mujica.SideBuy, Type: mujica.OrderStop, Price: 12, Quantity: 5, Status: mujica.OrderCanceled},
|
||||
{ID: "o4", Symbol: "DOLO", Side: mujica.SideSell, Type: mujica.OrderLimit, Price: 7.25, Quantity: 30, Status: mujica.OrderOpen},
|
||||
}}
|
||||
})
|
||||
formToggles(c, []string{"Disabled"}, &d.disabled)
|
||||
ui.Box(c).Width(1000).Height(300).Children(func() {
|
||||
if id, ok := mujica.OrderTable(c, &d.table, d.rows, mujica.OrderTableOptions{Disabled: d.disabled, Version: d.version}).Acted(); ok {
|
||||
for i := range d.rows {
|
||||
if d.rows[i].ID == id {
|
||||
d.rows[i].Status = mujica.OrderCanceled
|
||||
}
|
||||
}
|
||||
d.version++
|
||||
log("cancel " + id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type tradingHistoryState struct {
|
||||
disabled bool
|
||||
rows []mujica.FillRow
|
||||
table mujica.TradeHistoryTableState
|
||||
}
|
||||
|
||||
func tradingHistoryDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "175", func() tradingHistoryState {
|
||||
var rows []mujica.FillRow
|
||||
for i := range 30 {
|
||||
side := mujica.SideBuy
|
||||
if i%3 == 0 {
|
||||
side = mujica.SideSell
|
||||
}
|
||||
rows = append(rows, mujica.FillRow{ID: fmt.Sprint(i), Time: time.Date(2026, 9, 10+i/2, 9+i%6, 0, 0, 0, time.Local),
|
||||
Symbol: []string{"MUJC", "AVE", "OBLV"}[i%3], Side: side, Price: 100 + float64(i), Quantity: float64(i%5+1) * 10, Fee: 0.5})
|
||||
}
|
||||
return tradingHistoryState{rows: rows}
|
||||
})
|
||||
formToggles(c, []string{"Disabled"}, &d.disabled)
|
||||
ui.Box(c).Width(1000).Height(360).Children(func() {
|
||||
if fills, ok := mujica.TradeHistoryTable(c, &d.table, d.rows, mujica.TradeHistoryTableOptions{Disabled: d.disabled}).Exported(); ok {
|
||||
log(fmt.Sprintf("export %d fills", len(fills)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type tradingPnLState struct {
|
||||
redUp, hidden bool
|
||||
}
|
||||
|
||||
func tradingPnLDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "176", func() tradingPnLState { return tradingPnLState{} })
|
||||
tradingTrend(c, &d.redUp, log)
|
||||
if mujica.PnLDisplay(c, mujica.PnL{Realized: 1200.5, Unrealized: -340.2, Today: 88}, &d.hidden, mujica.PnLDisplayOptions{Currency: "$"}).Changed() {
|
||||
log(fmt.Sprintf("hidden %v", d.hidden))
|
||||
}
|
||||
}
|
||||
|
||||
type tradingLeverageState struct {
|
||||
disabled bool
|
||||
value float64
|
||||
}
|
||||
|
||||
func tradingLeverageDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "177", func() tradingLeverageState { return tradingLeverageState{value: 5} })
|
||||
formToggles(c, []string{"Disabled"}, &d.disabled)
|
||||
ui.Box(c).Width(380).Children(func() {
|
||||
if mujica.LeverageSlider(c, &d.value, mujica.LeverageSliderOptions{Warning: 10, Danger: 50, Disabled: d.disabled}).Changed() {
|
||||
log(fmt.Sprintf("leverage applied %v×", d.value))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type tradingMarginState struct{ ratio float64 }
|
||||
|
||||
func tradingMarginDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "178", func() tradingMarginState { return tradingMarginState{ratio: 0.55} })
|
||||
if mujica.Slider(c, &d.ratio, mujica.SliderOptions{Min: 0, Max: 1.4, Step: 0.01, Label: "Margin ratio", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprintf("ratio %.2f", d.ratio))
|
||||
}
|
||||
ui.Box(c).Width(360).Children(func() { mujica.MarginIndicator(c, d.ratio, mujica.MarginIndicatorOptions{}) })
|
||||
formStates(c, func() {
|
||||
for _, r := range []float64{0.3, 0.85, 1.1} {
|
||||
ui.Box(c).Width(260).Children(func() { mujica.MarginIndicator(c, r, mujica.MarginIndicatorOptions{}) })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type tradingRiskState struct{ level float64 }
|
||||
|
||||
func tradingRiskDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "179", func() tradingRiskState { return tradingRiskState{level: 3} })
|
||||
if mujica.Slider(c, &d.level, mujica.SliderOptions{Min: 1, Max: 5, Step: 1, Label: "Level", ShowValue: true}).Changed() {
|
||||
log(fmt.Sprintf("level %.0f", d.level))
|
||||
}
|
||||
ui.Box(c).Width(360).Children(func() { mujica.RiskMeter(c, int(d.level), mujica.RiskMeterOptions{}) })
|
||||
}
|
||||
|
||||
type tradingConfirmState struct {
|
||||
open, acknowledge bool
|
||||
}
|
||||
|
||||
func tradingConfirmDemo(c *ui.Context, log func(string)) {
|
||||
d := ui.Local(c.Root(), "180", func() tradingConfirmState { return tradingConfirmState{acknowledge: true} })
|
||||
formToggles(c, []string{"Require acknowledgement"}, &d.acknowledge)
|
||||
if tradingAction(c, "Review order") {
|
||||
d.open = true
|
||||
log("open")
|
||||
}
|
||||
ack := ""
|
||||
if d.acknowledge {
|
||||
ack = "I understand leveraged products can lose more than the deposit"
|
||||
}
|
||||
r := mujica.OrderConfirmDialog(c, &d.open, mujica.OrderConfirmDialogOptions{
|
||||
Lines: []mujica.OrderSummaryLine{{Label: "Symbol", Value: "MUJC"}, {Label: "Side", Value: "Buy"}, {Label: "Type", Value: "Limit 100.20"},
|
||||
{Label: "Quantity", Value: "100"}, {Label: "Estimated amount", Value: "10,020.00"}},
|
||||
Risk: "5× leverage: a 20% move against you wipes out the margin.", Acknowledge: ack,
|
||||
})
|
||||
if r.Confirmed {
|
||||
log("confirmed")
|
||||
}
|
||||
if r.Canceled {
|
||||
log("canceled")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user