aboutsummaryrefslogtreecommitdiff
path: root/internal/cryptomarket
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-06 12:57:23 -0400
committerhistoria <[not public]>2026-10-06 12:57:23 -0400
commit9666676766bf21364f4fc4d0d1d336e72f2a91b4 (patch)
tree0cade34091d7602790dc38cd44053f0b74bbadf2 /internal/cryptomarket
parent4bc1daaf259e0c19c5fe4a12ec8bc7f6e5a735c1 (diff)
downloadthehouseoficarus-9666676766bf21364f4fc4d0d1d336e72f2a91b4.tar.gz
refactor: split casino.go, dedup helpers, remove audit docs
Diffstat (limited to 'internal/cryptomarket')
-rw-r--r--internal/cryptomarket/chart.go5
-rw-r--r--internal/cryptomarket/client.go44
-rw-r--r--internal/cryptomarket/client_test.go21
3 files changed, 47 insertions, 23 deletions
diff --git a/internal/cryptomarket/chart.go b/internal/cryptomarket/chart.go
index c1fef29..5ea477c 100644
--- a/internal/cryptomarket/chart.go
+++ b/internal/cryptomarket/chart.go
@@ -13,7 +13,10 @@ func Chart(candles []Candle, width int, unicode bool, paint func(string, bool) s
width = max(12, min(80, width))
label := 12
count := width - label - 2
- if count < 2 || len(candles) < 2 {
+ if count < 2 {
+ return []string{"Chart too narrow; widen wrap_width."}
+ }
+ if len(candles) < 2 {
return []string{"Not enough completed daily candles."}
}
if len(candles) > count {
diff --git a/internal/cryptomarket/client.go b/internal/cryptomarket/client.go
index 4d6d0a8..8445f4a 100644
--- a/internal/cryptomarket/client.go
+++ b/internal/cryptomarket/client.go
@@ -95,16 +95,16 @@ func (s Snapshot) Expiry(symbol string, expiry int64) (string, bool) {
}
type Client struct {
- HTTP *http.Client
- Base string
- once sync.Once
- gate chan struct{}
- cache Snapshot
- fetchedAt map[string]time.Time
- historyDay int64
- history map[string][]Candle
- retryAfter time.Time
- lastError error
+ HTTP *http.Client
+ Base string
+ once sync.Once
+ gate chan struct{}
+ cache Snapshot
+ attemptedAt map[string]time.Time
+ historyDay int64
+ history map[string][]Candle
+ retryAfter time.Time
+ lastError error
}
func NewClient() *Client {
@@ -179,27 +179,27 @@ func (c *Client) Fetch(ctx context.Context, symbols []string) (Snapshot, error)
c.retryAfter = time.Now().Add(15 * time.Second)
return result, err
}
- if c.fetchedAt == nil {
- c.fetchedAt = map[string]time.Time{}
+ if c.attemptedAt == nil {
+ c.attemptedAt = map[string]time.Time{}
}
c.cache = c.cache.merge(result)
now := time.Now()
- for sym := range result.Quotes {
- c.fetchedAt[sym] = now
+ for _, sym := range symbols {
+ c.attemptedAt[sym] = now
}
return result, nil
}
-// fresh reports whether every requested symbol has a recent quote. Each
-// symbol ages independently, so another currency's fetch cannot make a stale
-// quote look usable. No background work runs: only an explicit interaction
-// refreshes anything.
+// fresh reports whether every requested symbol has a recent fetch attempt. A
+// symbol that was attempted but produced no quote (halted/delisted, or a
+// missing/empty ticker) is still considered fresh: without this, every crypto
+// command would re-run the full request chain for a permanently-unavailable
+// pair. No background work runs: only an explicit interaction refreshes
+// anything.
func (c *Client) fresh(symbols []string) bool {
for _, sym := range symbols {
- if _, ok := c.cache.Quotes[sym]; !ok {
- return false
- }
- if at, ok := c.fetchedAt[sym]; !ok || time.Since(at) >= 15*time.Second {
+ at, ok := c.attemptedAt[sym]
+ if !ok || time.Since(at) >= 15*time.Second {
return false
}
}
diff --git a/internal/cryptomarket/client_test.go b/internal/cryptomarket/client_test.go
index 39367fc..ab77b24 100644
--- a/internal/cryptomarket/client_test.go
+++ b/internal/cryptomarket/client_test.go
@@ -214,3 +214,24 @@ func TestClientCancellationDoesNotPoisonSharedBackoff(t *testing.T) {
t.Fatalf("cancelled fetch poisoned the shared client: %v", e)
}
}
+
+func TestHaltedSymbolIsFreshAfterAttempt(t *testing.T) {
+ c, calls := mockKraken(t, "halted")
+ if s, e := c.Fetch(context.Background(), []string{"BTC"}); e != nil {
+ t.Fatal(e)
+ } else if _, ok := s.Quotes["BTC"]; ok {
+ t.Fatal("halted symbol unexpectedly quoted")
+ }
+ first := calls.Load()
+ if first != 4 {
+ t.Fatalf("first fetch calls = %d, want 4", first)
+ }
+ // A halted/delisted pair has no quote, but the attempt must be cached so
+ // the next command does not re-run the full request chain forever.
+ if _, e := c.Fetch(context.Background(), []string{"BTC"}); e != nil {
+ t.Fatal(e)
+ }
+ if calls.Load() != first {
+ t.Fatalf("halted symbol re-fetched: %d -> %d", first, calls.Load())
+ }
+}