Skip to content

Commit

Permalink
Merge branch 'ws_outbound_req' into ws_oubtound_req_futures
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryan O'Hara-Reid committed Dec 9, 2024
2 parents fae9556 + 9c56cbb commit 8f0fd7f
Show file tree
Hide file tree
Showing 99 changed files with 293,120 additions and 183,404 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/proto-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
- uses: bufbuild/buf-setup-action@v1.46.0
- uses: bufbuild/buf-setup-action@v1.47.2

- name: buf generate
working-directory: ./gctrpc
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ jobs:
GOARCH: ${{ matrix.goarch }}

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

Expand Down
42 changes: 26 additions & 16 deletions common/math/math.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

// 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
}

// CalculatePercentageDifference returns the percentage of difference between
// multiple time periods
func CalculatePercentageDifference(amount, secondAmount float64) float64 {
return (amount - secondAmount) / ((amount + secondAmount) / 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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand All @@ -370,15 +380,15 @@ 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
return decimal.Zero, errGeometricNegative
}
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
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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() {
Expand Down
56 changes: 40 additions & 16 deletions common/math/math_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
}
}

Expand Down
4 changes: 2 additions & 2 deletions config_example.json

Large diffs are not rendered by default.

49 changes: 16 additions & 33 deletions currency/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,7 @@ func (b *BaseCodes) Register(c string, newRole Role) Code {
return EMPTYCODE
}

var format bool
// Digits fool upper and lower casing. So find first letter and check case.
for x := range c {
if unicode.IsLetter(rune(c[x])) {
format = unicode.IsUpper(rune(c[x]))
break
}
}
isUpperCase := strings.ContainsFunc(c, func(r rune) bool { return unicode.IsLetter(r) && unicode.IsUpper(r) })

// Force upper string storage and matching
c = strings.ToUpper(c)
Expand All @@ -218,13 +211,13 @@ func (b *BaseCodes) Register(c string, newRole Role) Code {
}
stored[x].Role = newRole
}
return Code{Item: stored[x], UpperCase: format}
return Code{Item: stored[x], upperCase: isUpperCase}
}
}

newItem := &Item{Symbol: c, Lower: strings.ToLower(c), Role: newRole}
b.Items[c] = append(b.Items[c], newItem)
return Code{Item: newItem, UpperCase: format}
return Code{Item: newItem, upperCase: isUpperCase}
}

// LoadItem sets item data
Expand Down Expand Up @@ -275,21 +268,29 @@ func (c Code) String() string {
if c.Item == nil {
return ""
}
if c.UpperCase {
if c.upperCase {
return c.Item.Symbol
}
return c.Item.Lower
}

// Lower converts the code to lowercase formatting
// Lower flags the Code to use LowerCase formatting, but does not change Symbol
// If Code cannot be lowercased then it will return Code unchanged
func (c Code) Lower() Code {
c.UpperCase = false
if c.Item == nil {
return c
}
c.upperCase = false
return c
}

// Upper converts the code to uppercase formatting
// Upper flags the Code to use UpperCase formatting, but does not change Symbol
// If Code cannot be uppercased then it will return Code unchanged
func (c Code) Upper() Code {
c.UpperCase = true
if c.Item == nil {
return c
}
c.upperCase = true
return c
}

Expand Down Expand Up @@ -346,21 +347,3 @@ func (i *Item) Currency() Code {
}
return NewCode(i.Symbol)
}

// UpperCurrency allows an item to revert to a code
// taking an upper
func (i *Item) UpperCurrency() Code {
if i == nil {
return EMPTYCODE.Upper()
}
return NewCode(i.Symbol).Upper()
}

// LowerCurrency allows an item to revert to a code
// returning in lower format
func (i *Item) LowerCurrency() Code {
if i == nil {
return EMPTYCODE.Lower()
}
return NewCode(i.Symbol).Lower()
}
12 changes: 12 additions & 0 deletions currency/code_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"encoding/json"
"errors"
"testing"

"github.com/stretchr/testify/require"
)

func TestRoleString(t *testing.T) {
Expand Down Expand Up @@ -456,6 +458,16 @@ func TestBaseCode(t *testing.T) {
}
}

func TestNewCodeFormatting(t *testing.T) {
require.True(t, NewCode("BTC").upperCase)
require.False(t, NewCode("btc").upperCase)
require.True(t, NewCode("BTC").Equal(NewCode("btc")))
require.False(t, NewCode("420").upperCase)
require.False(t, NewCode("btc420").upperCase)
require.False(t, NewCode("420").Lower().upperCase)
require.True(t, NewCode("4BTC").upperCase)
}

func TestCodeString(t *testing.T) {
if cc, expected := NewCode("TEST"), "TEST"; cc.String() != expected {
t.Errorf("Currency Code String() error expected %s but received %s",
Expand Down
2 changes: 1 addition & 1 deletion currency/code_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type Code struct {
// TODO: Below will force the use of the Equal method for comparison. Big
// job to update all maps and instances through the code base.
// _ []struct{}
UpperCase bool
upperCase bool
}

// Item defines a sub type containing the main attributes of a designated
Expand Down
2 changes: 1 addition & 1 deletion currency/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ func (p *PairsManager) SetDelimitersFromConfig() error {
}
for i, p := range []Pairs{s.Enabled, s.Available} {
for j := range p {
if p[j].Delimiter == cf.Delimiter {
if cf.Delimiter == "" || p[j].Delimiter == cf.Delimiter {
continue
}
nP, err := NewPairDelimiter(p[j].String(), cf.Delimiter)
Expand Down
2 changes: 1 addition & 1 deletion currency/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ func TestPairManagerSetDelimitersFromConfig(t *testing.T) {
err = json.Unmarshal([]byte(`{"pairs":{"spot":{"configFormat":{"delimiter":"_"},"enabled":"BTC-USDT","available":"BTC-USDT"}}}`), p)
if assert.NoError(t, err, "UnmarshalJSON should not error") {
err := p.SetDelimitersFromConfig()
assert.ErrorContains(t, err, "spot.enabled.BTC-USDT: delimiter: [_] not found in currencypair string", "SetDelimitersFromConfig should error correctly")
assert.ErrorIs(t, err, errDelimiterNotFound, "SetDelimitersFromConfig should error correctly")
}
}

Expand Down
Loading

0 comments on commit 8f0fd7f

Please sign in to comment.