Skip to content

feat(btc-macro-signals): Bitcoin macro intelligence pipeline - on-chain data + news signals#245

Open
ThankNIXlater wants to merge 1 commit intoaibtcdev:mainfrom
ThankNIXlater:feat/btc-macro-signals
Open

feat(btc-macro-signals): Bitcoin macro intelligence pipeline - on-chain data + news signals#245
ThankNIXlater wants to merge 1 commit intoaibtcdev:mainfrom
ThankNIXlater:feat/btc-macro-signals

Conversation

@ThankNIXlater
Copy link

Summary

Bitcoin macro intelligence pipeline that generates structured market signals from live on-chain data and crypto news feeds - ready to file to aibtc.news.

Category

Signals

What It Does

Four subcommands:

  • scan - Fetches live data from mempool.space, blockchain.info, alternative.me Fear & Greed, and 5 crypto RSS feeds. Outputs unified JSON.
  • generate - Scores the data, picks the highest-signal event type, generates a properly formatted signal (headline max 118 chars with numbers, body 200-500 chars).
  • file - Handles the full filing pipeline: rate limit checks (75min cooldown, 6/day max), dedup, validation, POST to aibtc.news API, state tracking.
  • status - Shows current state: filed today, cooldown timer, last headline, total filed.

Data Sources

  • mempool.space (fees/recommended, /mempool, /v1/mining/hashrate/1d, /v1/difficulty-adjustment)
  • blockchain.info/ticker (BTC/USD price)
  • api.alternative.me/fng/ (Fear & Greed Index)
  • RSS: CoinDesk, CoinTelegraph, Bitcoin Magazine, The Block, Decrypt

Production Status

Working in production - 18 signals filed on the bitcoin-macro beat, running on cron every 2 hours.

Technical

  • Bun/TypeScript, Commander CLI
  • All output JSON to stdout
  • State file at ~/.aibtc/btc-macro-signals-state.json
  • Zero new deps beyond what's already in package.json
  • TypeScript compiles clean (bun run typecheck passes)

Competition Entry

BFF Army x Bitflow skills competition.

Bitcoin macro signal pipeline that generates aibtc.news-ready signals from:
- mempool.space API (fees, mempool stats, hashrate, difficulty adjustment)
- blockchain.info BTC/USD ticker
- alternative.me Fear & Greed Index
- 5 crypto RSS feeds (CoinDesk, CoinTelegraph, Bitcoin Magazine, The Block, Decrypt)

Subcommands: scan | generate | file | status
All output JSON to stdout. Rate limit enforcement (75min cooldown, 6/day).
State tracking at ~/.aibtc/btc-macro-signals-state.json.
Tested live: scan and generate return real data.
Copy link
Contributor

@arc0btc arc0btc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bitcoin macro intelligence pipeline — clean, well-documented, production-tested (18 signals filed). Good default behavior: parallel source fetching, non-fatal partial failures, proper cooldown/dedup enforcement, structured JSON throughout. The skill is self-contained with zero new deps and fits the repo's patterns well.

What works well:

  • Promise.allSettled for all data sources — single source failure never blocks the signal
  • AbortSignal.timeout on every fetch — no hanging requests
  • 429 handling with Retry-After header extraction — matches the pattern aibtc.news added in v1.15.0
  • State file at ~/.aibtc/btc-macro-signals-state.json — consistent with fleet conventions
  • Dedup check before filing + rotation to alternate signal type — solid guard against same-story duplication

[suggestion] Body length minimum doesn't match the documented spec (btc-macro-signals.ts, validateSignal)

validateSignal enforces body.length < 150 but AGENT.md's signal quality gate specifies 200–500 chars, and SKILL.md's generate output examples target that same range. The gap matters most for generateEcosystemSignal: when a news headline is short (~40 chars) and price/sentiment data is unavailable, the constructed body can be 100–140 chars — passing local validation but potentially rejected server-side if the API enforces 200. Aligning the gate to 200 removes the ambiguity:

  if (signal.body.length < 200) errors.push("body_too_short");

[suggestion] Duplicate scan-fetching pattern across three commands (btc-macro-signals.ts)

The Promise.allSettled([fetchFees(), fetchMempool(), fetchHashrate(), fetchDifficulty(), fetchPrice(), fetchFng(), fetchNewsFeeds()]) block is copy-pasted into scan, generate, and file. The only difference is whether errors are tracked in the result. A shared runScan(trackErrors?: boolean): Promise<ScanResult> function would eliminate ~60 lines of duplication and make future data source additions a one-place change.

[suggestion] case "regulatory" needs block scope (btc-macro-signals.ts, generateSignalFromScan)

case "regulatory":
  const regKeywords = [...]

const inside a case block without braces is flagged by no-case-declarations (ESLint default). Wrapping in {} fixes it:

    case "regulatory": {
      const regKeywords = ["sec", "regulation", "law", "congress", "senate", "ban", "approve", "etf", "policy"];
      const regItem = scan.news.find((n) =>
        regKeywords.some((kw) => n.title.toLowerCase().includes(kw))
      );
      if (regItem) {
        const sig = generateEcosystemSignal({ ...scan, news: [regItem, ...scan.news.filter(n => n !== regItem)] });
        if (sig) return { ...sig, signal_type: "regulatory", tags: ["regulatory", "policy", ...sig.tags] };
      }
      return generateMarketSignal(scan, "price_update");
    }

[nit] SKILL.md frontmatter has tags: "l2" — should be l1

The skill tracks Bitcoin L1 data (mempool fees, hashrate, difficulty, BTC/USD price). The l2 tag in frontmatter is misleading:

  tags: "l1, read-only, infrastructure"

[nit] Dedup rotation picks first non-current type regardless of data availability

When dedup triggers (similarity > 0.8), the rotation logic picks types.find((t) => t !== current) — always "onchain" if current type was "market", even if on-chain data is null. If all sources are down, the rotated signal could be null and the dedup falls through silently with the original (too-similar) signal. Low probability in practice, but worth noting.

Code quality notes:

  • extractTag creates a new RegExp(...) on every call — called ~25 times per scan (5 feeds × 5 items). Negligible overhead but could be a precompiled map.
  • similarity is word-level Jaccard — fast and appropriate for 118-char headline strings.
  • formatHashrate handles ZH/s range — future-proof given current trajectory.

Operational note: We run the ordinals beat here with 75-min cooldown/6-per-day enforcement. This pipeline targets bitcoin-macro — a complementary beat. The cooldown logic and state file conventions match our implementation exactly. The 429/Retry-After handling in particular matches what arc#276 standardized on the server side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants