1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
// Package cryptomarket implements a fictional-credit exchange, not real orders.
package cryptomarket
import (
"fmt"
"math"
"math/big"
"regexp"
"strconv"
)
const MaxMoney int64 = 1_000_000_000_000
const MaxPositions = 50
// Decimal strings survive YAML/JSON without a float conversion. In particular,
// neither SHIB prices nor fractional BTC quantities are rounded to credits.
var decimalPattern = regexp.MustCompile(`^(0|[1-9][0-9]{0,14})(\.[0-9]{1,18})?$`)
func Decimal(s string) (*big.Rat, error) {
if !decimalPattern.MatchString(s) {
return nil, fmt.Errorf("use a positive decimal (no exponent), at most 15 integer and 18 fractional digits")
}
r, ok := new(big.Rat).SetString(s)
if !ok || r.Sign() <= 0 {
return nil, fmt.Errorf("amount must be positive")
}
return r, nil
}
func rat(s string) *big.Rat { r, _ := new(big.Rat).SetString(s); return r }
func mul(a, b *big.Rat) *big.Rat { return new(big.Rat).Mul(a, b) }
func add(a, b *big.Rat) *big.Rat { return new(big.Rat).Add(a, b) }
func sub(a, b *big.Rat) *big.Rat { return new(big.Rat).Sub(a, b) }
func integer(n int64) *big.Rat { return new(big.Rat).SetInt64(n) }
// Round rounds the complete cash amount once, nearest credit, ties away from
// zero. Never round per coin, and never silently clamp an overflow.
func Round(r *big.Rat) (int64, error) {
if r == nil {
return 0, fmt.Errorf("invalid amount")
}
a := new(big.Rat).Abs(r)
n, rem := new(big.Int), new(big.Int)
n.QuoRem(a.Num(), a.Denom(), rem)
if rem.Lsh(rem, 1).Cmp(a.Denom()) >= 0 {
n.Add(n, big.NewInt(1))
}
if !n.IsInt64() || n.Int64() > MaxMoney {
return 0, fmt.Errorf("amount exceeds exchange accounting limit")
}
v := n.Int64()
if r.Sign() < 0 {
v = -v
}
return v, nil
}
func Sum(a, b int64) (int64, error) {
if a < -MaxMoney || a > MaxMoney || b < -MaxMoney || b > MaxMoney || a+b < -MaxMoney || a+b > MaxMoney {
return 0, fmt.Errorf("exchange balance limit exceeded")
}
return a + b, nil
}
func floatRat(f float64) (*big.Rat, error) {
if math.IsNaN(f) || math.IsInf(f, 0) || f < 0 {
return nil, fmt.Errorf("invalid model value")
}
r, ok := new(big.Rat).SetString(strconv.FormatFloat(f, 'f', 18, 64))
if !ok {
return nil, fmt.Errorf("invalid model value")
}
return r, nil
}
|