aboutsummaryrefslogtreecommitdiff
path: root/internal/cryptomarket/money.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-05 14:22:14 -0400
committerhistoria <[not public]>2026-10-05 14:22:14 -0400
commit90ad8c0c33b519d5830c69d4128bdc984b5e9de8 (patch)
treec10bc0bebfbed6fde01a7f08cf6e2d98809b91a7 /internal/cryptomarket/money.go
parentc84a9944356f590d90d819d652bdf1349ebacd40 (diff)
downloadthehouseoficarus-90ad8c0c33b519d5830c69d4128bdc984b5e9de8.tar.gz
feat: cryptocurrency exchange added to casino
Diffstat (limited to 'internal/cryptomarket/money.go')
-rw-r--r--internal/cryptomarket/money.go72
1 files changed, 72 insertions, 0 deletions
diff --git a/internal/cryptomarket/money.go b/internal/cryptomarket/money.go
new file mode 100644
index 0000000..8154623
--- /dev/null
+++ b/internal/cryptomarket/money.go
@@ -0,0 +1,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
+}