Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: [xnav] support breakdown and improve price solving logics #1866

Merged
merged 20 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions pkg/accounting/pnl/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/slack-go/slack"

"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types/currency"

"github.com/c9s/bbgo/pkg/slack/slackstyle"
"github.com/c9s/bbgo/pkg/types"
Expand Down Expand Up @@ -45,28 +46,28 @@ func (report AverageCostPnLReport) Print() {
color.Green("TRADES SINCE: %v", report.StartTime)
color.Green("NUMBER OF TRADES: %d", report.NumTrades)
color.Green(report.Position.String())
color.Green("AVERAGE COST: %s", types.USD.FormatMoney(report.AverageCost))
color.Green("AVERAGE COST: %s", currency.USD.FormatMoney(report.AverageCost))
color.Green("BASE ASSET POSITION: %s", report.BaseAssetPosition.String())

color.Green("TOTAL BUY VOLUME: %v", report.BuyVolume)
color.Green("TOTAL SELL VOLUME: %v", report.SellVolume)

color.Green("CURRENT PRICE: %s", types.USD.FormatMoney(report.LastPrice))
color.Green("CURRENT PRICE: %s", currency.USD.FormatMoney(report.LastPrice))
color.Green("CURRENCY FEES:")
for currency, fee := range report.CurrencyFees {
color.Green(" - %s: %s", currency, fee.String())
}

if report.Profit.Sign() > 0 {
color.Green("PROFIT: %s", types.USD.FormatMoney(report.Profit))
color.Green("PROFIT: %s", currency.USD.FormatMoney(report.Profit))
} else {
color.Red("PROFIT: %s", types.USD.FormatMoney(report.Profit))
color.Red("PROFIT: %s", currency.USD.FormatMoney(report.Profit))
}

if report.UnrealizedProfit.Sign() > 0 {
color.Green("UNREALIZED PROFIT: %s", types.USD.FormatMoney(report.UnrealizedProfit))
color.Green("UNREALIZED PROFIT: %s", currency.USD.FormatMoney(report.UnrealizedProfit))
} else {
color.Red("UNREALIZED PROFIT: %s", types.USD.FormatMoney(report.UnrealizedProfit))
color.Red("UNREALIZED PROFIT: %s", currency.USD.FormatMoney(report.UnrealizedProfit))
}
}

