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