fixedpoint for exchange and indicators, some fixes in types

This commit is contained in:
zenix 2022-02-03 13:55:25 +09:00
parent e221f54397
commit b8bf2af14d
37 changed files with 645 additions and 429 deletions

View File

@ -70,8 +70,8 @@ func (report AverageCostPnlReport) SlackAttachment() slack.Attachment {
Fields: []slack.AttachmentField{
{Title: "Profit", Value: types.USD.FormatMoney(report.Profit)},
{Title: "Unrealized Profit", Value: types.USD.FormatMoney(report.UnrealizedProfit)},
{Title: "Current Price", Value: report.Market.FormatPrice(report.LastPrice.Float64()), Short: true},
{Title: "Average Cost", Value: report.Market.FormatPrice(report.AverageCost.Float64()), Short: true},
{Title: "Current Price", Value: report.Market.FormatPrice(report.LastPrice), Short: true},
{Title: "Average Cost", Value: report.Market.FormatPrice(report.AverageCost), Short: true},
// FIXME:
// {Title: "Fee (USD)", Value: types.USD.FormatMoney(report.FeeInUSD), Short: true},

View File

@ -124,7 +124,7 @@ func (m BacktestAccountBalanceMap) BalanceMap() types.BalanceMap {
balances[currency] = types.Balance{
Currency: currency,
Available: value,
Locked: 0,
Locked: fixedpoint.Zero,
}
}
return balances

View File

@ -2,7 +2,6 @@ package binance
import (
"fmt"
"strconv"
"strings"
"time"
@ -12,7 +11,6 @@ import (
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
)
func toGlobalMarket(symbol binance.Symbol) types.Market {
@ -26,8 +24,8 @@ func toGlobalMarket(symbol binance.Symbol) types.Market {
}
if f := symbol.MinNotionalFilter(); f != nil {
market.MinNotional = util.MustParseFloat(f.MinNotional)
market.MinAmount = util.MustParseFloat(f.MinNotional)
market.MinNotional = fixedpoint.MustNewFromString(f.MinNotional)
market.MinAmount = fixedpoint.MustNewFromString(f.MinNotional)
}
// The LOT_SIZE filter defines the quantity (aka "lots" in auction terms) rules for a symbol.
@ -36,15 +34,15 @@ func toGlobalMarket(symbol binance.Symbol) types.Market {
// maxQty defines the maximum quantity/icebergQty allowed.
// stepSize defines the intervals that a quantity/icebergQty can be increased/decreased by.
if f := symbol.LotSizeFilter(); f != nil {
market.MinQuantity = util.MustParseFloat(f.MinQuantity)
market.MaxQuantity = util.MustParseFloat(f.MaxQuantity)
market.StepSize = util.MustParseFloat(f.StepSize)
market.MinQuantity = fixedpoint.MustNewFromString(f.MinQuantity)
market.MaxQuantity = fixedpoint.MustNewFromString(f.MaxQuantity)
market.StepSize = fixedpoint.MustNewFromString(f.StepSize)
}
if f := symbol.PriceFilter(); f != nil {
market.MaxPrice = util.MustParseFloat(f.MaxPrice)
market.MinPrice = util.MustParseFloat(f.MinPrice)
market.TickSize = util.MustParseFloat(f.TickSize)
market.MaxPrice = fixedpoint.MustNewFromString(f.MaxPrice)
market.MinPrice = fixedpoint.MustNewFromString(f.MinPrice)
market.TickSize = fixedpoint.MustNewFromString(f.TickSize)
}
return market
@ -62,8 +60,8 @@ func toGlobalFuturesMarket(symbol futures.Symbol) types.Market {
}
if f := symbol.MinNotionalFilter(); f != nil {
market.MinNotional = util.MustParseFloat(f.Notional)
market.MinAmount = util.MustParseFloat(f.Notional)
market.MinNotional = fixedpoint.MustNewFromString(f.Notional)
market.MinAmount = fixedpoint.MustNewFromString(f.Notional)
}
// The LOT_SIZE filter defines the quantity (aka "lots" in auction terms) rules for a symbol.
@ -72,15 +70,15 @@ func toGlobalFuturesMarket(symbol futures.Symbol) types.Market {
// maxQty defines the maximum quantity/icebergQty allowed.
// stepSize defines the intervals that a quantity/icebergQty can be increased/decreased by.
if f := symbol.LotSizeFilter(); f != nil {
market.MinQuantity = util.MustParseFloat(f.MinQuantity)
market.MaxQuantity = util.MustParseFloat(f.MaxQuantity)
market.StepSize = util.MustParseFloat(f.StepSize)
market.MinQuantity = fixedpoint.MustNewFromString(f.MinQuantity)
market.MaxQuantity = fixedpoint.MustNewFromString(f.MaxQuantity)
market.StepSize = fixedpoint.MustNewFromString(f.StepSize)
}
if f := symbol.PriceFilter(); f != nil {
market.MaxPrice = util.MustParseFloat(f.MaxPrice)
market.MinPrice = util.MustParseFloat(f.MinPrice)
market.TickSize = util.MustParseFloat(f.TickSize)
market.MaxPrice = fixedpoint.MustNewFromString(f.MaxPrice)
market.MinPrice = fixedpoint.MustNewFromString(f.MinPrice)
market.TickSize = fixedpoint.MustNewFromString(f.TickSize)
}
return market
@ -236,13 +234,13 @@ func toGlobalFuturesUserAssets(assets []*futures.AccountAsset) (retAssets types.
func toGlobalTicker(stats *binance.PriceChangeStats) (*types.Ticker, error) {
return &types.Ticker{
Volume: util.MustParseFloat(stats.Volume),
Last: util.MustParseFloat(stats.LastPrice),
Open: util.MustParseFloat(stats.OpenPrice),
High: util.MustParseFloat(stats.HighPrice),
Low: util.MustParseFloat(stats.LowPrice),
Buy: util.MustParseFloat(stats.BidPrice),
Sell: util.MustParseFloat(stats.AskPrice),
Volume: fixedpoint.MustNewFromString(stats.Volume),
Last: fixedpoint.MustNewFromString(stats.LastPrice),
Open: fixedpoint.MustNewFromString(stats.OpenPrice),
High: fixedpoint.MustNewFromString(stats.HighPrice),
Low: fixedpoint.MustNewFromString(stats.LowPrice),
Buy: fixedpoint.MustNewFromString(stats.BidPrice),
Sell: fixedpoint.MustNewFromString(stats.AskPrice),
Time: time.Unix(0, stats.CloseTime*int64(time.Millisecond)),
}, nil
}
@ -324,15 +322,15 @@ func toGlobalOrder(binanceOrder *binance.Order, isMargin bool) (*types.Order, er
Symbol: binanceOrder.Symbol,
Side: toGlobalSideType(binanceOrder.Side),
Type: toGlobalOrderType(binanceOrder.Type),
Quantity: util.MustParseFloat(binanceOrder.OrigQuantity),
Price: util.MustParseFloat(binanceOrder.Price),
Quantity: fixedpoint.MustNewFromString(binanceOrder.OrigQuantity),
Price: fixedpoint.MustNewFromString(binanceOrder.Price),
TimeInForce: string(binanceOrder.TimeInForce),
},
Exchange: types.ExchangeBinance,
IsWorking: binanceOrder.IsWorking,
OrderID: uint64(binanceOrder.OrderID),
Status: toGlobalOrderStatus(binanceOrder.Status),
ExecutedQuantity: util.MustParseFloat(binanceOrder.ExecutedQuantity),
ExecutedQuantity: fixedpoint.MustNewFromString(binanceOrder.ExecutedQuantity),
CreationTime: types.Time(millisecondTime(binanceOrder.Time)),
UpdateTime: types.Time(millisecondTime(binanceOrder.UpdateTime)),
IsMargin: isMargin,
@ -349,14 +347,14 @@ func toGlobalFuturesOrder(futuresOrder *futures.Order, isMargin bool) (*types.Or
Type: toGlobalFuturesOrderType(futuresOrder.Type),
ReduceOnly: futuresOrder.ReduceOnly,
ClosePosition: futuresOrder.ClosePosition,
Quantity: util.MustParseFloat(futuresOrder.OrigQuantity),
Price: util.MustParseFloat(futuresOrder.Price),
Quantity: fixedpoint.MustNewFromString(futuresOrder.OrigQuantity),
Price: fixedpoint.MustNewFromString(futuresOrder.Price),
TimeInForce: string(futuresOrder.TimeInForce),
},
Exchange: types.ExchangeBinance,
OrderID: uint64(futuresOrder.OrderID),
Status: toGlobalFuturesOrderStatus(futuresOrder.Status),
ExecutedQuantity: util.MustParseFloat(futuresOrder.ExecutedQuantity),
ExecutedQuantity: fixedpoint.MustNewFromString(futuresOrder.ExecutedQuantity),
CreationTime: types.Time(millisecondTime(futuresOrder.Time)),
UpdateTime: types.Time(millisecondTime(futuresOrder.UpdateTime)),
IsMargin: isMargin,
@ -376,27 +374,27 @@ func toGlobalTrade(t binance.TradeV3, isMargin bool) (*types.Trade, error) {
side = types.SideTypeSell
}
price, err := strconv.ParseFloat(t.Price, 64)
price, err := fixedpoint.NewFromString(t.Price)
if err != nil {
return nil, errors.Wrapf(err, "price parse error, price: %+v", t.Price)
}
quantity, err := strconv.ParseFloat(t.Quantity, 64)
quantity, err := fixedpoint.NewFromString(t.Quantity)
if err != nil {
return nil, errors.Wrapf(err, "quantity parse error, quantity: %+v", t.Quantity)
}
var quoteQuantity = 0.0
var quoteQuantity fixedpoint.Value
if len(t.QuoteQuantity) > 0 {
quoteQuantity, err = strconv.ParseFloat(t.QuoteQuantity, 64)
quoteQuantity, err = fixedpoint.NewFromString(t.QuoteQuantity)
if err != nil {
return nil, errors.Wrapf(err, "quote quantity parse error, quoteQuantity: %+v", t.QuoteQuantity)
}
} else {
quoteQuantity = price * quantity
quoteQuantity = price.Mul(quantity)
}
fee, err := strconv.ParseFloat(t.Commission, 64)
fee, err := fixedpoint.NewFromString(t.Commission)
if err != nil {
return nil, errors.Wrapf(err, "commission parse error, commission: %+v", t.Commission)
}
@ -429,27 +427,27 @@ func toGlobalFuturesTrade(t futures.AccountTrade) (*types.Trade, error) {
side = types.SideTypeSell
}
price, err := strconv.ParseFloat(t.Price, 64)
price, err := fixedpoint.NewFromString(t.Price)
if err != nil {
return nil, errors.Wrapf(err, "price parse error, price: %+v", t.Price)
}
quantity, err := strconv.ParseFloat(t.Quantity, 64)
quantity, err := fixedpoint.NewFromString(t.Quantity)
if err != nil {
return nil, errors.Wrapf(err, "quantity parse error, quantity: %+v", t.Quantity)
}
var quoteQuantity = 0.0
var quoteQuantity fixedpoint.Value
if len(t.QuoteQuantity) > 0 {
quoteQuantity, err = strconv.ParseFloat(t.QuoteQuantity, 64)
quoteQuantity, err = fixedpoint.NewFromString(t.QuoteQuantity)
if err != nil {
return nil, errors.Wrapf(err, "quote quantity parse error, quoteQuantity: %+v", t.QuoteQuantity)
}
} else {
quoteQuantity = price * quantity
quoteQuantity = price.Mul(quantity)
}
fee, err := strconv.ParseFloat(t.Commission, 64)
fee, err := fixedpoint.NewFromString(t.Commission)
if err != nil {
return nil, errors.Wrapf(err, "commission parse error, commission: %+v", t.Commission)
}

View File

@ -24,7 +24,6 @@ import (
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
)
const BNB = "BNB"
@ -152,13 +151,13 @@ func (e *Exchange) QueryTickers(ctx context.Context, symbol ...string) (map[stri
}
tick := types.Ticker{
Volume: util.MustParseFloat(stats.Volume),
Last: util.MustParseFloat(stats.LastPrice),
Open: util.MustParseFloat(stats.OpenPrice),
High: util.MustParseFloat(stats.HighPrice),
Low: util.MustParseFloat(stats.LowPrice),
Buy: util.MustParseFloat(stats.BidPrice),
Sell: util.MustParseFloat(stats.AskPrice),
Volume: fixedpoint.MustNewFromString(stats.Volume),
Last: fixedpoint.MustNewFromString(stats.LastPrice),
Open: fixedpoint.MustNewFromString(stats.OpenPrice),
High: fixedpoint.MustNewFromString(stats.HighPrice),
Low: fixedpoint.MustNewFromString(stats.LowPrice),
Buy: fixedpoint.MustNewFromString(stats.BidPrice),
Sell: fixedpoint.MustNewFromString(stats.AskPrice),
Time: time.Unix(0, stats.CloseTime*int64(time.Millisecond)),
}
@ -197,13 +196,13 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
return markets, nil
}
func (e *Exchange) QueryAveragePrice(ctx context.Context, symbol string) (float64, error) {
func (e *Exchange) QueryAveragePrice(ctx context.Context, symbol string) (fixedpoint.Value, error) {
resp, err := e.Client.NewAveragePriceService().Symbol(symbol).Do(ctx)
if err != nil {
return 0, err
return fixedpoint.Zero, err
}
return util.MustParseFloat(resp.Price), nil
return fixedpoint.MustNewFromString(resp.Price), nil
}
func (e *Exchange) NewStream() types.Stream {
@ -342,10 +341,10 @@ func (e *Exchange) QueryWithdrawHistory(ctx context.Context, asset string, since
Exchange: types.ExchangeBinance,
ApplyTime: types.Time(applyTime),
Asset: d.Coin,
Amount: util.MustParseFloat(d.Amount),
Amount: fixedpoint.MustNewFromString(d.Amount),
Address: d.Address,
TransactionID: d.TxID,
TransactionFee: util.MustParseFloat(d.TransactionFee),
TransactionFee: fixedpoint.MustNewFromString(d.TransactionFee),
WithdrawOrderID: d.WithdrawOrderID,
Network: d.Network,
Status: status,
@ -414,7 +413,7 @@ func (e *Exchange) QueryDepositHistory(ctx context.Context, asset string, since,
Exchange: types.ExchangeBinance,
Time: types.Time(time.Unix(0, d.InsertTime*int64(time.Millisecond))),
Asset: d.Coin,
Amount: util.MustParseFloat(d.Amount),
Amount: fixedpoint.MustNewFromString(d.Amount),
Address: d.Address,
AddressTag: d.AddressTag,
TransactionID: d.TxID,
@ -754,7 +753,8 @@ func (e *Exchange) submitMarginOrder(ctx context.Context, order types.SubmitOrde
if order.Market.Symbol != "" {
req.Quantity(order.Market.FormatQuantity(order.Quantity))
} else {
req.Quantity(strconv.FormatFloat(order.Quantity, 'f', 8, 64))
// TODO report error
req.Quantity(order.Quantity.FormatString(8))
}
// set price field for limit orders
@ -763,7 +763,8 @@ func (e *Exchange) submitMarginOrder(ctx context.Context, order types.SubmitOrde
if order.Market.Symbol != "" {
req.Price(order.Market.FormatPrice(order.Price))
} else {
req.Price(strconv.FormatFloat(order.Price, 'f', 8, 64))
// TODO report error
req.Price(order.Price.FormatString(8))
}
}
@ -774,7 +775,8 @@ func (e *Exchange) submitMarginOrder(ctx context.Context, order types.SubmitOrde
if order.Market.Symbol != "" {
req.StopPrice(order.Market.FormatPrice(order.StopPrice))
} else {
req.StopPrice(strconv.FormatFloat(order.StopPrice, 'f', 8, 64))
// TODO report error
req.StopPrice(order.StopPrice.FormatString(8))
}
}
@ -838,7 +840,8 @@ func (e *Exchange) submitFuturesOrder(ctx context.Context, order types.SubmitOrd
if order.Market.Symbol != "" {
req.Quantity(order.Market.FormatQuantity(order.Quantity))
} else {
req.Quantity(strconv.FormatFloat(order.Quantity, 'f', 8, 64))
// TODO report error
req.Quantity(order.Quantity.FormatString(8))
}
// set price field for limit orders
@ -847,7 +850,8 @@ func (e *Exchange) submitFuturesOrder(ctx context.Context, order types.SubmitOrd
if order.Market.Symbol != "" {
req.Price(order.Market.FormatPrice(order.Price))
} else {
req.Price(strconv.FormatFloat(order.Price, 'f', 8, 64))
// TODO report error
req.Price(order.Price.FormatString(8))
}
}
@ -858,7 +862,8 @@ func (e *Exchange) submitFuturesOrder(ctx context.Context, order types.SubmitOrd
if order.Market.Symbol != "" {
req.StopPrice(order.Market.FormatPrice(order.StopPrice))
} else {
req.StopPrice(strconv.FormatFloat(order.StopPrice, 'f', 8, 64))
// TODO report error
req.StopPrice(order.StopPrice.FormatString(8))
}
}
@ -975,7 +980,8 @@ func (e *Exchange) submitSpotOrder(ctx context.Context, order types.SubmitOrder)
if order.Market.Symbol != "" {
req.Quantity(order.Market.FormatQuantity(order.Quantity))
} else {
req.Quantity(strconv.FormatFloat(order.Quantity, 'f', 8, 64))
// TODO: report error
req.Quantity(order.Quantity.FormatString(8))
}
// set price field for limit orders
@ -984,7 +990,8 @@ func (e *Exchange) submitSpotOrder(ctx context.Context, order types.SubmitOrder)
if order.Market.Symbol != "" {
req.Price(order.Market.FormatPrice(order.Price))
} else {
req.Price(strconv.FormatFloat(order.Price, 'f', 8, 64))
// TODO: report error
req.Price(order.Price.FormatString(8))
}
}
@ -993,7 +1000,8 @@ func (e *Exchange) submitSpotOrder(ctx context.Context, order types.SubmitOrder)
if order.Market.Symbol != "" {
req.StopPrice(order.Market.FormatPrice(order.StopPrice))
} else {
req.StopPrice(strconv.FormatFloat(order.StopPrice, 'f', 8, 64))
// TODO: report error
req.StopPrice(order.StopPrice.FormatString(8))
}
}
@ -1111,14 +1119,14 @@ func (e *Exchange) QueryKLines(ctx context.Context, symbol string, interval type
Interval: interval,
StartTime: types.NewTimeFromUnix(0, k.OpenTime*int64(time.Millisecond)),
EndTime: types.NewTimeFromUnix(0, k.CloseTime*int64(time.Millisecond)),
Open: util.MustParseFloat(k.Open),
Close: util.MustParseFloat(k.Close),
High: util.MustParseFloat(k.High),
Low: util.MustParseFloat(k.Low),
Volume: util.MustParseFloat(k.Volume),
QuoteVolume: util.MustParseFloat(k.QuoteAssetVolume),
TakerBuyBaseAssetVolume: util.MustParseFloat(k.TakerBuyBaseAssetVolume),
TakerBuyQuoteAssetVolume: util.MustParseFloat(k.TakerBuyQuoteAssetVolume),
Open: fixedpoint.MustNewFromString(k.Open),
Close: fixedpoint.MustNewFromString(k.Close),
High: fixedpoint.MustNewFromString(k.High),
Low: fixedpoint.MustNewFromString(k.Low),
Volume: fixedpoint.MustNewFromString(k.Volume),
QuoteVolume: fixedpoint.MustNewFromString(k.QuoteAssetVolume),
TakerBuyBaseAssetVolume: fixedpoint.MustNewFromString(k.TakerBuyBaseAssetVolume),
TakerBuyQuoteAssetVolume: fixedpoint.MustNewFromString(k.TakerBuyQuoteAssetVolume),
LastTradeID: 0,
NumberOfTrades: uint64(k.TradeNum),
Closed: true,

View File

@ -119,13 +119,13 @@ func (e *ExecutionReportEvent) Order() (*types.Order, error) {
ClientOrderID: e.ClientOrderID,
Side: toGlobalSideType(binance.SideType(e.Side)),
Type: toGlobalOrderType(binance.OrderType(e.OrderType)),
Quantity: e.OrderQuantity.Float64(),
Price: e.OrderPrice.Float64(),
Quantity: e.OrderQuantity,
Price: e.OrderPrice,
TimeInForce: e.TimeInForce,
},
OrderID: uint64(e.OrderID),
Status: toGlobalOrderStatus(binance.OrderStatusType(e.CurrentOrderStatus)),
ExecutedQuantity: e.CumulativeFilledQuantity.Float64(),
ExecutedQuantity: e.CumulativeFilledQuantity,
CreationTime: types.Time(orderCreationTime),
}, nil
}
@ -142,13 +142,13 @@ func (e *ExecutionReportEvent) Trade() (*types.Trade, error) {
Symbol: e.Symbol,
OrderID: uint64(e.OrderID),
Side: toGlobalSideType(binance.SideType(e.Side)),
Price: e.LastExecutedPrice.Float64(),
Quantity: e.LastExecutedQuantity.Float64(),
QuoteQuantity: e.LastQuoteAssetTransactedQuantity.Float64(),
Price: e.LastExecutedPrice,
Quantity: e.LastExecutedQuantity,
QuoteQuantity: e.LastQuoteAssetTransactedQuantity,
IsBuyer: e.Side == "BUY",
IsMaker: e.IsMaker,
Time: types.Time(tt),
Fee: e.CommissionAmount.Float64(),
Fee: e.CommissionAmount,
FeeCurrency: e.CommissionAsset,
}, nil
}
@ -528,14 +528,14 @@ func (k *KLine) KLine() types.KLine {
Interval: types.Interval(k.Interval),
StartTime: types.NewTimeFromUnix(0, k.StartTime*int64(time.Millisecond)),
EndTime: types.NewTimeFromUnix(0, k.EndTime*int64(time.Millisecond)),
Open: k.Open.Float64(),
Close: k.Close.Float64(),
High: k.High.Float64(),
Low: k.Low.Float64(),
Volume: k.Volume.Float64(),
QuoteVolume: k.QuoteVolume.Float64(),
TakerBuyBaseAssetVolume: k.TakerBuyBaseAssetVolume.Float64(),
TakerBuyQuoteAssetVolume: k.TakerBuyQuoteAssetVolume.Float64(),
Open: k.Open,
Close: k.Close,
High: k.High,
Low: k.Low,
Volume: k.Volume,
QuoteVolume: k.QuoteVolume,
TakerBuyBaseAssetVolume: k.TakerBuyBaseAssetVolume,
TakerBuyQuoteAssetVolume: k.TakerBuyQuoteAssetVolume,
LastTradeID: uint64(k.LastTradeID),
NumberOfTrades: uint64(k.NumberOfTrades),
Closed: k.Closed,
@ -708,13 +708,13 @@ func (e *OrderTradeUpdateEvent) OrderFutures() (*types.Order, error) {
ClientOrderID: e.OrderTrade.ClientOrderID,
Side: toGlobalFuturesSideType(futures.SideType(e.OrderTrade.Side)),
Type: toGlobalFuturesOrderType(futures.OrderType(e.OrderTrade.OrderType)),
Quantity: e.OrderTrade.OriginalQuantity.Float64(),
Price: e.OrderTrade.OriginalPrice.Float64(),
Quantity: e.OrderTrade.OriginalQuantity,
Price: e.OrderTrade.OriginalPrice,
TimeInForce: e.OrderTrade.TimeInForce,
},
OrderID: uint64(e.OrderTrade.OrderId),
Status: toGlobalFuturesOrderStatus(futures.OrderStatusType(e.OrderTrade.CurrentOrderStatus)),
ExecutedQuantity: e.OrderTrade.OrderFilledAccumulatedQuantity.Float64(),
ExecutedQuantity: e.OrderTrade.OrderFilledAccumulatedQuantity,
CreationTime: types.Time(orderCreationTime),
}, nil
}
@ -731,13 +731,13 @@ func (e *OrderTradeUpdateEvent) TradeFutures() (*types.Trade, error) {
Symbol: e.OrderTrade.Symbol,
OrderID: uint64(e.OrderTrade.OrderId),
Side: toGlobalSideType(binance.SideType(e.OrderTrade.Side)),
Price: e.OrderTrade.LastFilledPrice.Float64(),
Quantity: e.OrderTrade.OrderLastFilledQuantity.Float64(),
QuoteQuantity: e.OrderTrade.OrderFilledAccumulatedQuantity.Float64(),
Price: e.OrderTrade.LastFilledPrice,
Quantity: e.OrderTrade.OrderLastFilledQuantity,
QuoteQuantity: e.OrderTrade.OrderFilledAccumulatedQuantity,
IsBuyer: e.OrderTrade.Side == "BUY",
IsMaker: e.OrderTrade.IsMaker,
Time: types.Time(tt),
Fee: e.OrderTrade.CommissionAmount.Float64(),
Fee: e.OrderTrade.CommissionAmount,
FeeCurrency: e.OrderTrade.CommissionAsset,
}, nil
}

View File

@ -7,7 +7,6 @@ import (
log "github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
@ -64,14 +63,14 @@ func toGlobalOrder(r order) (types.Order, error) {
case "new":
o.Status = types.OrderStatusNew
case "open":
if fixedpoint.NewFromFloat(o.ExecutedQuantity) != fixedpoint.NewFromInt(0) {
if !o.ExecutedQuantity.IsZero() {
o.Status = types.OrderStatusPartiallyFilled
} else {
o.Status = types.OrderStatusNew
}
case "closed":
// filled or canceled
if fixedpoint.NewFromFloat(o.Quantity) == fixedpoint.NewFromFloat(o.ExecutedQuantity) {
if o.Quantity == o.ExecutedQuantity {
o.Status = types.OrderStatusFilled
} else {
// can't distinguish it's canceled or rejected from order response, so always set to canceled
@ -124,7 +123,7 @@ func toGlobalTrade(f fill) (types.Trade, error) {
Exchange: types.ExchangeFTX,
Price: f.Price,
Quantity: f.Size,
QuoteQuantity: f.Price * f.Size,
QuoteQuantity: f.Price.Mul(f.Size),
Symbol: toGlobalSymbol(f.Market),
Side: f.Side,
IsBuyer: f.Side == types.SideTypeBuy,

View File

@ -38,10 +38,10 @@ type Exchange struct {
type MarketTicker struct {
Market types.Market
Price float64
Ask float64
Bid float64
Last float64
Price fixedpoint.Value
Ask fixedpoint.Value
Bid fixedpoint.Value
Last fixedpoint.Value
}
type MarketMap map[string]MarketTicker
@ -138,19 +138,19 @@ func (e *Exchange) _queryMarkets(ctx context.Context) (MarketMap, error) {
LocalSymbol: m.Name,
// The max precision is length(DefaultPow). For example, currently fixedpoint.DefaultPow
// is 1e8, so the max precision will be 8.
PricePrecision: fixedpoint.NumFractionalDigits(fixedpoint.NewFromFloat(m.PriceIncrement)),
VolumePrecision: fixedpoint.NumFractionalDigits(fixedpoint.NewFromFloat(m.SizeIncrement)),
PricePrecision: fixedpoint.NumFractionalDigits(m.PriceIncrement),
VolumePrecision: fixedpoint.NumFractionalDigits(m.SizeIncrement),
QuoteCurrency: toGlobalCurrency(m.QuoteCurrency),
BaseCurrency: toGlobalCurrency(m.BaseCurrency),
// FTX only limit your order by `MinProvideSize`, so I assign zero value to unsupported fields:
// MinNotional, MinAmount, MaxQuantity, MinPrice and MaxPrice.
MinNotional: 0,
MinAmount: 0,
MinNotional: fixedpoint.Zero,
MinAmount: fixedpoint.Zero,
MinQuantity: m.MinProvideSize,
MaxQuantity: 0,
MaxQuantity: fixedpoint.Zero,
StepSize: m.SizeIncrement,
MinPrice: 0,
MaxPrice: 0,
MinPrice: fixedpoint.Zero,
MaxPrice: fixedpoint.Zero,
TickSize: m.PriceIncrement,
},
Price: m.Price,
@ -173,9 +173,9 @@ func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) {
}
a := &types.Account{
MakerCommission: fixedpoint.NewFromFloat(resp.Result.MakerFee),
TakerCommission: fixedpoint.NewFromFloat(resp.Result.TakerFee),
TotalAccountValue: fixedpoint.NewFromFloat(resp.Result.TotalAccountValue),
MakerCommission: resp.Result.MakerFee,
TakerCommission: resp.Result.TakerFee,
TotalAccountValue: resp.Result.TotalAccountValue,
}
balances, err := e.QueryAccountBalances(ctx)
@ -199,8 +199,8 @@ func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap,
for _, r := range resp.Result {
balances[toGlobalCurrency(r.Coin)] = types.Balance{
Currency: toGlobalCurrency(r.Coin),
Available: fixedpoint.NewFromFloat(r.Free),
Locked: fixedpoint.NewFromFloat(r.Total).Sub(fixedpoint.NewFromFloat(r.Free)),
Available: r.Free,
Locked: r.Total.Sub(r.Free),
}
}

View File

@ -6,6 +6,7 @@ import (
"fmt"
"strconv"
"time"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
type orderRequest struct {
@ -28,9 +29,9 @@ type orderRequest struct {
type PlaceOrderPayload struct {
Market string
Side string
Price float64
Price fixedpoint.Value
Type string
Size float64
Size fixedpoint.Value
ReduceOnly bool
IOC bool
PostOnly bool

View File

@ -6,6 +6,7 @@ import (
"time"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
// ex: 2019-03-05T09:56:55.728933+00:00
@ -87,9 +88,9 @@ type accountResponse struct {
}
type account struct {
MakerFee float64 `json:"makerFee"`
TakerFee float64 `json:"takerFee"`
TotalAccountValue float64 `json:"totalAccountValue"`
MakerFee fixedpoint.Value `json:"makerFee"`
TakerFee fixedpoint.Value `json:"takerFee"`
TotalAccountValue fixedpoint.Value `json:"totalAccountValue"`
}
type positionsResponse struct {
@ -117,21 +118,21 @@ type positionsResponse struct {
}
*/
type position struct {
Cost float64 `json:"cost"`
EntryPrice float64 `json:"entryPrice"`
EstimatedLiquidationPrice float64 `json:"estimatedLiquidationPrice"`
Cost fixedpoint.Value `json:"cost"`
EntryPrice fixedpoint.Value `json:"entryPrice"`
EstimatedLiquidationPrice fixedpoint.Value `json:"estimatedLiquidationPrice"`
Future string `json:"future"`
InitialMarginRequirement float64 `json:"initialMarginRequirement"`
LongOrderSize float64 `json:"longOrderSize"`
MaintenanceMarginRequirement float64 `json:"maintenanceMarginRequirement"`
NetSize float64 `json:"netSize"`
OpenSize float64 `json:"openSize"`
RealizedPnl float64 `json:"realizedPnl"`
ShortOrderSize float64 `json:"shortOrderSize"`
InitialMarginRequirement fixedpoint.Value `json:"initialMarginRequirement"`
LongOrderSize fixedpoint.Value `json:"longOrderSize"`
MaintenanceMarginRequirement fixedpoint.Value `json:"maintenanceMarginRequirement"`
NetSize fixedpoint.Value `json:"netSize"`
OpenSize fixedpoint.Value `json:"openSize"`
RealizedPnl fixedpoint.Value `json:"realizedPnl"`
ShortOrderSize fixedpoint.Value `json:"shortOrderSize"`
Side string `json:"Side"`
Size float64 `json:"size"`
UnrealizedPnl float64 `json:"unrealizedPnl"`
CollateralUsed float64 `json:"collateralUsed"`
Size fixedpoint.Value `json:"size"`
UnrealizedPnl fixedpoint.Value `json:"unrealizedPnl"`
CollateralUsed fixedpoint.Value `json:"collateralUsed"`
}
type balances struct {
@ -139,8 +140,8 @@ type balances struct {
Result []struct {
Coin string `json:"coin"`
Free float64 `json:"free"`
Total float64 `json:"total"`
Free fixedpoint.Value `json:"free"`
Total fixedpoint.Value `json:"total"`
} `json:"result"`
}
@ -180,24 +181,24 @@ type market struct {
Name string `json:"name"`
Enabled bool `json:"enabled"`
PostOnly bool `json:"postOnly"`
PriceIncrement float64 `json:"priceIncrement"`
SizeIncrement float64 `json:"sizeIncrement"`
MinProvideSize float64 `json:"minProvideSize"`
Last float64 `json:"last"`
Bid float64 `json:"bid"`
Ask float64 `json:"ask"`
Price float64 `json:"price"`
PriceIncrement fixedpoint.Value `json:"priceIncrement"`
SizeIncrement fixedpoint.Value `json:"sizeIncrement"`
MinProvideSize fixedpoint.Value `json:"minProvideSize"`
Last fixedpoint.Value `json:"last"`
Bid fixedpoint.Value `json:"bid"`
Ask fixedpoint.Value `json:"ask"`
Price fixedpoint.Value `json:"price"`
Type string `json:"type"`
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
Underlying string `json:"underlying"`
Restricted bool `json:"restricted"`
HighLeverageFeeExempt bool `json:"highLeverageFeeExempt"`
Change1h float64 `json:"change1h"`
Change24h float64 `json:"change24h"`
ChangeBod float64 `json:"changeBod"`
QuoteVolume24h float64 `json:"quoteVolume24h"`
VolumeUsd24h float64 `json:"volumeUsd24h"`
Change1h fixedpoint.Value `json:"change1h"`
Change24h fixedpoint.Value `json:"change24h"`
ChangeBod fixedpoint.Value `json:"changeBod"`
QuoteVolume24h fixedpoint.Value `json:"quoteVolume24h"`
VolumeUsd24h fixedpoint.Value `json:"volumeUsd24h"`
}
/*
@ -221,12 +222,12 @@ type HistoricalPricesResponse struct {
}
type Candle struct {
Close float64 `json:"close"`
High float64 `json:"high"`
Low float64 `json:"low"`
Open float64 `json:"open"`
Close fixedpoint.Value `json:"close"`
High fixedpoint.Value `json:"high"`
Low fixedpoint.Value `json:"low"`
Open fixedpoint.Value `json:"open"`
StartTime datetime `json:"startTime"`
Volume float64 `json:"volume"`
Volume fixedpoint.Value `json:"volume"`
}
type ordersHistoryResponse struct {
@ -248,16 +249,16 @@ type cancelOrderResponse struct {
type order struct {
CreatedAt datetime `json:"createdAt"`
FilledSize float64 `json:"filledSize"`
FilledSize fixedpoint.Value `json:"filledSize"`
// Future field is not defined in the response format table but in the response example.
Future string `json:"future"`
ID int64 `json:"id"`
Market string `json:"market"`
Price float64 `json:"price"`
AvgFillPrice float64 `json:"avgFillPrice"`
RemainingSize float64 `json:"remainingSize"`
Price fixedpoint.Value `json:"price"`
AvgFillPrice fixedpoint.Value `json:"avgFillPrice"`
RemainingSize fixedpoint.Value `json:"remainingSize"`
Side string `json:"side"`
Size float64 `json:"size"`
Size fixedpoint.Value `json:"size"`
Status string `json:"status"`
Type string `json:"type"`
ReduceOnly bool `json:"reduceOnly"`
@ -304,9 +305,9 @@ type depositHistory struct {
Address address `json:"address"`
Confirmations int64 `json:"confirmations"`
ConfirmedTime datetime `json:"confirmedTime"`
Fee float64 `json:"fee"`
Fee fixedpoint.Value `json:"fee"`
SentTime datetime `json:"sentTime"`
Size float64 `json:"size"`
Size fixedpoint.Value `json:"size"`
Status string `json:"status"`
Time datetime `json:"time"`
Notes string `json:"notes"`
@ -360,13 +361,13 @@ type fill struct {
QuoteCurrency string `json:"quoteCurrency"`
Type string `json:"type"`
Side types.SideType `json:"side"`
Price float64 `json:"price"`
Size float64 `json:"size"`
Price fixedpoint.Value `json:"price"`
Size fixedpoint.Value `json:"size"`
OrderId uint64 `json:"orderId"`
Time datetime `json:"time"`
TradeId uint64 `json:"tradeId"`
FeeRate float64 `json:"feeRate"`
Fee float64 `json:"fee"`
FeeRate fixedpoint.Value `json:"feeRate"`
Fee fixedpoint.Value `json:"fee"`
FeeCurrency string `json:"feeCurrency"`
Liquidity string `json:"liquidity"`
}
@ -379,7 +380,7 @@ type transferResponse struct {
type transfer struct {
Id uint `json:"id"`
Coin string `json:"coin"`
Size float64 `json:"size"`
Size fixedpoint.Value `json:"size"`
Time string `json:"time"`
Notes string `json:"notes"`
Status string `json:"status"`

View File

@ -9,6 +9,7 @@ import (
"github.com/c9s/bbgo/pkg/exchange/kucoin/kucoinapi"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
func toGlobalBalanceMap(accounts []kucoinapi.Account) types.BalanceMap {
@ -42,28 +43,28 @@ func toGlobalMarket(m kucoinapi.Symbol) types.Market {
VolumePrecision: int(math.Log10(m.BaseIncrement.Float64())),
QuoteCurrency: m.QuoteCurrency,
BaseCurrency: m.BaseCurrency,
MinNotional: m.QuoteMinSize.Float64(),
MinAmount: m.QuoteMinSize.Float64(),
MinQuantity: m.BaseMinSize.Float64(),
MaxQuantity: 0, // not used
StepSize: m.BaseIncrement.Float64(),
MinNotional: m.QuoteMinSize,
MinAmount: m.QuoteMinSize,
MinQuantity: m.BaseMinSize,
MaxQuantity: fixedpoint.Zero, // not used
StepSize: m.BaseIncrement,
MinPrice: 0, // not used
MaxPrice: 0, // not used
TickSize: m.PriceIncrement.Float64(),
MinPrice: fixedpoint.Zero, // not used
MaxPrice: fixedpoint.Zero, // not used
TickSize: m.PriceIncrement,
}
}
func toGlobalTicker(s kucoinapi.Ticker24H) types.Ticker {
return types.Ticker{
Time: s.Time.Time(),
Volume: s.Volume.Float64(),
Last: s.Last.Float64(),
Open: s.Last.Float64() - s.ChangePrice.Float64(),
High: s.High.Float64(),
Low: s.Low.Float64(),
Buy: s.Buy.Float64(),
Sell: s.Sell.Float64(),
Volume: s.Volume,
Last: s.Last,
Open: s.Last.Sub(s.ChangePrice),
High: s.High,
Low: s.Low,
Buy: s.Buy,
Sell: s.Sell,
}
}
@ -146,7 +147,7 @@ func toGlobalOrderStatus(o kucoinapi.Order) types.OrderStatus {
var status types.OrderStatus
if o.IsActive {
status = types.OrderStatusNew
if o.DealSize > 0 {
if o.DealSize.Sign() > 0 {
status = types.OrderStatusPartiallyFilled
}
} else if o.CancelExist {
@ -209,16 +210,16 @@ func toGlobalOrder(o kucoinapi.Order) types.Order {
Symbol: toGlobalSymbol(o.Symbol),
Side: toGlobalSide(o.Side),
Type: toGlobalOrderType(o.Type),
Quantity: o.Size.Float64(),
Price: o.Price.Float64(),
StopPrice: o.StopPrice.Float64(),
Quantity: o.Size,
Price: o.Price,
StopPrice: o.StopPrice,
TimeInForce: string(o.TimeInForce),
},
Exchange: types.ExchangeKucoin,
OrderID: hashStringID(o.ID),
UUID: o.ID,
Status: status,
ExecutedQuantity: o.DealSize.Float64(),
ExecutedQuantity: o.DealSize,
IsWorking: o.IsActive,
CreationTime: types.Time(o.CreatedAt.Time()),
UpdateTime: types.Time(o.CreatedAt.Time()), // kucoin does not response updated time
@ -231,15 +232,15 @@ func toGlobalTrade(fill kucoinapi.Fill) types.Trade {
ID: hashStringID(fill.TradeId),
OrderID: hashStringID(fill.OrderId),
Exchange: types.ExchangeKucoin,
Price: fill.Price.Float64(),
Quantity: fill.Size.Float64(),
QuoteQuantity: fill.Funds.Float64(),
Price: fill.Price,
Quantity: fill.Size,
QuoteQuantity: fill.Funds,
Symbol: toGlobalSymbol(fill.Symbol),
Side: toGlobalSide(string(fill.Side)),
IsBuyer: fill.Side == kucoinapi.SideTypeBuy,
IsMaker: fill.Liquidity == kucoinapi.LiquidityTypeMaker,
Time: types.Time(fill.CreatedAt.Time()),
Fee: fill.Fee.Float64(),
Fee: fill.Fee,
FeeCurrency: toGlobalSymbol(fill.FeeCurrency),
}
return trade

View File

@ -14,6 +14,7 @@ import (
"github.com/c9s/bbgo/pkg/exchange/kucoin/kucoinapi"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
var marketDataLimiter = rate.NewLimiter(rate.Every(500*time.Millisecond), 1)
@ -184,12 +185,12 @@ func (e *Exchange) QueryKLines(ctx context.Context, symbol string, interval type
StartTime: types.Time(k.StartTime),
EndTime: types.Time(k.StartTime.Add(gi.Duration() - time.Millisecond)),
Interval: gi,
Open: k.Open.Float64(),
Close: k.Close.Float64(),
High: k.High.Float64(),
Low: k.Low.Float64(),
Volume: k.Volume.Float64(),
QuoteVolume: k.QuoteVolume.Float64(),
Open: k.Open,
Close: k.Close,
High: k.High,
Low: k.Low,
Volume: k.Volume,
QuoteVolume: k.QuoteVolume,
Closed: true,
})
}
@ -214,7 +215,8 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
if order.Market.Symbol != "" {
req.Size(order.Market.FormatQuantity(order.Quantity))
} else {
req.Size(strconv.FormatFloat(order.Quantity, 'f', 8, 64))
// TODO: report error?
req.Size(order.Quantity.FormatString(8))
}
// set price field for limit orders
@ -223,7 +225,8 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
if order.Market.Symbol != "" {
req.Price(order.Market.FormatPrice(order.Price))
} else {
req.Price(strconv.FormatFloat(order.Price, 'f', 8, 64))
// TODO: report error?
req.Price(order.Price.FormatString(8))
}
}
@ -248,7 +251,7 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
OrderID: hashStringID(orderResponse.OrderID),
UUID: orderResponse.OrderID,
Status: types.OrderStatusNew,
ExecutedQuantity: 0,
ExecutedQuantity: fixedpoint.Zero,
IsWorking: true,
CreationTime: types.Time(time.Now()),
UpdateTime: types.Time(time.Now()),

View File

@ -11,6 +11,7 @@ import (
"github.com/c9s/bbgo/pkg/exchange/kucoin/kucoinapi"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
const readTimeout = 30 * time.Second
@ -116,15 +117,15 @@ func (s *Stream) handlePrivateOrderEvent(e *WebSocketPrivateOrderEvent) {
OrderID: hashStringID(e.OrderId),
ID: hashStringID(e.TradeId),
Exchange: types.ExchangeKucoin,
Price: e.MatchPrice.Float64(),
Quantity: e.MatchSize.Float64(),
QuoteQuantity: e.MatchPrice.Float64() * e.MatchSize.Float64(),
Price: e.MatchPrice,
Quantity: e.MatchSize,
QuoteQuantity: e.MatchPrice.Mul(e.MatchSize),
Symbol: toGlobalSymbol(e.Symbol),
Side: toGlobalSide(e.Side),
IsBuyer: e.Side == "buy",
IsMaker: e.Liquidity == "maker",
Time: types.Time(e.Ts.Time()),
Fee: 0, // not supported
Fee: fixedpoint.Zero, // not supported
FeeCurrency: "", // not supported
})
}
@ -139,7 +140,7 @@ func (s *Stream) handlePrivateOrderEvent(e *WebSocketPrivateOrderEvent) {
status = types.OrderStatusCanceled
}
} else if e.Status == "open" {
if e.FilledSize > 0 {
if e.FilledSize.Sign() > 0 {
status = types.OrderStatusPartiallyFilled
}
}
@ -150,14 +151,14 @@ func (s *Stream) handlePrivateOrderEvent(e *WebSocketPrivateOrderEvent) {
Symbol: toGlobalSymbol(e.Symbol),
Side: toGlobalSide(e.Side),
Type: toGlobalOrderType(e.OrderType),
Quantity: e.Size.Float64(),
Price: e.Price.Float64(),
Quantity: e.Size,
Price: e.Price,
},
Exchange: types.ExchangeKucoin,
OrderID: hashStringID(e.OrderId),
UUID: e.OrderId,
Status: status,
ExecutedQuantity: e.FilledSize.Float64(),
ExecutedQuantity: e.FilledSize,
IsWorking: e.Status == "open",
CreationTime: types.Time(e.OrderTime.Time()),
UpdateTime: types.Time(e.Ts.Time()),

View File

@ -6,7 +6,6 @@ import (
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
)
type WebSocketMessageType string
@ -97,12 +96,12 @@ type WebSocketCandleEvent struct {
func (e *WebSocketCandleEvent) KLine() types.KLine {
startTime := types.MustParseUnixTimestamp(e.Candles[0])
openPrice := util.MustParseFloat(e.Candles[1])
closePrice := util.MustParseFloat(e.Candles[2])
highPrice := util.MustParseFloat(e.Candles[3])
lowPrice := util.MustParseFloat(e.Candles[4])
volume := util.MustParseFloat(e.Candles[5])
quoteVolume := util.MustParseFloat(e.Candles[6])
openPrice := fixedpoint.MustNewFromString(e.Candles[1])
closePrice := fixedpoint.MustNewFromString(e.Candles[2])
highPrice := fixedpoint.MustNewFromString(e.Candles[3])
lowPrice := fixedpoint.MustNewFromString(e.Candles[4])
volume := fixedpoint.MustNewFromString(e.Candles[5])
quoteVolume := fixedpoint.MustNewFromString(e.Candles[6])
kline := types.KLine{
Exchange: types.ExchangeKucoin,
Symbol: toGlobalSymbol(e.Symbol),

View File

@ -2,7 +2,6 @@ package max
import (
"fmt"
"strconv"
"strings"
"time"
@ -11,7 +10,6 @@ import (
"github.com/c9s/bbgo/pkg/exchange/max/maxapi"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
)
func toGlobalCurrency(currency string) string {
@ -77,23 +75,23 @@ func toGlobalOrderStatus(orderState max.OrderState, executedVolume, remainingVol
return types.OrderStatusCanceled
case max.OrderStateFinalizing, max.OrderStateDone:
if executedVolume == 0 {
if executedVolume.IsZero() {
return types.OrderStatusCanceled
} else if remainingVolume == 0 {
} else if remainingVolume.IsZero() {
return types.OrderStatusFilled
}
return types.OrderStatusFilled
case max.OrderStateWait:
if executedVolume > 0 && remainingVolume > 0 {
if executedVolume.Sign() > 0 && remainingVolume.Sign() > 0 {
return types.OrderStatusPartiallyFilled
}
return types.OrderStatusNew
case max.OrderStateConvert:
if executedVolume > 0 && remainingVolume > 0 {
if executedVolume.Sign() > 0 && remainingVolume.Sign() > 0 {
return types.OrderStatusPartiallyFilled
}
@ -189,8 +187,8 @@ func toGlobalOrder(maxOrder max.Order) (*types.Order, error) {
Symbol: toGlobalSymbol(maxOrder.Market),
Side: toGlobalSideType(maxOrder.Side),
Type: toGlobalOrderType(maxOrder.OrderType),
Quantity: util.MustParseFloat(maxOrder.Volume),
Price: util.MustParseFloat(maxOrder.Price),
Quantity: fixedpoint.MustNewFromString(maxOrder.Volume),
Price: fixedpoint.MustNewFromString(maxOrder.Price),
TimeInForce: "GTC", // MAX only supports GTC
GroupID: maxOrder.GroupID,
},
@ -198,7 +196,7 @@ func toGlobalOrder(maxOrder max.Order) (*types.Order, error) {
IsWorking: maxOrder.State == "wait",
OrderID: maxOrder.ID,
Status: toGlobalOrderStatus(maxOrder.State, executedVolume, remainingVolume),
ExecutedQuantity: executedVolume.Float64(),
ExecutedQuantity: executedVolume,
CreationTime: types.Time(maxOrder.CreatedAtMs.Time()),
UpdateTime: types.Time(maxOrder.CreatedAtMs.Time()),
}, nil
@ -211,22 +209,22 @@ func toGlobalTrade(t max.Trade) (*types.Trade, error) {
// trade time
mts := time.Unix(0, t.CreatedAtMilliSeconds*int64(time.Millisecond))
price, err := strconv.ParseFloat(t.Price, 64)
price, err := fixedpoint.NewFromString(t.Price)
if err != nil {
return nil, err
}
quantity, err := strconv.ParseFloat(t.Volume, 64)
quantity, err := fixedpoint.NewFromString(t.Volume)
if err != nil {
return nil, err
}
quoteQuantity, err := strconv.ParseFloat(t.Funds, 64)
quoteQuantity, err := fixedpoint.NewFromString(t.Funds)
if err != nil {
return nil, err
}
fee, err := strconv.ParseFloat(t.Fee, 64)
fee, err := fixedpoint.NewFromString(t.Fee)
if err != nil {
return nil, err
}
@ -276,19 +274,19 @@ func convertWebSocketTrade(t max.TradeUpdate) (*types.Trade, error) {
// trade time
mts := time.Unix(0, t.Timestamp*int64(time.Millisecond))
price, err := strconv.ParseFloat(t.Price, 64)
price, err := fixedpoint.NewFromString(t.Price)
if err != nil {
return nil, err
}
quantity, err := strconv.ParseFloat(t.Volume, 64)
quantity, err := fixedpoint.NewFromString(t.Volume)
if err != nil {
return nil, err
}
quoteQuantity := price * quantity
quoteQuantity := price.Mul(quantity)
fee, err := strconv.ParseFloat(t.Fee, 64)
fee, err := fixedpoint.NewFromString(t.Fee)
if err != nil {
return nil, err
}
@ -327,16 +325,16 @@ func convertWebSocketOrderUpdate(u max.OrderUpdate) (*types.Order, error) {
Symbol: toGlobalSymbol(u.Market),
Side: toGlobalSideType(u.Side),
Type: toGlobalOrderType(u.OrderType),
Quantity: util.MustParseFloat(u.Volume),
Price: util.MustParseFloat(u.Price),
StopPrice: util.MustParseFloat(u.StopPrice),
Quantity: fixedpoint.MustNewFromString(u.Volume),
Price: fixedpoint.MustNewFromString(u.Price),
StopPrice: fixedpoint.MustNewFromString(u.StopPrice),
TimeInForce: "GTC", // MAX only supports GTC
GroupID: u.GroupID,
},
Exchange: types.ExchangeMax,
OrderID: u.ID,
Status: toGlobalOrderStatus(u.State, executedVolume, remainingVolume),
ExecutedQuantity: executedVolume.Float64(),
ExecutedQuantity: executedVolume,
CreationTime: types.Time(time.Unix(0, u.CreatedAtMs*int64(time.Millisecond))),
}, nil
}

View File

@ -6,7 +6,6 @@ import (
"math"
"os"
"sort"
"strconv"
"time"
"github.com/pkg/errors"
@ -60,13 +59,13 @@ func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticke
return &types.Ticker{
Time: ticker.Time,
Volume: util.MustParseFloat(ticker.Volume),
Last: util.MustParseFloat(ticker.Last),
Open: util.MustParseFloat(ticker.Open),
High: util.MustParseFloat(ticker.High),
Low: util.MustParseFloat(ticker.Low),
Buy: util.MustParseFloat(ticker.Buy),
Sell: util.MustParseFloat(ticker.Sell),
Volume: fixedpoint.MustNewFromString(ticker.Volume),
Last: fixedpoint.MustNewFromString(ticker.Last),
Open: fixedpoint.MustNewFromString(ticker.Open),
High: fixedpoint.MustNewFromString(ticker.High),
Low: fixedpoint.MustNewFromString(ticker.Low),
Buy: fixedpoint.MustNewFromString(ticker.Buy),
Sell: fixedpoint.MustNewFromString(ticker.Sell),
}, nil
}
@ -102,13 +101,13 @@ func (e *Exchange) QueryTickers(ctx context.Context, symbol ...string) (map[stri
}
tickers[toGlobalSymbol(k)] = types.Ticker{
Time: v.Time,
Volume: util.MustParseFloat(v.Volume),
Last: util.MustParseFloat(v.Last),
Open: util.MustParseFloat(v.Open),
High: util.MustParseFloat(v.High),
Low: util.MustParseFloat(v.Low),
Buy: util.MustParseFloat(v.Buy),
Sell: util.MustParseFloat(v.Sell),
Volume: fixedpoint.MustNewFromString(v.Volume),
Last: fixedpoint.MustNewFromString(v.Last),
Open: fixedpoint.MustNewFromString(v.Open),
High: fixedpoint.MustNewFromString(v.High),
Low: fixedpoint.MustNewFromString(v.Low),
Buy: fixedpoint.MustNewFromString(v.Buy),
Sell: fixedpoint.MustNewFromString(v.Sell),
}
}
}
@ -139,12 +138,13 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
MinAmount: m.MinQuoteAmount,
MinQuantity: m.MinBaseAmount,
MaxQuantity: 10000.0,
StepSize: 1.0 / math.Pow10(m.BaseUnitPrecision), // make it like 0.0001
MinPrice: 1.0 / math.Pow10(m.QuoteUnitPrecision), // used in the price formatter
MaxPrice: 10000.0,
TickSize: 1.0 / math.Pow10(m.QuoteUnitPrecision),
MaxQuantity: fixedpoint.NewFromInt(10000),
// make it like 0.0001
StepSize: fixedpoint.NewFromFloat(1.0 / math.Pow10(m.BaseUnitPrecision)),
// used in the price formatter
MinPrice: fixedpoint.NewFromFloat(1.0 / math.Pow10(m.QuoteUnitPrecision)),
MaxPrice: fixedpoint.NewFromInt(10000),
TickSize: fixedpoint.NewFromFloat(1.0 / math.Pow10(m.QuoteUnitPrecision)),
}
markets[symbol] = market
@ -421,7 +421,7 @@ func toMaxSubmitOrder(o types.SubmitOrder) (*maxapi.Order, error) {
if o.Market.Symbol != "" {
quantityString = o.Market.FormatQuantity(o.Quantity)
} else {
quantityString = strconv.FormatFloat(o.Quantity, 'f', -1, 64)
quantityString = o.Quantity.String()
}
maxOrder := maxapi.Order{
@ -447,7 +447,7 @@ func toMaxSubmitOrder(o types.SubmitOrder) (*maxapi.Order, error) {
if o.Market.Symbol != "" {
priceInString = o.Market.FormatPrice(o.Price)
} else {
priceInString = strconv.FormatFloat(o.Price, 'f', -1, 64)
priceInString = o.Price.String()
}
maxOrder.Price = priceInString
}
@ -459,7 +459,7 @@ func toMaxSubmitOrder(o types.SubmitOrder) (*maxapi.Order, error) {
if o.Market.Symbol != "" {
priceInString = o.Market.FormatPrice(o.StopPrice)
} else {
priceInString = strconv.FormatFloat(o.StopPrice, 'f', -1, 64)
priceInString = o.StopPrice.String()
}
maxOrder.StopPrice = priceInString
}
@ -713,11 +713,11 @@ func (e *Exchange) QueryWithdrawHistory(ctx context.Context, asset string, since
Exchange: types.ExchangeMax,
ApplyTime: types.Time(time.Unix(d.CreatedAt, 0)),
Asset: toGlobalCurrency(d.Currency),
Amount: util.MustParseFloat(d.Amount),
Amount: fixedpoint.MustNewFromString(d.Amount),
Address: "",
AddressTag: "",
TransactionID: d.TxID,
TransactionFee: util.MustParseFloat(d.Fee),
TransactionFee: fixedpoint.MustNewFromString(d.Fee),
TransactionFeeCurrency: d.FeeCurrency,
// WithdrawOrderID: d.WithdrawOrderID,
// Network: d.Network,
@ -784,7 +784,7 @@ func (e *Exchange) QueryDepositHistory(ctx context.Context, asset string, since,
allDeposits = append(allDeposits, types.Deposit{
Exchange: types.ExchangeMax,
Time: types.Time(time.Unix(d.CreatedAt, 0)),
Amount: util.MustParseFloat(d.Amount),
Amount: fixedpoint.MustNewFromString(d.Amount),
Asset: toGlobalCurrency(d.Currency),
Address: "", // not supported
AddressTag: "", // not supported
@ -965,11 +965,14 @@ func (e *Exchange) QueryKLines(ctx context.Context, symbol string, interval type
return kLines, nil
}
func (e *Exchange) QueryAveragePrice(ctx context.Context, symbol string) (float64, error) {
var Two = fixedpoint.NewFromInt(2)
func (e *Exchange) QueryAveragePrice(ctx context.Context, symbol string) (fixedpoint.Value, error) {
ticker, err := e.client.PublicService.Ticker(toLocalSymbol(symbol))
if err != nil {
return 0, err
return fixedpoint.Zero, err
}
return (util.MustParseFloat(ticker.Sell) + util.MustParseFloat(ticker.Buy)) / 2, nil
return fixedpoint.MustNewFromString(ticker.Sell).
Add(fixedpoint.MustNewFromString(ticker.Buy)).Div(Two), nil
}

View File

@ -11,6 +11,7 @@ import (
"github.com/pkg/errors"
"github.com/valyala/fastjson"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
@ -25,8 +26,8 @@ type Market struct {
BaseUnitPrecision int `json:"base_unit_precision"`
QuoteUnit string `json:"quote_unit"`
QuoteUnitPrecision int `json:"quote_unit_precision"`
MinBaseAmount float64 `json:"min_base_amount"`
MinQuoteAmount float64 `json:"min_quote_amount"`
MinBaseAmount fixedpoint.Value `json:"min_base_amount"`
MinQuoteAmount fixedpoint.Value `json:"min_quote_amount"`
}
type Ticker struct {
@ -206,8 +207,8 @@ type KLine struct {
Symbol string
Interval string
StartTime, EndTime time.Time
Open, High, Low, Close float64
Volume float64
Open, High, Low, Close fixedpoint.Value
Volume fixedpoint.Value
Closed bool
}
@ -309,11 +310,11 @@ func parseKLines(payload []byte, symbol, resolution string, interval Interval) (
Interval: resolution,
StartTime: startTime,
EndTime: endTime,
Open: slice[1].GetFloat64(),
High: slice[2].GetFloat64(),
Low: slice[3].GetFloat64(),
Close: slice[4].GetFloat64(),
Volume: slice[5].GetFloat64(),
Open: fixedpoint.MustNewFromBytes(slice[1].GetStringBytes()),
High: fixedpoint.MustNewFromBytes(slice[2].GetStringBytes()),
Low: fixedpoint.MustNewFromBytes(slice[3].GetStringBytes()),
Close: fixedpoint.MustNewFromBytes(slice[4].GetStringBytes()),
Volume: fixedpoint.MustNewFromBytes(slice[5].GetStringBytes()),
Closed: isClosed,
})
}

View File

@ -9,7 +9,6 @@ import (
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
)
var ErrIncorrectBookEntryElementLength = errors.New("incorrect book entry element length")
@ -122,12 +121,12 @@ func (k KLinePayload) KLine() types.KLine {
EndTime: types.Time(time.Unix(0, k.EndTime*int64(time.Millisecond))),
Symbol: k.Market,
Interval: types.Interval(k.Resolution),
Open: util.MustParseFloat(k.Open),
Close: util.MustParseFloat(k.Close),
High: util.MustParseFloat(k.High),
Low: util.MustParseFloat(k.Low),
Volume: util.MustParseFloat(k.Volume),
QuoteVolume: 0, // TODO: add this from kingfisher
Open: fixedpoint.MustNewFromString(k.Open),
Close: fixedpoint.MustNewFromString(k.Close),
High: fixedpoint.MustNewFromString(k.High),
Low: fixedpoint.MustNewFromString(k.Low),
Volume: fixedpoint.MustNewFromString(k.Volume),
QuoteVolume: fixedpoint.Zero, // TODO: add this from kingfisher
LastTradeID: uint64(k.LastTradeID),
NumberOfTrades: 0, // TODO: add this from kingfisher
Closed: k.Closed,
@ -211,11 +210,11 @@ func parseKLineEvent(val *fastjson.Value) (*KLineEvent, error) {
Interval: string(val.GetStringBytes("k", "R")),
StartTime: time.Unix(0, val.GetInt64("k", "ST")*int64(time.Millisecond)),
EndTime: time.Unix(0, val.GetInt64("k", "ET")*int64(time.Millisecond)),
Open: util.MustParseFloat(string(val.GetStringBytes("k", "O"))),
High: util.MustParseFloat(string(val.GetStringBytes("k", "H"))),
Low: util.MustParseFloat(string(val.GetStringBytes("k", "L"))),
Close: util.MustParseFloat(string(val.GetStringBytes("k", "C"))),
Volume: util.MustParseFloat(string(val.GetStringBytes("k", "v"))),
Open: fixedpoint.MustNewFromBytes(val.GetStringBytes("k", "O")),
High: fixedpoint.MustNewFromBytes(val.GetStringBytes("k", "H")),
Low: fixedpoint.MustNewFromBytes(val.GetStringBytes("k", "L")),
Close: fixedpoint.MustNewFromBytes(val.GetStringBytes("k", "C")),
Volume: fixedpoint.MustNewFromBytes(val.GetStringBytes("k", "v")),
Closed: val.GetBool("k", "x"),
}

View File

@ -7,6 +7,7 @@ import (
"github.com/c9s/bbgo/pkg/exchange/okex/okexapi"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/pkg/errors"
)
@ -28,13 +29,13 @@ func toLocalSymbol(symbol string) string {
func toGlobalTicker(marketTicker okexapi.MarketTicker) *types.Ticker {
return &types.Ticker{
Time: marketTicker.Timestamp.Time(),
Volume: marketTicker.Volume24H.Float64(),
Last: marketTicker.Last.Float64(),
Open: marketTicker.Open24H.Float64(),
High: marketTicker.High24H.Float64(),
Low: marketTicker.Low24H.Float64(),
Buy: marketTicker.BidPrice.Float64(),
Sell: marketTicker.AskPrice.Float64(),
Volume: marketTicker.Volume24H,
Last: marketTicker.Last,
Open: marketTicker.Open24H,
High: marketTicker.High24H,
Low: marketTicker.Low24H,
Buy: marketTicker.BidPrice,
Sell: marketTicker.AskPrice,
}
}
@ -139,15 +140,15 @@ func toGlobalTrades(orderDetails []okexapi.OrderDetails) ([]types.Trade, error)
ID: uint64(tradeID),
OrderID: uint64(orderID),
Exchange: types.ExchangeOKEx,
Price: orderDetail.LastFilledPrice.Float64(),
Quantity: orderDetail.LastFilledQuantity.Float64(),
QuoteQuantity: orderDetail.LastFilledPrice.Float64() * orderDetail.LastFilledQuantity.Float64(),
Price: orderDetail.LastFilledPrice,
Quantity: orderDetail.LastFilledQuantity,
QuoteQuantity: orderDetail.LastFilledPrice.Mul(orderDetail.LastFilledQuantity),
Symbol: toGlobalSymbol(orderDetail.InstrumentID),
Side: side,
IsBuyer: side == types.SideTypeBuy,
IsMaker: orderDetail.ExecutionType == "M",
Time: types.Time(orderDetail.LastFilledTime),
Fee: orderDetail.LastFilledFee.Float64(),
Fee: orderDetail.LastFilledFee,
FeeCurrency: orderDetail.LastFilledFeeCurrency,
IsMargin: false,
IsIsolated: false,
@ -199,15 +200,15 @@ func toGlobalOrders(orderDetails []okexapi.OrderDetails) ([]types.Order, error)
Symbol: toGlobalSymbol(orderDetail.InstrumentID),
Side: side,
Type: orderType,
Price: orderDetail.Price.Float64(),
Quantity: orderDetail.Quantity.Float64(),
StopPrice: 0, // not supported yet
Price: orderDetail.Price,
Quantity: orderDetail.Quantity,
StopPrice: fixedpoint.Zero, // not supported yet
TimeInForce: timeInForce,
},
Exchange: types.ExchangeOKEx,
OrderID: uint64(orderID),
Status: orderStatus,
ExecutedQuantity: orderDetail.FilledQuantity.Float64(),
ExecutedQuantity: orderDetail.FilledQuantity,
IsWorking: isWorking,
CreationTime: types.Time(orderDetail.CreationTime),
UpdateTime: types.Time(orderDetail.UpdateTime),

View File

@ -11,6 +11,7 @@ import (
"github.com/c9s/bbgo/pkg/exchange/okex/okexapi"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
// OKB is the platform currency of OKEx, pre-allocate static string here
@ -69,17 +70,17 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
VolumePrecision: int(-math.Log10(instrument.LotSize.Float64())),
// TickSize: OKEx's price tick, for BTC-USDT it's "0.1"
TickSize: instrument.TickSize.Float64(),
TickSize: instrument.TickSize,
// Quantity step size, for BTC-USDT, it's "0.00000001"
StepSize: instrument.LotSize.Float64(),
StepSize: instrument.LotSize,
// for BTC-USDT, it's "0.00001"
MinQuantity: instrument.MinSize.Float64(),
MinQuantity: instrument.MinSize,
// OKEx does not offer minimal notional, use 1 USD here.
MinNotional: 1.0,
MinAmount: 1.0,
MinNotional: fixedpoint.One,
MinAmount: fixedpoint.One,
}
markets[symbol] = market
}
@ -170,7 +171,8 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
if order.Market.Symbol != "" {
orderReq.Quantity(order.Market.FormatQuantity(order.Quantity))
} else {
orderReq.Quantity(strconv.FormatFloat(order.Quantity, 'f', 8, 64))
// TODO report error
orderReq.Quantity(order.Quantity.FormatString(8))
}
// set price field for limit orders
@ -179,7 +181,8 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
if order.Market.Symbol != "" {
orderReq.Price(order.Market.FormatPrice(order.Price))
} else {
orderReq.Price(strconv.FormatFloat(order.Price, 'f', 8, 64))
// TODO report error
orderReq.Price(order.Price.FormatString(8))
}
}
@ -214,7 +217,7 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
Exchange: types.ExchangeOKEx,
OrderID: uint64(orderID),
Status: types.OrderStatusNew,
ExecutedQuantity: 0,
ExecutedQuantity: fixedpoint.Zero,
IsWorking: true,
CreationTime: types.Time(time.Now()),
UpdateTime: types.Time(time.Now()),

View File

@ -108,6 +108,13 @@ var halfpow10 = [...]uint64{
500000000000000000,
5000000000000000000}
func min(a int, b int) int {
if a < b {
return a
}
return b
}
func (v Value) Value() (driver.Value, error) {
return v.Float64(), nil
}
@ -233,6 +240,41 @@ func Inf(sign int8) Value {
}
}
func (dn Value) FormatString(prec int) string {
if dn.sign == 0 {
return "0"
}
const maxLeadingZeros = 7
sign := ""
if dn.sign < 0 {
sign = "-"
}
if dn.IsInf() {
return sign + "inf"
}
digits := getDigits(dn.coef)
nd := len(digits)
e := int(dn.exp) - nd
if -maxLeadingZeros <= dn.exp && dn.exp <= 0 {
// decimal to the left
return sign + "." + strings.Repeat("0", -e-nd) + digits[:min(prec, nd)]
} else if -nd < e && e <= -1 {
// decimal within
dec := nd + e
return sign + digits[:dec] + "." + digits[dec:min(dec+prec, nd)]
} else if 0 < dn.exp && dn.exp <= digitsMax {
// decimal to the right
return sign + digits + strings.Repeat("0", e)
} else {
// scientific notation
after := ""
if nd > 1 {
after = "." + digits[1:min(1+prec, nd)]
}
return sign + digits[:1] + after + "e" + strconv.Itoa(int(dn.exp-1))
}
}
// String returns a string representation of the Value
func (dn Value) String() string {
if dn.sign == 0 {
@ -360,12 +402,12 @@ func NewFromString(s string) (Value, error) {
s = s[:length-1]
}
r := &reader{s, 0}
sign := getSign(r)
sign := r.getSign()
if r.matchStr("inf") {
return Inf(sign), nil
}
coef, exp := getCoef(r)
exp += getExp(r)
coef, exp := r.getCoef()
exp += r.getExp()
if r.len() != 0 { // didn't consume entire string
return Zero, errors.New("invalid number")
} else if coef == 0 || exp < math.MinInt8 {
@ -388,6 +430,158 @@ func MustNewFromString(input string) Value {
return v
}
func NewFromBytes(s []byte) (Value, error) {
length := len(s)
isPercentage := s[length - 1] == '%'
if isPercentage {
s = s[:length-1]
}
r := &readerBytes{s, 0}
sign := r.getSign()
if r.matchStr("inf") {
return Inf(sign), nil
}
coef, exp := r.getCoef()
exp += r.getExp()
if r.len() != 0 { // didn't consume entire string
return Zero, errors.New("invalid number")
} else if coef == 0 || exp < math.MinInt8 {
return Zero, nil
} else if exp > math.MaxInt8 {
return Inf(sign), nil
}
if isPercentage {
exp -= 2
}
//check(coefMin <= coef && coef <= coefMax)
return Value{coef, sign, exp}, nil
}
func MustNewFromBytes(input []byte) Value {
v, err := NewFromBytes(input)
if err != nil {
panic(fmt.Errorf("cannot parse %s into fixedpoint, error: %s", input, err.Error()))
}
return v
}
// TODO: refactor by interface
type readerBytes struct {
s []byte
i int
}
func (r *readerBytes) cur() byte {
if r.i >= len(r.s) {
return 0
}
return byte(r.s[r.i])
}
func (r *readerBytes) prev() byte {
if r.i == 0 {
return 0
}
return byte(r.s[r.i-1])
}
func (r *readerBytes) len() int {
return len(r.s) - r.i
}
func (r *readerBytes) match(c byte) bool {
if r.cur() == c {
r.i++
return true
}
return false
}
func (r *readerBytes) matchDigit() bool {
c := r.cur()
if '0' <= c && c <= '9' {
r.i++
return true
}
return false
}
func (r *readerBytes) matchStr(pre string) bool {
for i, c := range r.s[r.i:] {
if pre[i] != c {
return false
}
}
r.i += len(pre)
return true
}
func (r *readerBytes) getSign() int8 {
if r.match('-') {
return int8(signNeg)
}
r.match('+')
return int8(signPos)
}
func (r *readerBytes) getCoef() (uint64, int) {
digits := false
beforeDecimal := true
for r.match('0') {
digits = true
}
if r.cur() == '.' && r.len() > 1 {
digits = false
}
n := uint64(0)
exp := 0
p := shiftMax
for {
c := r.cur()
if r.matchDigit() {
digits = true
// ignore extra decimal places
if c != '0' && p >= 0 {
n += uint64(c-'0') * pow10[p]
}
p--
} else if beforeDecimal {
// decimal point or end
exp = shiftMax - p
if !r.match('.') {
break
}
beforeDecimal = false
if !digits {
for r.match('0') {
digits = true
exp--
}
}
} else {
break
}
}
if !digits {
panic("numbers require at least one digit")
}
return n, exp
}
func (r *readerBytes) getExp() int {
e := 0
if r.match('e') || r.match('E') {
esign := r.getSign()
for r.matchDigit() {
e = e*10 + int(r.prev()-'0')
}
e *= int(esign)
}
return e
}
type reader struct {
s string
i int
@ -436,7 +630,7 @@ func (r *reader) matchStr(pre string) bool {
return false
}
func getSign(r *reader) int8 {
func (r *reader) getSign() int8 {
if r.match('-') {
return int8(signNeg)
}
@ -444,7 +638,7 @@ func getSign(r *reader) int8 {
return int8(signPos)
}
func getCoef(r *reader) (uint64, int) {
func (r *reader) getCoef() (uint64, int) {
digits := false
beforeDecimal := true
for r.match('0') {
@ -488,10 +682,10 @@ func getCoef(r *reader) (uint64, int) {
return n, exp
}
func getExp(r *reader) int {
func (r *reader) getExp() int {
e := 0
if r.match('e') || r.match('E') {
esign := getSign(r)
esign := r.getSign()
for r.matchDigit() {
e = e*10 + int(r.prev()-'0')
}

View File

@ -48,6 +48,7 @@ func TestMulString(t *testing.T) {
y := NewFromFloat(10.55)
x = x.Mul(y)
assert.Equal(t, "111.3025", x.String())
assert.Equal(t, "111.30", x.FormatString(2))
}
// Not used

View File

@ -23,10 +23,10 @@ type AD struct {
}
func (inc *AD) update(kLine types.KLine) {
close := kLine.Close
high := kLine.High
low := kLine.Low
volume := kLine.Volume
close := kLine.Close.Float64()
high := kLine.High.Float64()
low := kLine.Low.Float64()
volume := kLine.Volume.Float64()
moneyFlowVolume := ((2*close - high - low) / (high - low)) * volume

View File

@ -93,7 +93,7 @@ func (inc *BOLL) calculateAndUpdate(kLines []types.KLine) {
var prices []float64
for _, k := range recentK {
prices = append(prices, k.Close)
prices = append(prices, k.Close.Float64())
}
var std = stat.StdDev(prices, nil)

View File

@ -115,15 +115,15 @@ func ewma(prices []float64, multiplier float64) float64 {
type KLinePriceMapper func(k types.KLine) float64
func KLineOpenPriceMapper(k types.KLine) float64 {
return k.Open
return k.Open.Float64()
}
func KLineClosePriceMapper(k types.KLine) float64 {
return k.Close
return k.Close.Float64()
}
func KLineTypicalPriceMapper(k types.KLine) float64 {
return (k.High + k.Low + k.Close) / float64(3)
return (k.High.Float64() + k.Low.Float64() + k.Close.Float64()) / 3.
}
func MapKLinePrice(kLines []types.KLine, f KLinePriceMapper) (prices []float64) {

View File

@ -4,7 +4,6 @@ import (
"time"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
/*
@ -17,15 +16,15 @@ On-Balance Volume (OBV) Definition
type OBV struct {
types.IntervalWindow
Values types.Float64Slice
PrePrice fixedpoint.Value
PrePrice float64
EndTime time.Time
UpdateCallbacks []func(value fixedpoint.Value)
UpdateCallbacks []func(value float64)
}
func (inc *OBV) update(kLine types.KLine, priceF KLinePriceMapper) {
price := priceF(kLine)
volume := kLine.Volume
volume := kLine.Volume.Float64()
if len(inc.Values) == 0 {
inc.PrePrice = price

View File

@ -30,10 +30,11 @@ func (inc *STOCH) update(kLine types.KLine) {
inc.KLineWindow.Add(kLine)
inc.KLineWindow.Truncate(inc.Window)
lowest := inc.KLineWindow.GetLow()
highest := inc.KLineWindow.GetHigh()
lowest := inc.KLineWindow.GetLow().Float64()
highest := inc.KLineWindow.GetHigh().Float64()
clos := kLine.Close.Float64()
k := 100.0 * (kLine.Close - lowest) / (highest - lowest)
k := 100.0 * (clos - lowest) / (highest - lowest)
inc.K.Push(k)
d := inc.K.Tail(DPeriod).Mean()

View File

@ -44,7 +44,7 @@ func (inc *VWAP) calculateVWAP(kLines []types.KLine, priceF KLinePriceMapper) (v
func (inc *VWAP) update(kLine types.KLine, priceF KLinePriceMapper, multiplier float64) {
// multiplier = 1 or -1
price := priceF(kLine)
volume := kLine.Volume
volume := kLine.Volume.Float64()
inc.WeightedSum += multiplier * price * volume
inc.VolumeSum += multiplier * volume
@ -63,7 +63,7 @@ func (inc *VWAP) calculateAndUpdate(kLines []types.KLine) {
if len(inc.Values) == 0 {
// for the first value, we should use the close price
price := priceF(kLines[0])
volume := kLines[0].Volume
volume := kLines[0].Volume.Float64()
inc.Values = []float64{price}
inc.WeightedSum = price * volume

View File

@ -35,11 +35,11 @@ func (inc *VWMA) Last() float64 {
}
func KLinePriceVolumeMapper(k types.KLine) float64 {
return k.Close * k.Volume
return k.Close.Mul(k.Volume).Float64()
}
func KLineVolumeMapper(k types.KLine) float64 {
return k.Volume
return k.Volume.Float64()
}
func (inc *VWMA) calculateAndUpdate(kLines []types.KLine) {

View File

@ -81,7 +81,7 @@ func (s *OrderService) Sync(ctx context.Context, exchange types.Exchange, symbol
}
// skip canceled and not filled orders
if order.Status == types.OrderStatusCanceled && order.ExecutedQuantity == 0.0 {
if order.Status == types.OrderStatusCanceled && order.ExecutedQuantity.IsZero() {
continue
}

View File

@ -2,6 +2,7 @@ package types
import (
"time"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
type DepositStatus string
@ -25,7 +26,7 @@ type Deposit struct {
GID int64 `json:"gid" db:"gid"`
Exchange ExchangeName `json:"exchange" db:"exchange"`
Time Time `json:"time" db:"time"`
Amount float64 `json:"amount" db:"amount"`
Amount fixedpoint.Value `json:"amount" db:"amount"`
Asset string `json:"asset" db:"asset"`
Address string `json:"address" db:"address"`
AddressTag string `json:"addressTag"`

View File

@ -74,30 +74,30 @@ type Market struct {
// The MIN_NOTIONAL filter defines the minimum notional value allowed for an order on a symbol.
// An order's notional value is the price * quantity
MinNotional float64 `json:"minNotional,omitempty"`
MinAmount float64 `json:"minAmount,omitempty"`
MinNotional fixedpoint.Value `json:"minNotional,omitempty"`
MinAmount fixedpoint.Value `json:"minAmount,omitempty"`
// The LOT_SIZE filter defines the quantity
MinQuantity float64 `json:"minQuantity,omitempty"`
MinQuantity fixedpoint.Value `json:"minQuantity,omitempty"`
// MaxQuantity is currently not used in the code
MaxQuantity float64 `json:"maxQuantity,omitempty"`
MaxQuantity fixedpoint.Value `json:"maxQuantity,omitempty"`
// StepSize is the step size of quantity
// can be converted from precision, e.g.
// 1.0 / math.Pow10(m.BaseUnitPrecision)
StepSize float64 `json:"stepSize,omitempty"`
StepSize fixedpoint.Value `json:"stepSize,omitempty"`
MinPrice float64 `json:"minPrice,omitempty"`
MaxPrice float64 `json:"maxPrice,omitempty"`
MinPrice fixedpoint.Value `json:"minPrice,omitempty"`
MaxPrice fixedpoint.Value `json:"maxPrice,omitempty"`
// TickSize is the step size of price
TickSize float64 `json:"tickSize,omitempty"`
TickSize fixedpoint.Value `json:"tickSize,omitempty"`
}
// TruncateQuantity uses the step size to truncate floating number, in order to avoid the rounding issue
func (m Market) TruncateQuantity(quantity fixedpoint.Value) fixedpoint.Value {
stepRound := math.Pow10(-int(math.Log10(m.StepSize)))
stepRound := math.Pow10(-int(math.Log10(m.StepSize.Float64())))
return fixedpoint.NewFromFloat(math.Trunc(quantity.Float64()*stepRound) / stepRound)
}
@ -125,55 +125,59 @@ func (m Market) QuoteCurrencyFormatter() *accounting.Accounting {
return a
}
func (m Market) FormatPriceCurrency(val float64) string {
func (m Market) FormatPriceCurrency(val fixedpoint.Value) string {
switch m.QuoteCurrency {
case "USD", "USDT":
return USD.FormatMoneyFloat64(val)
return USD.FormatMoney(val)
case "BTC":
return BTC.FormatMoneyFloat64(val)
return BTC.FormatMoney(val)
case "BNB":
return BNB.FormatMoneyFloat64(val)
return BNB.FormatMoney(val)
}
return m.FormatPrice(val)
}
func (m Market) FormatPrice(val float64) string {
func (m Market) FormatPrice(val fixedpoint.Value) string {
// p := math.Pow10(m.PricePrecision)
return formatPrice(val, m.TickSize)
}
func formatPrice(price float64, tickSize float64) string {
prec := int(math.Round(math.Abs(math.Log10(tickSize))))
func formatPrice(price fixedpoint.Value, tickSize fixedpoint.Value) string {
// TODO Round
prec := int(math.Round(math.Abs(math.Log10(tickSize.Float64()))))
p := math.Pow10(prec)
price = math.Trunc(price*p) / p
return strconv.FormatFloat(price, 'f', prec, 64)
pp := math.Trunc(price.Float64()*p) / p
return strconv.FormatFloat(pp, 'f', prec, 64)
}
func (m Market) FormatQuantity(val float64) string {
func (m Market) FormatQuantity(val fixedpoint.Value) string {
return formatQuantity(val, m.StepSize)
}
func formatQuantity(quantity float64, lot float64) string {
prec := int(math.Round(math.Abs(math.Log10(lot))))
func formatQuantity(quantity fixedpoint.Value, lot fixedpoint.Value) string {
// TODO Round
prec := int(math.Round(math.Abs(math.Log10(lot.Float64()))))
p := math.Pow10(prec)
quantity = math.Trunc(quantity*p) / p
return strconv.FormatFloat(quantity, 'f', prec, 64)
q := math.Trunc(quantity.Float64() * p) / p
return strconv.FormatFloat(q, 'f', prec, 64)
}
func (m Market) FormatVolume(val float64) string {
func (m Market) FormatVolume(val fixedpoint.Value) string {
// TODO Round
p := math.Pow10(m.VolumePrecision)
val = math.Trunc(val*p) / p
return strconv.FormatFloat(val, 'f', m.VolumePrecision, 64)
v := math.Trunc(val.Float64()*p) / p
return strconv.FormatFloat(v, 'f', m.VolumePrecision, 64)
}
func (m Market) CanonicalizeVolume(val float64) float64 {
func (m Market) CanonicalizeVolume(val fixedpoint.Value) float64 {
// TODO Round
p := math.Pow10(m.VolumePrecision)
return math.Trunc(p*val) / p
return math.Trunc(p*val.Float64()) / p
}
type MarketMap map[string]Market

View File

@ -11,6 +11,7 @@ import (
"github.com/slack-go/slack"
"github.com/c9s/bbgo/pkg/util"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
func init() {
@ -108,9 +109,9 @@ type SubmitOrder struct {
Side SideType `json:"side" db:"side"`
Type OrderType `json:"orderType" db:"order_type"`
Quantity float64 `json:"quantity" db:"quantity"`
Price float64 `json:"price" db:"price"`
StopPrice float64 `json:"stopPrice,omitempty" db:"stop_price"`
Quantity fixedpoint.Value `json:"quantity" db:"quantity"`
Price fixedpoint.Value `json:"price" db:"price"`
StopPrice fixedpoint.Value `json:"stopPrice,omitempty" db:"stop_price"`
Market Market `json:"-" db:"-"`
@ -129,40 +130,40 @@ type SubmitOrder struct {
func (o SubmitOrder) String() string {
switch o.Type {
case OrderTypeMarket:
return fmt.Sprintf("SubmitOrder %s %s %s %f", o.Symbol, o.Type, o.Side, o.Quantity)
return fmt.Sprintf("SubmitOrder %s %s %s %s", o.Symbol, o.Type, o.Side, o.Quantity.String())
}
return fmt.Sprintf("SubmitOrder %s %s %s %f @ %f", o.Symbol, o.Type, o.Side, o.Quantity, o.Price)
return fmt.Sprintf("SubmitOrder %s %s %s %s @ %s", o.Symbol, o.Type, o.Side, o.Quantity.String(), o.Price.String())
}
func (o SubmitOrder) PlainText() string {
switch o.Type {
case OrderTypeMarket:
return fmt.Sprintf("SubmitOrder %s %s %s %f", o.Symbol, o.Type, o.Side, o.Quantity)
return fmt.Sprintf("SubmitOrder %s %s %s %s", o.Symbol, o.Type, o.Side, o.Quantity.String())
}
return fmt.Sprintf("SubmitOrder %s %s %s %f @ %f", o.Symbol, o.Type, o.Side, o.Quantity, o.Price)
return fmt.Sprintf("SubmitOrder %s %s %s %s @ %s", o.Symbol, o.Type, o.Side, o.Quantity.String(), o.Price.String())
}
func (o SubmitOrder) SlackAttachment() slack.Attachment {
var fields = []slack.AttachmentField{
{Title: "Symbol", Value: o.Symbol, Short: true},
{Title: "Side", Value: string(o.Side), Short: true},
{Title: "Price", Value: trimTrailingZeroFloat(o.Price), Short: true},
{Title: "Quantity", Value: trimTrailingZeroFloat(o.Quantity), Short: true},
{Title: "Price", Value: o.Price.String(), Short: true},
{Title: "Quantity", Value: o.Quantity.String(), Short: true},
}
if o.Price > 0 && o.Quantity > 0 && len(o.Market.QuoteCurrency) > 0 {
if o.Price.Sign() > 0 && o.Quantity.Sign() > 0 && len(o.Market.QuoteCurrency) > 0 {
if IsFiatCurrency(o.Market.QuoteCurrency) {
fields = append(fields, slack.AttachmentField{
Title: "Amount",
Value: USD.FormatMoneyFloat64(o.Price * o.Quantity),
Value: USD.FormatMoney(o.Price.Mul(o.Quantity)),
Short: true,
})
} else {
fields = append(fields, slack.AttachmentField{
Title: "Amount",
Value: fmt.Sprintf("%f %s", o.Price*o.Quantity, o.Market.QuoteCurrency),
Value: fmt.Sprintf("%s %s", o.Price.Mul(o.Quantity).String(), o.Market.QuoteCurrency),
Short: true,
})
}
@ -201,7 +202,7 @@ type Order struct {
UUID string `json:"uuid,omitempty"`
Status OrderStatus `json:"status" db:"status"`
ExecutedQuantity float64 `json:"executedQuantity" db:"executed_quantity"`
ExecutedQuantity fixedpoint.Value `json:"executedQuantity" db:"executed_quantity"`
IsWorking bool `json:"isWorking" db:"is_working"`
CreationTime Time `json:"creationTime" db:"created_at"`
UpdateTime Time `json:"updateTime" db:"updated_at"`
@ -214,7 +215,7 @@ type Order struct {
// so that we can post the order later when we want to restore the orders.
func (o Order) Backup() SubmitOrder {
so := o.SubmitOrder
so.Quantity = o.Quantity - o.ExecutedQuantity
so.Quantity = o.Quantity.Sub(o.ExecutedQuantity)
// ClientOrderID can not be reused
so.ClientOrderID = ""
@ -229,14 +230,14 @@ func (o Order) String() string {
orderID = strconv.FormatUint(o.OrderID, 10)
}
return fmt.Sprintf("ORDER %s %s %s %s %f/%f @ %f -> %s",
return fmt.Sprintf("ORDER %s %s %s %s %s/%s @ %s -> %s",
o.Exchange.String(),
orderID,
o.Symbol,
o.Side,
o.ExecutedQuantity,
o.Quantity,
o.Price,
o.ExecutedQuantity.String(),
o.Quantity.String(),
o.Price.String(),
o.Status)
}
@ -247,9 +248,9 @@ func (o Order) PlainText() string {
o.Symbol,
o.Type,
o.Side,
util.FormatFloat(o.Price, 2),
util.FormatFloat(o.ExecutedQuantity, 2),
util.FormatFloat(o.Quantity, 4),
o.Price.FormatString(2),
o.ExecutedQuantity.FormatString(2),
o.Quantity.FormatString(4),
o.Status)
}
@ -257,10 +258,10 @@ func (o Order) SlackAttachment() slack.Attachment {
var fields = []slack.AttachmentField{
{Title: "Symbol", Value: o.Symbol, Short: true},
{Title: "Side", Value: string(o.Side), Short: true},
{Title: "Price", Value: trimTrailingZeroFloat(o.Price), Short: true},
{Title: "Price", Value: o.Price.String(), Short: true},
{
Title: "Executed Quantity",
Value: trimTrailingZeroFloat(o.ExecutedQuantity) + "/" + trimTrailingZeroFloat(o.Quantity),
Value: o.ExecutedQuantity.String() + "/" + o.Quantity.String(),
Short: true,
},
}

View File

@ -53,11 +53,10 @@ type Position struct {
sync.Mutex
}
func (p *Position) NewClosePositionOrder(percentage float64) *SubmitOrder {
func (p *Position) NewClosePositionOrder(percentage fixedpoint.Value) *SubmitOrder {
base := p.GetBase()
quantity := base.Float64()
quantity = quantity * percentage
if quantity < p.Market.MinQuantity {
quantity := base.Mul(percentage)
if quantity.Compare(p.Market.MinQuantity) < 0 {
return nil
}
@ -67,8 +66,6 @@ func (p *Position) NewClosePositionOrder(percentage float64) *SubmitOrder {
return nil
} else if sign < 0 {
side = SideTypeBuy
} else if sign > 0 {
side = SideTypeSell
}
return &SubmitOrder{
@ -189,9 +186,9 @@ func (p *Position) SlackAttachment() slack.Attachment {
title := util.Render(string(posType)+` Position {{ .Symbol }} `, p)
fields := []slack.AttachmentField{
{Title: "Average Cost", Value: trimTrailingZeroFloat(averageCost.Float64()) + " " + p.QuoteCurrency, Short: true},
{Title: p.BaseCurrency, Value: trimTrailingZeroFloat(base.Float64()), Short: true},
{Title: p.QuoteCurrency, Value: trimTrailingZeroFloat(quote.Float64())},
{Title: "Average Cost", Value: averageCost.String() + " " + p.QuoteCurrency, Short: true},
{Title: p.BaseCurrency, Value: base.String(), Short: true},
{Title: p.QuoteCurrency, Value: quote.String()},
}
if p.TotalFee != nil {
@ -199,7 +196,7 @@ func (p *Position) SlackAttachment() slack.Attachment {
if fee.Sign() > 0 {
fields = append(fields, slack.AttachmentField{
Title: fmt.Sprintf("Fee (%s)", feeCurrency),
Value: trimTrailingZeroFloat(fee.Float64()),
Value: fee.String(),
Short: true,
})
}
@ -222,14 +219,14 @@ func (p *Position) PlainText() (msg string) {
msg = fmt.Sprintf("%s Position %s: average cost = %s, base = %s, quote = %s",
posType,
p.Symbol,
trimTrailingZeroFloat(p.AverageCost.Float64()),
trimTrailingZeroFloat(p.Base.Float64()),
trimTrailingZeroFloat(p.Quote.Float64()),
p.AverageCost.String(),
p.Base.String(),
p.Quote.String(),
)
if p.TotalFee != nil {
for feeCurrency, fee := range p.TotalFee {
msg += fmt.Sprintf("\nfee (%s) = %s", feeCurrency, trimTrailingZeroFloat(fee.Float64()))
msg += fmt.Sprintf("\nfee (%s) = %s", feeCurrency, fee.String())
}
}

View File

@ -2,15 +2,16 @@ package types
import (
"time"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
type Ticker struct {
Time time.Time
Volume float64 // `volume` from Max & binance
Last float64 // `last` from Max, `lastPrice` from binance
Open float64 // `open` from Max, `openPrice` from binance
High float64 // `high` from Max, `highPrice` from binance
Low float64 // `low` from Max, `lowPrice` from binance
Buy float64 // `buy` from Max, `bidPrice` from binance
Sell float64 // `sell` from Max, `askPrice` from binance
Volume fixedpoint.Value // `volume` from Max & binance
Last fixedpoint.Value // `last` from Max, `lastPrice` from binance
Open fixedpoint.Value // `open` from Max, `openPrice` from binance
High fixedpoint.Value // `high` from Max, `highPrice` from binance
Low fixedpoint.Value // `low` from Max, `lowPrice` from binance
Buy fixedpoint.Value // `buy` from Max, `bidPrice` from binance
Sell fixedpoint.Value // `sell` from Max, `askPrice` from binance
}

View File

@ -90,7 +90,7 @@ func (trade Trade) PositionChange() fixedpoint.Value {
return fixedpoint.Zero
}
func trimTrailingZero(a string) string {
/*func trimTrailingZero(a string) string {
index := strings.Index(a, ".")
if index == -1 {
return a
@ -111,9 +111,9 @@ func trimTrailingZero(a string) string {
return a
}
func trimTrailingZeroFloat(a float64) string {
func trimTrailingZero(a float64) string {
return trimTrailingZero(fmt.Sprintf("%f", a))
}
}*/
// String is for console output
func (trade Trade) String() string {
@ -121,10 +121,10 @@ func (trade Trade) String() string {
trade.Exchange.String(),
trade.Symbol,
trade.Side,
trimTrailingZeroFloat(trade.Quantity.Float64()),
trimTrailingZeroFloat(trade.Price.Float64()),
trimTrailingZeroFloat(trade.QuoteQuantity.Float64()),
trimTrailingZeroFloat(trade.Fee.Float64()),
trade.Quantity.String(),
trade.Price.String(),
trade.QuoteQuantity.String(),
trade.Fee.String(),
trade.FeeCurrency,
trade.OrderID,
trade.Time.Time().Format(time.StampMilli),
@ -137,10 +137,10 @@ func (trade Trade) PlainText() string {
trade.Exchange.String(),
trade.Symbol,
trade.Side,
trimTrailingZeroFloat(trade.Quantity.Float64()),
trimTrailingZeroFloat(trade.Price.Float64()),
trimTrailingZeroFloat(trade.QuoteQuantity.Float64()),
trimTrailingZeroFloat(trade.Fee.Float64()),
trade.Quantity.String(),
trade.Price.String(),
trade.QuoteQuantity.String(),
trade.Fee.String(),
trade.FeeCurrency)
}
@ -184,10 +184,10 @@ func (trade Trade) SlackAttachment() slack.Attachment {
Color: color,
Fields: []slack.AttachmentField{
{Title: "Exchange", Value: trade.Exchange.String(), Short: true},
{Title: "Price", Value: trimTrailingZeroFloat(trade.Price.Float64()), Short: true},
{Title: "Quantity", Value: trimTrailingZeroFloat(trade.Quantity.Float64()), Short: true},
{Title: "QuoteQuantity", Value: trimTrailingZeroFloat(trade.QuoteQuantity.Float64()), Short: true},
{Title: "Fee", Value: trimTrailingZeroFloat(trade.Fee.Float64()), Short: true},
{Title: "Price", Value: trade.Price.String(), Short: true},
{Title: "Quantity", Value: trade.Quantity.String(), Short: true},
{Title: "QuoteQuantity", Value: trade.QuoteQuantity.String(), Short: true},
{Title: "Fee", Value: trade.Fee.String(), Short: true},
{Title: "FeeCurrency", Value: trade.FeeCurrency, Short: true},
{Title: "Liquidity", Value: liquidity, Short: true},
{Title: "Order ID", Value: strconv.FormatUint(trade.OrderID, 10), Short: true},

View File

@ -3,19 +3,20 @@ package types
import (
"fmt"
"time"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
type Withdraw struct {
GID int64 `json:"gid" db:"gid"`
Exchange ExchangeName `json:"exchange" db:"exchange"`
Asset string `json:"asset" db:"asset"`
Amount float64 `json:"amount" db:"amount"`
Amount fixedpoint.Value `json:"amount" db:"amount"`
Address string `json:"address" db:"address"`
AddressTag string `json:"addressTag"`
Status string `json:"status"`
TransactionID string `json:"transactionID" db:"txn_id"`
TransactionFee float64 `json:"transactionFee" db:"txn_fee"`
TransactionFee fixedpoint.Value `json:"transactionFee" db:"txn_fee"`
TransactionFeeCurrency string `json:"transactionFeeCurrency" db:"txn_fee_currency"`
WithdrawOrderID string `json:"withdrawOrderId"`
ApplyTime Time `json:"applyTime" db:"time"`

View File

@ -20,7 +20,7 @@ func Pow10(n int64) int64 {
}
func FormatValue(val fixedpoint.Value, prec int) string {
return strconv.FormatFloat(val.Float64(), 'f', prec, 64)
return val.FormatString(prec)
}
func FormatFloat(val float64, prec int) string {