Expand All @@ -79,13 +80,13 @@ func (report AverageCostPnLReport) SlackAttachment() slack.Attachment {

return slack.Attachment{
Title: report.Symbol + " Profit and Loss report",
Text: "Profit " + types.USD.FormatMoney(report.Profit),
Text: "Profit " + currency.USD.FormatMoney(report.Profit),
Color: color,
// Pretext: "",
// Text: "",
Fields: []slack.AttachmentField{
{Title: "Profit", Value: types.USD.FormatMoney(report.Profit)},
{Title: "Unrealized Profit", Value: types.USD.FormatMoney(report.UnrealizedProfit)},
{Title: "Profit", Value: currency.USD.FormatMoney(report.Profit)},
{Title: "Unrealized Profit", Value: currency.USD.FormatMoney(report.UnrealizedProfit)},
{Title: "Current Price", Value: report.Market.FormatPrice(report.LastPrice), Short: true},
{Title: "Average Cost", Value: report.Market.FormatPrice(report.AverageCost), Short: true},

Expand Down
7 changes: 4 additions & 3 deletions pkg/bbgo/account_value_calc.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/c9s/bbgo/pkg/pricesolver"
"github.com/c9s/bbgo/pkg/risk"
"github.com/c9s/bbgo/pkg/types"
currency2 "github.com/c9s/bbgo/pkg/types/currency"
)

var defaultLeverage = fixedpoint.NewFromInt(3)
Expand Down Expand Up @@ -159,7 +160,7 @@ func aggregateUsdNetValue(balances types.BalanceMap) fixedpoint.Value {
totalUsdValue := fixedpoint.Zero
// get all usd value if any
for currency, balance := range balances {
if types.IsUSDFiatCurrency(currency) {
if currency2.IsUSDFiatCurrency(currency) {
totalUsdValue = totalUsdValue.Add(balance.Net())
}
}
Expand All @@ -171,7 +172,7 @@ func usdFiatBalances(balances types.BalanceMap) (fiats types.BalanceMap, rest ty
rest = make(types.BalanceMap)
fiats = make(types.BalanceMap)
for currency, balance := range balances {
if types.IsUSDFiatCurrency(currency) {
if currency2.IsUSDFiatCurrency(currency) {
fiats[currency] = balance
} else {
rest[currency] = balance
Expand Down Expand Up @@ -214,7 +215,7 @@ func CalculateBaseQuantity(

// for isolated margin, we can calculate from these two pair
totalUsdValue := fixedpoint.Zero
if len(restBalances) == 1 && types.IsUSDFiatCurrency(market.QuoteCurrency) {
if len(restBalances) == 1 && currency2.IsUSDFiatCurrency(market.QuoteCurrency) {
totalUsdValue = aggregateUsdNetValue(balances)
} else if len(restBalances) > 1 {
priceSolver := pricesolver.NewSimplePriceResolver(session.Markets())
Expand Down
3 changes: 2 additions & 1 deletion pkg/bbgo/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
googleservice "github.com/c9s/bbgo/pkg/service/google"
"github.com/c9s/bbgo/pkg/slack/slacklog"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/types/asset"
"github.com/c9s/bbgo/pkg/util"
)

Expand Down Expand Up @@ -542,7 +543,7 @@ func (environ *Environment) Sync(ctx context.Context, userConfig ...*Config) err
return nil
}

func (environ *Environment) RecordAsset(t time.Time, session *ExchangeSession, assets types.AssetMap) {
func (environ *Environment) RecordAsset(t time.Time, session *ExchangeSession, assets asset.Map) {
// skip for back-test
if environ.BacktestService != nil {
return
Expand Down
5 changes: 3 additions & 2 deletions pkg/bbgo/order_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/c9s/bbgo/pkg/envvar"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/types/currency"
)

var DefaultSubmitOrderRetryTimeout = 5 * time.Minute
Expand Down Expand Up @@ -176,8 +177,8 @@ func (c *BasicRiskController) ProcessOrders(

if quoteBalance.Available.Compare(c.MinQuoteBalance) < 0 {
addError(errors.Wrapf(ErrQuoteBalanceLevelTooLow, "can not place buy order, quote balance level is too low: %s < %s, order: %s",
types.USD.FormatMoney(quoteBalance.Available),
types.USD.FormatMoney(c.MinQuoteBalance), order.String()))
currency.USD.FormatMoney(quoteBalance.Available),
currency.USD.FormatMoney(c.MinQuoteBalance), order.String()))
continue
}

Expand Down
55 changes: 22 additions & 33 deletions pkg/bbgo/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/c9s/bbgo/pkg/exchange/retry"
"github.com/c9s/bbgo/pkg/metrics"
"github.com/c9s/bbgo/pkg/pricesolver"
currency2 "github.com/c9s/bbgo/pkg/types/currency"
"github.com/c9s/bbgo/pkg/util/templateutil"

exchange2 "github.com/c9s/bbgo/pkg/exchange"
Expand Down Expand Up @@ -202,6 +203,14 @@ func NewExchangeSession(name string, exchange types.Exchange) *ExchangeSession {
return session
}

func (session *ExchangeSession) GetPriceSolver() *pricesolver.SimplePriceSolver {
if session.priceSolver == nil {
session.priceSolver = pricesolver.NewSimplePriceResolver(session.markets)
}

return session.priceSolver
}

func (session *ExchangeSession) GetAccountLabel() string {
var label string

Expand Down Expand Up @@ -764,16 +773,12 @@ func (session *ExchangeSession) FormatOrder(order types.SubmitOrder) (types.Subm
}

func (session *ExchangeSession) UpdatePrices(ctx context.Context, currencies []string, fiat string) (err error) {
// TODO: move this cache check to the http routes
// if session.lastPriceUpdatedAt.After(time.Now().Add(-time.Hour)) {
// return nil
// }

markets := session.Markets()
var symbols []string
symbols := make([]string, 0, 50)
for _, c := range currencies {
possibleSymbols := findPossibleMarketSymbols(markets, c, fiat)
symbols = append(symbols, possibleSymbols...)
for symbol := range markets.FindAssetMarkets(c) {
symbols = append(symbols, symbol)
}
}

if len(symbols) == 0 {
Expand All @@ -785,15 +790,20 @@ func (session *ExchangeSession) UpdatePrices(ctx context.Context, currencies []s
return err
}

priceSolver := session.GetPriceSolver()

var lastTime time.Time
for k, v := range tickers {
validPrice := v.GetValidPrice()
priceSolver.Update(k, validPrice)

// for {Crypto}/USDT markets
// map things like BTCUSDT = {price}
if market, ok := markets[k]; ok {
if types.IsFiatCurrency(market.BaseCurrency) {
session.lastPrices[k] = v.Last.Div(fixedpoint.One)
if currency2.IsFiatCurrency(market.BaseCurrency) {
session.lastPrices[k] = validPrice.Div(fixedpoint.One)
} else {
session.lastPrices[k] = v.Last
session.lastPrices[k] = validPrice
}
} else {
session.lastPrices[k] = v.Last
Expand All @@ -819,7 +829,7 @@ func (session *ExchangeSession) FindPossibleAssetSymbols() (symbols []string, er
var balances = session.GetAccount().Balances()
var fiatAssets []string

for _, currency := range types.FiatCurrencies {
for _, currency := range currency2.FiatCurrencies {
if balance, ok := balances[currency]; ok && balance.Total().Sign() > 0 {
fiatAssets = append(fiatAssets, currency)
}
Expand Down Expand Up @@ -1115,24 +1125,3 @@ func (session *ExchangeSession) FormatOrders(orders []types.SubmitOrder) (format

return formattedOrders, err
}

func findPossibleMarketSymbols(markets types.MarketMap, c, fiat string) (symbols []string) {
var tries []string
// expand USD stable coin currencies
if types.IsUSDFiatCurrency(fiat) {
for _, usdFiat := range types.USDFiatCurrencies {
tries = append(tries, c+usdFiat, usdFiat+c)
}
} else {
tries = []string{c + fiat, fiat + c}
}

for _, try := range tries {
if markets.Has(try) {
symbols = append(symbols, try)
break
}
}

return symbols
}
43 changes: 0 additions & 43 deletions pkg/bbgo/session_test.go
Original file line number Diff line number Diff line change
@@ -1,44 +1 @@
package bbgo

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/c9s/bbgo/pkg/types"
)

func Test_findPossibleMarketSymbols(t *testing.T) {
t.Run("btcusdt", func(t *testing.T) {
markets := types.MarketMap{
"BTCUSDT": types.Market{},
"BTCUSDC": types.Market{},
"BTCUSD": types.Market{},
"BTCBUSD": types.Market{},
}
symbols := findPossibleMarketSymbols(markets, "BTC", "USDT")
if assert.Len(t, symbols, 1) {
assert.Equal(t, "BTCUSDT", symbols[0])
}
})

t.Run("btcusd only", func(t *testing.T) {
markets := types.MarketMap{
"BTCUSD": types.Market{},
}
symbols := findPossibleMarketSymbols(markets, "BTC", "USDT")
if assert.Len(t, symbols, 1) {
assert.Equal(t, "BTCUSD", symbols[0])
}
})

t.Run("usd to stable coin", func(t *testing.T) {
markets := types.MarketMap{
"BTCUSDT": types.Market{},
}
symbols := findPossibleMarketSymbols(markets, "BTC", "USD")
if assert.Len(t, symbols, 1) {
assert.Equal(t, "BTCUSDT", symbols[0])
}
})
}
17 changes: 14 additions & 3 deletions pkg/notifier/slacknotifier/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ var emailRegExp = regexp.MustCompile("`^(?P<name>[a-zA-Z0-9.!#$%&'*+/=?^_ \\x60{

var typeNamePrefixRE = regexp.MustCompile(`^\*?([a-zA-Z0-9_]+\.)?`)

type SlackAttachmentCreator interface {
SlackAttachment() slack.Attachment
}

type SlackBlocksCreator interface {
SlackBlocks() slack.Blocks
}

type notifyTask struct {
channel string

Expand Down Expand Up @@ -297,7 +305,7 @@ func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...livenote.Option) er
}

var attachment slack.Attachment
if creator, ok := note.Object.(types.SlackAttachmentCreator); ok {
if creator, ok := note.Object.(SlackAttachmentCreator); ok {
attachment = creator.SlackAttachment()
} else {
return fmt.Errorf("livenote object does not support types.SlackAttachmentCreator interface")
Expand Down Expand Up @@ -422,7 +430,7 @@ func filterSlackAttachments(args []interface{}) (slackAttachments []slack.Attach

slackAttachments = append(slackAttachments, *a)

case types.SlackAttachmentCreator:
case SlackAttachmentCreator:
if firstAttachmentOffset == -1 {
firstAttachmentOffset = idx
}
Expand Down Expand Up @@ -468,7 +476,10 @@ func (n *Notifier) NotifyTo(channel string, obj interface{}, args ...interface{}
case slack.Attachment:
opts = append(opts, slack.MsgOptionAttachments(append([]slack.Attachment{a}, slackAttachments...)...))

case types.SlackAttachmentCreator:
case *slack.Attachment:
opts = append(opts, slack.MsgOptionAttachments(append([]slack.Attachment{*a}, slackAttachments...)...))

case SlackAttachmentCreator:
// convert object to slack attachment (if supported)
opts = append(opts, slack.MsgOptionAttachments(append([]slack.Attachment{a.SlackAttachment()}, slackAttachments...)...))

Expand Down
7 changes: 4 additions & 3 deletions pkg/server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/service"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/types/asset"
)

const DefaultBindAddress = "localhost:8080"
Expand Down Expand Up @@ -408,9 +409,9 @@ func (s *Server) listSessionOpenOrders(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"orders": nil})
}

func genFakeAssets() types.AssetMap {
func genFakeAssets() asset.Map {

totalAssets := types.AssetMap{}
totalAssets := asset.Map{}
balances := types.BalanceMap{
"BTC": types.Balance{Currency: "BTC", Available: fixedpoint.NewFromFloat(10.0 * rand.Float64())},
"BCH": types.Balance{Currency: "BCH", Available: fixedpoint.NewFromFloat(0.01 * rand.Float64())},
Expand Down Expand Up @@ -447,7 +448,7 @@ func (s *Server) listAssets(c *gin.Context) {
return
}

totalAssets := types.AssetMap{}
totalAssets := asset.Map{}
for _, session := range s.Environ.Sessions() {
balances := session.GetAccount().Balances()

Expand Down
16 changes: 11 additions & 5 deletions pkg/service/account.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package service

import (
"github.com/c9s/bbgo/pkg/types"
"time"

"github.com/jmoiron/sqlx"
"go.uber.org/multierr"
"time"

"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/types/asset"
)

type AccountService struct {
Expand All @@ -16,7 +19,10 @@ func NewAccountService(db *sqlx.DB) *AccountService {
}

// TODO: should pass bbgo.ExchangeSession to this function, but that might cause cyclic import
func (s *AccountService) InsertAsset(time time.Time, session string, name types.ExchangeName, account string, isMargin bool, isIsolatedMargin bool, isolatedMarginSymbol string, assets types.AssetMap) error {
func (s *AccountService) InsertAsset(
time time.Time, session string, name types.ExchangeName, account string, isMargin bool, isIsolatedMargin bool,
isolatedMarginSymbol string, assets asset.Map,
) error {
if s.DB == nil {
// skip db insert when no db connection setting.
return nil
Expand Down Expand Up @@ -46,8 +52,8 @@ func (s *AccountService) InsertAsset(time time.Time, session string, name types.
account,
time,
v.Currency,
v.InUSD,
v.InBTC,
v.NetAssetInUSD,
v.NetAssetInBTC,
v.Total,
v.Available,
v.Locked,
Expand Down
Loading
Loading