mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-17 23:16:52 +00:00
common/math: Add math.Abs to PercentageDifference calculation (#1617)
* fix bug and add decimal calc * pew pew * Update common/math/math.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits * nits: plus change name convention * gk: nits and splits --------- Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io> Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
This commit is contained in:
@@ -24,6 +24,10 @@ var (
|
||||
errCAGRNoIntervals = errors.New("cannot calculate CAGR with no intervals")
|
||||
errCAGRZeroOpenValue = errors.New("cannot calculate CAGR with an open value of 0")
|
||||
errInformationBadLength = errors.New("benchmark rates length does not match returns rates")
|
||||
|
||||
one = decimal.NewFromInt(1)
|
||||
two = decimal.NewFromInt(2)
|
||||
oneHundred = decimal.NewFromInt(100)
|
||||
)
|
||||
|
||||
// CalculateAmountWithFee returns a calculated fee included amount on fee
|
||||
@@ -36,16 +40,22 @@ func CalculateFee(amount, fee float64) float64 {
|
||||
return amount * (fee / 100)
|
||||
}
|
||||
|
||||
// CalculatePercentageGainOrLoss returns the percentage rise over a certain
|
||||
// period
|
||||
func CalculatePercentageGainOrLoss(priceNow, priceThen float64) float64 {
|
||||
return (priceNow - priceThen) / priceThen * 100
|
||||
// PercentageChange returns the percentage change between two numbers, x is reference value.
|
||||
func PercentageChange(x, y float64) float64 {
|
||||
return (y - x) / x * 100
|
||||
}
|
||||
|
||||
// CalculatePercentageDifference returns the percentage of difference between
|
||||
// multiple time periods
|
||||
func CalculatePercentageDifference(amount, secondAmount float64) float64 {
|
||||
return (amount - secondAmount) / ((amount + secondAmount) / 2) * 100
|
||||
// PercentageDifference returns difference between two numbers as a percentage of their average
|
||||
func PercentageDifference(x, y float64) float64 {
|
||||
return math.Abs(x-y) / ((x + y) / 2) * 100
|
||||
}
|
||||
|
||||
// PercentageDifferenceDecimal returns the difference between two decimal values as a percentage of their average
|
||||
func PercentageDifferenceDecimal(x, y decimal.Decimal) decimal.Decimal {
|
||||
if x.IsZero() && y.IsZero() {
|
||||
return decimal.Zero
|
||||
}
|
||||
return x.Sub(y).Abs().Div(x.Add(y).Div(two)).Mul(oneHundred)
|
||||
}
|
||||
|
||||
// CalculateNetProfit returns net profit
|
||||
@@ -267,7 +277,7 @@ func DecimalCompoundAnnualGrowthRate(openValue, closeValue, intervalsPerYear, nu
|
||||
if pow.IsZero() {
|
||||
return decimal.Zero, ErrPowerDifferenceTooSmall
|
||||
}
|
||||
k := pow.Sub(decimal.NewFromInt(1)).Mul(decimal.NewFromInt(100))
|
||||
k := pow.Sub(one).Mul(oneHundred)
|
||||
return k, nil
|
||||
}
|
||||
|
||||
@@ -317,7 +327,7 @@ func DecimalPopulationStandardDeviation(values []decimal.Decimal) (decimal.Decim
|
||||
diffs := make([]decimal.Decimal, len(values))
|
||||
for x := range values {
|
||||
val := values[x].Sub(valAvg)
|
||||
exp := decimal.NewFromInt(2)
|
||||
exp := two
|
||||
pow := DecimalPow(val, exp)
|
||||
diffs[x] = pow
|
||||
}
|
||||
@@ -349,11 +359,11 @@ func DecimalSampleStandardDeviation(values []decimal.Decimal) (decimal.Decimal,
|
||||
superMean := make([]decimal.Decimal, len(values))
|
||||
var combined decimal.Decimal
|
||||
for i := range values {
|
||||
pow := values[i].Sub(mean).Pow(decimal.NewFromInt(2))
|
||||
pow := values[i].Sub(mean).Pow(two)
|
||||
superMean[i] = pow
|
||||
combined.Add(pow)
|
||||
}
|
||||
avg := combined.Div(decimal.NewFromInt(int64(len(superMean))).Sub(decimal.NewFromInt(1)))
|
||||
avg := combined.Div(decimal.NewFromInt(int64(len(superMean))).Sub(one))
|
||||
f, exact := avg.Float64()
|
||||
err = nil
|
||||
if !exact {
|
||||
@@ -370,7 +380,7 @@ func DecimalGeometricMean(values []decimal.Decimal) (decimal.Decimal, error) {
|
||||
if len(values) == 0 {
|
||||
return decimal.Zero, errZeroValue
|
||||
}
|
||||
product := decimal.NewFromInt(1)
|
||||
product := one
|
||||
for i := range values {
|
||||
if values[i].LessThanOrEqual(decimal.Zero) {
|
||||
// cannot use negative or zero values in geometric calculation
|
||||
@@ -378,7 +388,7 @@ func DecimalGeometricMean(values []decimal.Decimal) (decimal.Decimal, error) {
|
||||
}
|
||||
product = product.Mul(values[i])
|
||||
}
|
||||
exp := decimal.NewFromInt(1).Div(decimal.NewFromInt(int64(len(values))))
|
||||
exp := one.Div(decimal.NewFromInt(int64(len(values))))
|
||||
pow := DecimalPow(product, exp)
|
||||
geometricPower := pow
|
||||
return geometricPower, nil
|
||||
@@ -413,7 +423,7 @@ func DecimalFinancialGeometricMean(values []decimal.Decimal) (decimal.Decimal, e
|
||||
// as we cannot have negative or zero value geometric numbers
|
||||
// adding a 1 to the percentage movements allows for differentiation between
|
||||
// negative numbers (eg -0.1 translates to 0.9) and positive numbers (eg 0.1 becomes 1.1)
|
||||
modVal := values[i].Add(decimal.NewFromInt(1)).InexactFloat64()
|
||||
modVal := values[i].Add(one).InexactFloat64()
|
||||
product *= modVal
|
||||
}
|
||||
prod := 1 / float64(len(values))
|
||||
@@ -446,7 +456,7 @@ func DecimalSortinoRatio(movementPerCandle []decimal.Decimal, riskFreeRatePerInt
|
||||
totalNegativeResultsSquared := decimal.Zero
|
||||
for x := range movementPerCandle {
|
||||
if movementPerCandle[x].Sub(riskFreeRatePerInterval).LessThan(decimal.Zero) {
|
||||
totalNegativeResultsSquared = totalNegativeResultsSquared.Add(movementPerCandle[x].Sub(riskFreeRatePerInterval).Pow(decimal.NewFromInt(2)))
|
||||
totalNegativeResultsSquared = totalNegativeResultsSquared.Add(movementPerCandle[x].Sub(riskFreeRatePerInterval).Pow(two))
|
||||
}
|
||||
}
|
||||
if totalNegativeResultsSquared.IsZero() {
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCalculateFee(t *testing.T) {
|
||||
@@ -28,27 +30,49 @@ func TestCalculateAmountWithFee(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculatePercentageGainOrLoss(t *testing.T) {
|
||||
func TestPercentageChange(t *testing.T) {
|
||||
t.Parallel()
|
||||
originalInput := float64(9300)
|
||||
secondInput := float64(9000)
|
||||
expectedOutput := 3.3333333333333335
|
||||
actualResult := CalculatePercentageGainOrLoss(originalInput, secondInput)
|
||||
if expectedOutput != actualResult {
|
||||
t.Errorf(
|
||||
"Expected '%v'. Actual '%v'.", expectedOutput, actualResult)
|
||||
assert.Equal(t, 3.3333333333333335, PercentageChange(9000, 9300))
|
||||
assert.Equal(t, -3.225806451612903, PercentageChange(9300, 9000))
|
||||
assert.True(t, math.IsNaN(PercentageChange(0, 0)))
|
||||
assert.Equal(t, 0.0, PercentageChange(1, 1))
|
||||
assert.Equal(t, 0.0, PercentageChange(-1, -1))
|
||||
assert.True(t, math.IsInf(PercentageChange(0, 1), 1))
|
||||
assert.Equal(t, -100., PercentageChange(1, 0))
|
||||
}
|
||||
|
||||
func TestPercentageDifference(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Equal(t, 196.03960396039605, PercentageDifference(1, 100))
|
||||
require.Equal(t, 196.03960396039605, PercentageDifference(100, 1))
|
||||
require.Equal(t, 0.13605442176870758, PercentageDifference(1.469, 1.471))
|
||||
require.Equal(t, 0.13605442176870758, PercentageDifference(1.471, 1.469))
|
||||
require.Equal(t, 0.0, PercentageDifference(1.0, 1.0))
|
||||
require.True(t, math.IsNaN(PercentageDifference(0.0, 0.0)))
|
||||
}
|
||||
|
||||
// 1000000000 0.2215 ns/op 0 B/op 0 allocs/op
|
||||
func BenchmarkPercentageDifference(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
PercentageDifference(1.469, 1.471)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculatePercentageDifference(t *testing.T) {
|
||||
func TestPercentageDifferenceDecimal(t *testing.T) {
|
||||
t.Parallel()
|
||||
originalInput := float64(10)
|
||||
secondAmount := float64(5)
|
||||
expectedOutput := 66.66666666666666
|
||||
actualResult := CalculatePercentageDifference(originalInput, secondAmount)
|
||||
if expectedOutput != actualResult {
|
||||
t.Errorf(
|
||||
"Expected '%f'. Actual '%f'.", expectedOutput, actualResult)
|
||||
require.Equal(t, "196.03960396039604", PercentageDifferenceDecimal(decimal.NewFromFloat(1), decimal.NewFromFloat(100)).String())
|
||||
require.Equal(t, "196.03960396039604", PercentageDifferenceDecimal(decimal.NewFromFloat(100), decimal.NewFromFloat(1)).String())
|
||||
require.Equal(t, "0.13605442176871", PercentageDifferenceDecimal(decimal.NewFromFloat(1.469), decimal.NewFromFloat(1.471)).String())
|
||||
require.Equal(t, "0.13605442176871", PercentageDifferenceDecimal(decimal.NewFromFloat(1.471), decimal.NewFromFloat(1.469)).String())
|
||||
require.Equal(t, "0", PercentageDifferenceDecimal(decimal.NewFromFloat(1.0), decimal.NewFromFloat(1.0)).String())
|
||||
require.Equal(t, "0", PercentageDifferenceDecimal(decimal.Zero, decimal.Zero).String())
|
||||
}
|
||||
|
||||
// 1585596 751.8 ns/op 792 B/op 27 allocs/op
|
||||
func BenchmarkDecimalPercentageDifference(b *testing.B) {
|
||||
d1, d2 := decimal.NewFromFloat(1.469), decimal.NewFromFloat(1.471)
|
||||
for i := 0; i < b.N; i++ {
|
||||
PercentageDifferenceDecimal(d1, d2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1049,10 +1049,10 @@ func (m *DataHistoryManager) CheckCandleIssue(job *DataHistoryJob, multiplier in
|
||||
}
|
||||
if apiData != dbData {
|
||||
var diff float64
|
||||
if apiData > dbData {
|
||||
diff = gctmath.CalculatePercentageGainOrLoss(apiData, dbData)
|
||||
if apiData < dbData {
|
||||
diff = gctmath.PercentageChange(apiData, dbData)
|
||||
} else {
|
||||
diff = gctmath.CalculatePercentageGainOrLoss(dbData, apiData)
|
||||
diff = gctmath.PercentageChange(dbData, apiData)
|
||||
}
|
||||
if diff > job.IssueTolerancePercentage {
|
||||
issue = fmt.Sprintf("%s api: %v db: %v diff: %v %%", candleField, apiData, dbData, diff)
|
||||
|
||||
@@ -46,7 +46,7 @@ func (b *Base) WhaleBomb(priceTarget float64, buy bool) (*WhaleBombResult, error
|
||||
minPrice = action.ReferencePrice
|
||||
maxPrice = action.TranchePositionPrice
|
||||
amount = action.QuoteAmount
|
||||
percent = math.CalculatePercentageGainOrLoss(action.TranchePositionPrice, action.ReferencePrice)
|
||||
percent = math.PercentageChange(action.ReferencePrice, action.TranchePositionPrice)
|
||||
status = fmt.Sprintf("Buying using %.2f %s worth of %s will send the price from %v to %v [%.2f%%] and impact %d price tranche(s). %s",
|
||||
amount, b.Pair.Quote, b.Pair.Base, minPrice, maxPrice,
|
||||
percent, len(action.Tranches), warning)
|
||||
@@ -54,7 +54,7 @@ func (b *Base) WhaleBomb(priceTarget float64, buy bool) (*WhaleBombResult, error
|
||||
minPrice = action.TranchePositionPrice
|
||||
maxPrice = action.ReferencePrice
|
||||
amount = action.BaseAmount
|
||||
percent = math.CalculatePercentageGainOrLoss(action.TranchePositionPrice, action.ReferencePrice)
|
||||
percent = math.PercentageChange(action.ReferencePrice, action.TranchePositionPrice)
|
||||
status = fmt.Sprintf("Selling using %.2f %s worth of %s will send the price from %v to %v [%.2f%%] and impact %d price tranche(s). %s",
|
||||
amount, b.Pair.Base, b.Pair.Quote, maxPrice, minPrice,
|
||||
percent, len(action.Tranches), warning)
|
||||
@@ -108,7 +108,7 @@ func (b *Base) SimulateOrder(amount float64, buy bool) (*WhaleBombResult, error)
|
||||
warning = fullLiquidityUsageWarning
|
||||
}
|
||||
|
||||
pct := math.CalculatePercentageGainOrLoss(action.TranchePositionPrice, action.ReferencePrice)
|
||||
pct := math.PercentageChange(action.ReferencePrice, action.TranchePositionPrice)
|
||||
status := fmt.Sprintf("%s using %f %v worth of %v will send the price from %v to %v [%.2f%%] and impact %v price tranche(s). %s",
|
||||
direction, soldAmount, sold, bought, action.ReferencePrice,
|
||||
action.TranchePositionPrice, pct, len(action.Tranches), warning)
|
||||
|
||||
@@ -400,7 +400,7 @@ func (bids *bidTranches) hitBidsByNominalSlippage(slippage, refPrice float64) (*
|
||||
currentTotalAmounts := cumulativeAmounts + bids.Tranches[x].Amount
|
||||
|
||||
nominal.AverageOrderCost = currentFullValue / currentTotalAmounts
|
||||
percent := math.CalculatePercentageGainOrLoss(nominal.AverageOrderCost, refPrice)
|
||||
percent := math.PercentageChange(refPrice, nominal.AverageOrderCost)
|
||||
if percent != 0 {
|
||||
percent *= -1
|
||||
}
|
||||
@@ -461,7 +461,7 @@ func (bids *bidTranches) hitBidsByImpactSlippage(slippage, refPrice float64) (*M
|
||||
|
||||
impact := &Movement{StartPrice: refPrice, EndPrice: refPrice}
|
||||
for x := range bids.Tranches {
|
||||
percent := math.CalculatePercentageGainOrLoss(bids.Tranches[x].Price, refPrice)
|
||||
percent := math.PercentageChange(refPrice, bids.Tranches[x].Price)
|
||||
if percent != 0 {
|
||||
percent *= -1
|
||||
}
|
||||
@@ -529,7 +529,7 @@ func (ask *askTranches) liftAsksByNominalSlippage(slippage, refPrice float64) (*
|
||||
currentAmounts := cumulativeAmounts + ask.Tranches[x].Amount
|
||||
|
||||
nominal.AverageOrderCost = currentValue / currentAmounts
|
||||
percent := math.CalculatePercentageGainOrLoss(nominal.AverageOrderCost, refPrice)
|
||||
percent := math.PercentageChange(refPrice, nominal.AverageOrderCost)
|
||||
|
||||
if slippage < percent {
|
||||
targetCost := (1 + slippage/100) * refPrice
|
||||
@@ -582,7 +582,7 @@ func (ask *askTranches) liftAsksByImpactSlippage(slippage, refPrice float64) (*M
|
||||
|
||||
impact := &Movement{StartPrice: refPrice, EndPrice: refPrice}
|
||||
for x := range ask.Tranches {
|
||||
percent := math.CalculatePercentageGainOrLoss(ask.Tranches[x].Price, refPrice)
|
||||
percent := math.PercentageChange(refPrice, ask.Tranches[x].Price)
|
||||
impact.ImpactPercentage = percent
|
||||
impact.EndPrice = ask.Tranches[x].Price
|
||||
if slippage <= percent {
|
||||
@@ -625,7 +625,7 @@ func (m *Movement) finalizeFields(cost, amount, headPrice, leftover float64, swa
|
||||
|
||||
// Nominal percentage is the difference from the reference price to average
|
||||
// order cost.
|
||||
m.NominalPercentage = math.CalculatePercentageGainOrLoss(m.AverageOrderCost, m.StartPrice)
|
||||
m.NominalPercentage = math.PercentageChange(m.StartPrice, m.AverageOrderCost)
|
||||
if m.NominalPercentage < 0 {
|
||||
m.NominalPercentage *= -1
|
||||
}
|
||||
@@ -634,7 +634,7 @@ func (m *Movement) finalizeFields(cost, amount, headPrice, leftover float64, swa
|
||||
// Impact percentage is how much the orderbook slips from the reference
|
||||
// price to the remaining tranche price.
|
||||
|
||||
m.ImpactPercentage = math.CalculatePercentageGainOrLoss(m.EndPrice, m.StartPrice)
|
||||
m.ImpactPercentage = math.PercentageChange(m.StartPrice, m.EndPrice)
|
||||
if m.ImpactPercentage < 0 {
|
||||
m.ImpactPercentage *= -1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user