Skip to content

Commit

Permalink
add dnum as the fixedpoint implementation. change types float64 to fi…
Browse files Browse the repository at this point in the history
…xedpoint.Value

change pnl report to use fixedpoint

fix: migrate kline to use fixedpoint
  • Loading branch information
zenixls2 committed Feb 15, 2022
1 parent 336d868 commit e221f54
Show file tree
Hide file tree
Showing 26 changed files with 1,619 additions and 812 deletions.
58 changes: 30 additions & 28 deletions pkg/accounting/cost_distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"sync"

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

func zero(a float64) bool {
Expand All @@ -25,30 +26,30 @@ func (stock *Stock) String() string {
return fmt.Sprintf("%f (%f)", stock.Price, stock.Quantity)
}

func (stock *Stock) Consume(quantity float64) float64 {
q := math.Min(stock.Quantity, quantity)
stock.Quantity = round(stock.Quantity - q)
func (stock *Stock) Consume(quantity fixedpoint.Value) fixedpoint.Value {
q := fixedpoint.Min(stock.Quantity, quantity)
stock.Quantity = stock.Quantity.Sub(q).Round(0, fixedpoint.Down)
return q
}

type StockSlice []Stock

func (slice StockSlice) QuantityBelowPrice(price float64) (quantity float64) {
func (slice StockSlice) QuantityBelowPrice(price fixedpoint.Value) (quantity fixedpoint.Value) {
for _, stock := range slice {
if stock.Price < price {
quantity += stock.Quantity
if stock.Price.Compare(price) < 0 {
quantity = quantity.Add(stock.Quantity)
}
}

return round(quantity)
return quantity.Round(0, fixedpoint.Down)
}

func (slice StockSlice) Quantity() (total float64) {
func (slice StockSlice) Quantity() (total fixedpoint.Value) {
for _, stock := range slice {
total += stock.Quantity
total = total.Add(stock.Quantity)
}

return round(total)
return total.Round(0, fixedpoint.Down)
}

type StockDistribution struct {
Expand All @@ -62,27 +63,28 @@ type StockDistribution struct {

type DistributionStats struct {
PriceLevels []string `json:"priceLevels"`
TotalQuantity float64 `json:"totalQuantity"`
Quantities map[string]float64 `json:"quantities"`
TotalQuantity fixedpoint.Value `json:"totalQuantity"`
Quantities map[string]fixedpoint.Value `json:"quantities"`
Stocks map[string]StockSlice `json:"stocks"`
}

func (m *StockDistribution) DistributionStats(level int) *DistributionStats {
var d = DistributionStats{
Quantities: map[string]float64{},
Quantities: map[string]fixedpoint.Value {},
Stocks: map[string]StockSlice{},
}

for _, stock := range m.Stocks {
n := math.Ceil(math.Log10(stock.Price))
n := math.Ceil(math.Log10(stock.Price.Float64()))
digits := int(n - math.Max(float64(level), 1.0))
// TODO: use Round function in fixedpoint
div := math.Pow10(digits)
priceLevel := math.Floor(stock.Price/div) * div
priceLevel := math.Floor(stock.Price.Float64()/div) * div
key := strconv.FormatFloat(priceLevel, 'f', 2, 64)

d.TotalQuantity += stock.Quantity
d.TotalQuantity = d.TotalQuantity.Add(stock.Quantity)
d.Stocks[key] = append(d.Stocks[key], stock)
d.Quantities[key] += stock.Quantity
d.Quantities[key] = d.Quantities[key].Add(stock.Quantity)
}

var priceLevels []float64
Expand Down Expand Up @@ -114,7 +116,7 @@ func (m *StockDistribution) squash() {

var squashed StockSlice
for _, stock := range m.Stocks {
if !zero(stock.Quantity) {
if !stock.Quantity.IsZero() {
squashed = append(squashed, stock)
}
}
Expand Down Expand Up @@ -152,19 +154,19 @@ func (m *StockDistribution) consume(sell Stock) error {
stock := m.Stocks[idx]

// find any stock price is lower than the sell trade
if stock.Price >= sell.Price {
if stock.Price.Compare(sell.Price) >= 0 {
continue
}

if zero(stock.Quantity) {
if stock.Quantity.IsZero() {
continue
}

delta := stock.Consume(sell.Quantity)
sell.Consume(delta)
m.Stocks[idx] = stock

if zero(sell.Quantity) {
if sell.Quantity.IsZero() {
return nil
}
}
Expand All @@ -173,20 +175,20 @@ func (m *StockDistribution) consume(sell Stock) error {
for ; idx >= 0; idx-- {
stock := m.Stocks[idx]

if zero(stock.Quantity) {
if stock.Quantity.IsZero() {
continue
}

delta := stock.Consume(sell.Quantity)
sell.Consume(delta)
m.Stocks[idx] = stock

if zero(sell.Quantity) {
if sell.Quantity.IsZero() {
return nil
}
}

if sell.Quantity > 0.0 {
if sell.Quantity.Sign() > 0 {
m.PendingSells = append(m.PendingSells, sell)
}

Expand All @@ -203,7 +205,7 @@ func (m *StockDistribution) AddTrades(trades []types.Trade) (checkpoints []int,
trade.Symbol = m.Symbol
trade.IsBuyer = false
trade.Quantity = trade.Fee
trade.Fee = 0.0
trade.Fee = fixedpoint.Zero
}
}

Expand Down Expand Up @@ -238,11 +240,11 @@ func (m *StockDistribution) AddTrades(trades []types.Trade) (checkpoints []int,
func toStock(trade types.Trade) Stock {
if strings.HasPrefix(trade.Symbol, trade.FeeCurrency) {
if trade.IsBuyer {
trade.Quantity -= trade.Fee
trade.Quantity = trade.Quantity.Sub(trade.Fee)
} else {
trade.Quantity += trade.Fee
trade.Quantity = trade.Quantity.Add(trade.Fee)
}
trade.Fee = 0.0
trade.Fee = fixedpoint.Zero
}
return Stock(trade)
}
29 changes: 15 additions & 14 deletions pkg/accounting/pnl/avg_cost.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ type AverageCostCalculator struct {
Market types.Market
}

func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, currentPrice float64) *AverageCostPnlReport {
func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, currentPrice fixedpoint.Value) *AverageCostPnlReport {
// copy trades, so that we can truncate it.
var bidVolume = 0.0
var askVolume = 0.0
var feeUSD = 0.0
var bidVolume = fixedpoint.Zero
var askVolume = fixedpoint.Zero
var feeUSD = fixedpoint.Zero

if len(trades) == 0 {
return &AverageCostPnlReport{
Expand All @@ -32,7 +32,7 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
}
}

var currencyFees = map[string]float64{}
var currencyFees = map[string]fixedpoint.Value{}

var position = types.NewPositionFromMarket(c.Market)
position.SetFeeRate(types.ExchangeFee{
Expand Down Expand Up @@ -60,26 +60,27 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c

profit, netProfit, madeProfit := position.AddTrade(trade)
if madeProfit {
totalProfit += profit
totalNetProfit += netProfit
totalProfit = totalProfit.Add(profit)
totalNetProfit = totalNetProfit.Add(netProfit)
}

if trade.IsBuyer {
bidVolume += trade.Quantity
bidVolume = bidVolume.Add(trade.Quantity)
} else {
askVolume += trade.Quantity
askVolume = askVolume.Add(trade.Quantity)
}

if _, ok := currencyFees[trade.FeeCurrency]; !ok {
currencyFees[trade.FeeCurrency] = trade.Fee
} else {
currencyFees[trade.FeeCurrency] += trade.Fee
currencyFees[trade.FeeCurrency] = currencyFees[trade.FeeCurrency].Add(trade.Fee)
}

tradeIDs[trade.ID] = trade
}

unrealizedProfit := (fixedpoint.NewFromFloat(currentPrice) - position.AverageCost).Mul(position.GetBase())
unrealizedProfit := currentPrice.Sub(position.AverageCost).
Mul(position.GetBase())
return &AverageCostPnlReport{
Symbol: symbol,
Market: c.Market,
Expand All @@ -90,12 +91,12 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
BuyVolume: bidVolume,
SellVolume: askVolume,

Stock: position.GetBase().Float64(),
Stock: position.GetBase(),
Profit: totalProfit,
NetProfit: totalNetProfit,
UnrealizedProfit: unrealizedProfit,
AverageCost: position.AverageCost.Float64(),
FeeInUSD: (totalProfit - totalNetProfit).Float64(),
AverageCost: position.AverageCost,
FeeInUSD: totalProfit.Sub(totalNetProfit),
CurrencyFees: currencyFees,
}
}
38 changes: 19 additions & 19 deletions pkg/accounting/pnl/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

type AverageCostPnlReport struct {
LastPrice float64 `json:"lastPrice"`
LastPrice fixedpoint.Value `json:"lastPrice"`
StartTime time.Time `json:"startTime"`
Symbol string `json:"symbol"`
Market types.Market `json:"market"`
Expand All @@ -23,12 +23,12 @@ type AverageCostPnlReport struct {
Profit fixedpoint.Value `json:"profit"`
NetProfit fixedpoint.Value `json:"netProfit"`
UnrealizedProfit fixedpoint.Value `json:"unrealizedProfit"`
AverageCost float64 `json:"averageCost"`
BuyVolume float64 `json:"buyVolume,omitempty"`
SellVolume float64 `json:"sellVolume,omitempty"`
FeeInUSD float64 `json:"feeInUSD"`
Stock float64 `json:"stock"`
CurrencyFees map[string]float64 `json:"currencyFees"`
AverageCost fixedpoint.Value `json:"averageCost"`
BuyVolume fixedpoint.Value `json:"buyVolume,omitempty"`
SellVolume fixedpoint.Value `json:"sellVolume,omitempty"`
FeeInUSD fixedpoint.Value `json:"feeInUSD"`
Stock fixedpoint.Value `json:"stock"`
CurrencyFees map[string]fixedpoint.Value `json:"currencyFees"`
}

func (report *AverageCostPnlReport) JSON() ([]byte, error) {
Expand All @@ -38,26 +38,26 @@ func (report *AverageCostPnlReport) JSON() ([]byte, error) {
func (report AverageCostPnlReport) Print() {
log.Infof("TRADES SINCE: %v", report.StartTime)
log.Infof("NUMBER OF TRADES: %d", report.NumTrades)
log.Infof("AVERAGE COST: %s", types.USD.FormatMoneyFloat64(report.AverageCost))
log.Infof("TOTAL BUY VOLUME: %f", report.BuyVolume)
log.Infof("TOTAL SELL VOLUME: %f", report.SellVolume)
log.Infof("STOCK: %f", report.Stock)
log.Infof("AVERAGE COST: %s", types.USD.FormatMoney(report.AverageCost))
log.Infof("TOTAL BUY VOLUME: %s", report.BuyVolume.String())
log.Infof("TOTAL SELL VOLUME: %s", report.SellVolume.String())
log.Infof("STOCK: %s", report.Stock.String())

// FIXME:
// log.Infof("FEE (USD): %f", report.FeeInUSD)
log.Infof("CURRENT PRICE: %s", types.USD.FormatMoneyFloat64(report.LastPrice))
log.Infof("CURRENT PRICE: %s", types.USD.FormatMoney(report.LastPrice))
log.Infof("CURRENCY FEES:")
for currency, fee := range report.CurrencyFees {
log.Infof(" - %s: %f", currency, fee)
log.Infof(" - %s: %s", currency, fee.String())
}
log.Infof("PROFIT: %s", types.USD.FormatMoneyFloat64(report.Profit.Float64()))
log.Infof("UNREALIZED PROFIT: %s", types.USD.FormatMoneyFloat64(report.UnrealizedProfit.Float64()))
log.Infof("PROFIT: %s", types.USD.FormatMoney(report.Profit))
log.Infof("UNREALIZED PROFIT: %s", types.USD.FormatMoney(report.UnrealizedProfit))
}

func (report AverageCostPnlReport) SlackAttachment() slack.Attachment {
var color = slackstyle.Red

if report.UnrealizedProfit > 0 {
if report.UnrealizedProfit.Sign() > 0 {
color = slackstyle.Green
}

Expand All @@ -70,12 +70,12 @@ 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), Short: true},
{Title: "Average Cost", Value: report.Market.FormatPrice(report.AverageCost), Short: true},
{Title: "Current Price", Value: report.Market.FormatPrice(report.LastPrice.Float64()), Short: true},
{Title: "Average Cost", Value: report.Market.FormatPrice(report.AverageCost.Float64()), Short: true},

// FIXME:
// {Title: "Fee (USD)", Value: types.USD.FormatMoney(report.FeeInUSD), Short: true},
{Title: "Stock", Value: strconv.FormatFloat(report.Stock, 'f', 8, 64), Short: true},
{Title: "Stock", Value: report.Stock.String(), Short: true},
{Title: "Number of Trades", Value: strconv.Itoa(report.NumTrades), Short: true},
},
Footer: report.StartTime.Format(time.RFC822),
Expand Down
4 changes: 3 additions & 1 deletion pkg/bbgo/scale.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type ExponentialScale struct {
a float64
b float64
h float64
s float64
}

func (s *ExponentialScale) Solve() error {
Expand All @@ -51,6 +52,7 @@ func (s *ExponentialScale) Solve() error {
s.h = s.Domain[0]
s.a = s.Range[0]
s.b = math.Pow(s.Range[1]/s.Range[0], 1/(s.Domain[1]-s.h))
s.s = s.Domain[1] - s.h
return nil
}

Expand All @@ -73,7 +75,7 @@ func (s *ExponentialScale) Call(x float64) (y float64) {
x = s.Domain[1]
}

y = s.a * math.Pow(s.b, x-s.h)
y = s.a * math.Pow(s.Range[1]/s.Range[0], (x-s.h)/s.s)
return y
}

Expand Down
Loading

0 comments on commit e221f54

Please sign in to comment.