Pourquoi les données macro alimentent le Bitcoin
Bitcoin n'est pas une entreprise. Il ne paie aucun dividende, ne rapporte aucun bénéfice et n'a pas de multiples de revenus pour ancrer une valorisation. Au lieu de cela, BTC se négocie comme l'actif le plus sensible au risque du marché un pari à effet de levier sur les conditions de liquidité mondiales, la force du dollar et les attentes en termes de taux d'intérêt réels. Cela signifie que la même boîte à outils macro utilisée par les traders FX pour positionner en EUR/USD ou AUD/JPY s'applique directement à Bitcoin.
Lorsque la Fed réduit les taux, la liquidité du dollar s'élargit et les actifs à risque rallient BTC mène généralement la charge. Lorsque l'IPC surprend à la hausse, les récits de couverture de l'inflation réapparaissent. Lorsque les rendements réels s'effondrent, les narrations sur la dévaluation monétaire poussent le capital vers des alternatives d'actifs dur. Chacun de ces signaux est observable à l'avance via les points d'extrémité des indicateurs de FXMacroData, timestampés à la seconde et disponibles via une API REST propre.
Ce guide vous guide dans la construction d'un robot de trading algorithmique basé sur des signaux macro pour BTC/USDT sur Binance.
- Extrait des signaux macro en temps réel de FXMacroData (taux directeur, inflation, taux de rentabilité, or)
- Les combiner dans un score macro composite
- Planifier l'exécution autour des sorties à fort impact via le calendrier de sortie
- Envoie des ordres de marché et de limite sur Binance via le SDK Python officiel
Thèse de base
Les changements de régime macro cycles de réduction des taux, pivots d'inflation, inversions de tendance du dollar créent des vents de revanche directionnels de plusieurs semaines pour Bitcoin.
Pré-requis
Avant de commencer, assurez- vous d'avoir les choses suivantes prêtes:
- Python 3.9+ tous les extraits utilisent une syntaxe de saisie standard
- Clé de l'API FXMacroData inscrivez-vous à / souscrivez et prends ta clé du tableau de bord
- Compte Binance avec un solde USDT financé créer des clés API dans le tableau de bord Binance avec Activation des opérations au comptant et sur marge vérifié
- Paquets PythonJe suis désolé .
requestsJe suis désolé .python-binanceJe suis désolé .pandasJe suis désolé .schedule
pip install requests python-binance pandas schedule
Stockez vos clés API sous forme de variables d'environnement plutôt que de les coder:
export FXMACRO_API_KEY="YOUR_FXMACRODATA_KEY"
export BINANCE_API_KEY="YOUR_BINANCE_KEY"
export BINANCE_SECRET_KEY="YOUR_BINANCE_SECRET"
Étape 1: Récupérer les signaux macro à partir de FXMacroData
Quatre séries macro sont au cœur d' un modèle de régime BTC: Taux directeur en USDJe suis désolé . Inflation par IPC- Je ne sais pas . Taux d'inflation de rupture de rentabilité à 10 ans, et prix au comptant de l'orEnsemble, ils décrivent l'environnement de liquidité, le régime d'inflation et le sentiment de fuite vers les actifs dures.
import os
import requests
BASE_URL = "https://fxmacrodata.com/api/v1"
FXMACRO_KEY = os.environ["FXMACRO_API_KEY"]
def get_series(path: str, start: str = "2024-01-01") -> list[dict]:
"""Fetch a time-series from FXMacroData."""
resp = requests.get(
f"{BASE_URL}{path}",
params={"api_key": FXMACRO_KEY, "start": start},
timeout=10,
)
resp.raise_for_status()
return resp.json()["data"]
# Macro inputs
policy_rate = get_series("/announcements/usd/policy_rate")
cpi = get_series("/announcements/usd/inflation")
breakeven_10y = get_series("/announcements/usd/breakeven_inflation_rate")
gold = get_series("/commodities/gold")
# Each item: {"date": "2025-04-08", "val": 5.25, "announcement_datetime": "..."}
print(f"Policy rate (latest): {policy_rate[0]['val']}%")
print(f"CPI (latest): {cpi[0]['val']}%")
print(f"10Y breakeven: {breakeven_10y[0]['val']}%")
print(f"Gold spot: ${gold[0]['val']:.2f}")
Chaque point final renvoie une liste classée de la plus récente à la plus ancienne data[0] La clé pour les produits de base est val; pour les indicateurs macroéconomiques, val Il tient le titre et ... announcement_datetime porte l'horodatage de sortie de deuxième niveau utile pour la planification (couvert à l'étape 4).
Les entrées de macrosignal Régime actuel
Les valeurs illustratives basées sur les données de 2024-2025.
Étape 2: Construire un score macro composite
Au lieu de réagir à un seul indicateur, un score composite synthétise les quatre signaux en un nombre directionnel entre -1 (complètement sans risque) et +1 (completelement sans risque).
def macro_score(
policy_rate_pct: float,
cpi_pct: float,
breakeven_pct: float,
gold_usd: float,
*,
gold_baseline: float = 1900.0,
) -> float:
"""
Returns a composite macro score in [-1, +1].
Positive = risk-on / BTC bullish environment.
Negative = risk-off / BTC bearish environment.
"""
score = 0.0
# ── Policy rate regime (weight 0.35) ──────────────────────────
# Sub-4.5% = accommodative → bullish; above 5.5% = restrictive → bearish
if policy_rate_pct < 4.5:
score += 0.35
elif policy_rate_pct <= 5.5:
score += 0.35 * (5.5 - policy_rate_pct) / 1.0
else:
score -= 0.20
# ── Inflation regime (weight 0.25) ────────────────────────────
# CPI 2–4%: moderate inflation → neutral/slightly bullish
# CPI > 6%: high inflation → monetary debasement narrative → bullish
# CPI < 1.5%: deflationary risk → bearish
if cpi_pct > 6.0:
score += 0.25
elif cpi_pct >= 2.0:
score += 0.10
else:
score -= 0.15
# ── Breakeven inflation (weight 0.20) ─────────────────────────
# Rising breakevens signal inflation expectations are re-anchoring → bullish
if breakeven_pct >= 2.5:
score += 0.20
elif breakeven_pct >= 2.0:
score += 0.10
else:
score -= 0.10
# ── Gold trend (weight 0.20) ──────────────────────────────────
# Gold above baseline confirms hard-asset demand → bullish
gold_ratio = (gold_usd - gold_baseline) / gold_baseline
score += 0.20 * max(-1.0, min(1.0, gold_ratio * 5))
return round(max(-1.0, min(1.0, score)), 4)
score = macro_score(
policy_rate_pct=policy_rate[0]["val"],
cpi_pct=cpi[0]["val"],
breakeven_pct=breakeven_10y[0]["val"],
gold_usd=gold[0]["val"],
)
print(f"Composite macro score: {score:+.4f}")
# e.g. → +0.6500 (bullish regime)
Guide d'interprétation du score
| Plage de notation | Régime macro | Signal suggéré |
|---|---|---|
| +0,5 à +1,0 | Risque sur taux accommodants, inflation modérée à élevée | Accumuler / détenir des BTC longs |
| +0,1 à +0,5 | Légèrement favorable signaux mixtes, régime de transition | Position réduite, en attente de confirmation. |
| -0,1 à +0,1 | Neutre pas de signal de régime fort | Plate / hors de marché |
| -1,0 à -0,1 | Risque de déduction taux élevés, environnement déflationniste ou stagnflationnistes | Sortie / Réduction de la longue exposition |
Étape 3: Connectez-vous à Binance et obtenez le prix BTC
Avec le signal macro prêt, connectez-vous à Binance en utilisant le service officiel. python-binance Il est toujours nécessaire de rechercher le prix au comptant actuel avant de passer une commande afin d'éviter que les valeurs de référence ne soient obsolètes.
import os
from binance.client import Client
BINANCE_KEY = os.environ["BINANCE_API_KEY"]
BINANCE_SECRET = os.environ["BINANCE_SECRET_KEY"]
client = Client(BINANCE_KEY, BINANCE_SECRET)
# Verify connectivity
status = client.get_system_status()
print(f"Binance status: {status['msg']}") # → "normal"
# Latest BTC/USDT price
ticker = client.get_symbol_ticker(symbol="BTCUSDT")
btc_price = float(ticker["price"])
print(f"BTC/USDT spot: ${btc_price:,.2f}")
# Current USDT balance
account = client.get_account()
usdt_balance = next(
(float(b["free"]) for b in account["balances"] if b["asset"] == "USDT"),
0.0,
)
print(f"Available USDT: ${usdt_balance:,.2f}")
Le commerce du papier d'abord
Binance fournit un environnement de testnet à testnet.binance.visionUtilisez Client(key, secret, testnet=True) Valider la logique du signal et le dimensionnement pendant au moins deux semaines avant de passer à la vie.
Étape 4: Planifier autour des événements de sortie de macro
L'une des fonctionnalités les plus puissantes de FXMacroData pour le trading algorithmique est la point final du calendrier de sortieAu lieu de polling indicateurs sur un chronomètre fixe, vous pouvez interroger l'heure de sortie prévue exacte pour tout indicateur et le feu de votre logique précisément lorsque de nouvelles données atterrissent.
import datetime
import schedule
import time
def get_next_release(currency: str, indicator: str) -> datetime.datetime | None:
"""
Returns the next scheduled release datetime for an indicator.
The calendar endpoint returns upcoming release dates ordered ascending.
"""
resp = requests.get(
f"{BASE_URL}/calendar/{currency}",
params={"api_key": FXMACRO_KEY},
timeout=10,
)
resp.raise_for_status()
events = resp.json().get("data", [])
now_utc = datetime.datetime.now(tz=datetime.timezone.utc)
for event in events:
if event.get("indicator") != indicator:
continue
release_str = event.get("release_datetime") or event.get("date")
if not release_str:
continue
release_dt = datetime.datetime.fromisoformat(release_str.replace("Z", "+00:00"))
if release_dt > now_utc:
return release_dt
return None
# Find when the next FOMC policy rate decision is scheduled
next_fomc = get_next_release("usd", "policy_rate")
if next_fomc:
delta = next_fomc - datetime.datetime.now(tz=datetime.timezone.utc)
print(f"Next FOMC release: {next_fomc.isoformat()} ({delta.days}d {delta.seconds // 3600}h away)")
else:
print("No upcoming policy_rate event found in calendar.")
# Find when the next CPI release is scheduled
next_cpi = get_next_release("usd", "inflation")
if next_cpi:
print(f"Next CPI release: {next_cpi.isoformat()}")
Armé de l'horodatage exact de la sortie, vous pouvez planifier une mise à jour du signal post-sortie permettant au marché d'absorber l'impression pendant quelques minutes avant de réévaluer et de rééchanger:
def on_macro_release():
"""Called shortly after a scheduled macro release."""
print("Macro release fired — refreshing signals...")
run_strategy()
def schedule_next_release(currency: str, indicator: str, delay_seconds: int = 90):
"""
Schedules the strategy to run 'delay_seconds' after the next release.
A 90-second delay lets the initial market reaction settle slightly.
"""
release_dt = get_next_release(currency, indicator)
if not release_dt:
return
fire_at = release_dt + datetime.timedelta(seconds=delay_seconds)
fire_str = fire_at.strftime("%H:%M:%S") # schedule library uses HH:MM:SS
schedule.every().day.at(fire_str).do(on_macro_release).tag(f"{currency}_{indicator}")
print(f"Scheduled signal refresh at {fire_str} UTC after {currency.upper()} {indicator}")
schedule_next_release("usd", "policy_rate", delay_seconds=90)
schedule_next_release("usd", "inflation", delay_seconds=60)
Étape 5: Taille des positions et soumission des commandes
La taille de position est l'endroit où la plupart des stratégies algorithmiques perdent de l'argent pas dans la logique du signal. La fonction ci-dessous mesure le commerce BTC comme une fraction du capital disponible, en s'échelonnant avec la grandeur absolue du score macro. Un environnement macro à plus forte conviction (score plus loin de zéro) justifie une allocation plus grande, mais jamais plus qu'un maximum configurable.
from binance.enums import SIDE_BUY, SIDE_SELL, ORDER_TYPE_MARKET, ORDER_TYPE_LIMIT
from binance.exceptions import BinanceAPIException
import math
def compute_quantity(
score: float,
usdt_balance: float,
btc_price: float,
max_position_pct: float = 0.20,
) -> float:
"""
Scale position size between 0 and max_position_pct of USDT balance.
Only trade when |score| > 0.30 to avoid noise-driven entries.
Returns BTC quantity rounded to Binance's minimum lot size (0.00001 BTC).
"""
if abs(score) < 0.30:
return 0.0
conviction = (abs(score) - 0.30) / 0.70 # 0.0 → 1.0
usdt_to_trade = usdt_balance * max_position_pct * conviction
btc_qty = usdt_to_trade / btc_price
return math.floor(btc_qty * 100_000) / 100_000 # 5 decimal places
def place_order(side: str, quantity: float, btc_price: float) -> dict | None:
"""
Submit a market order. For limit orders, pass a price to get_order_book
and sit 0.1% inside the spread.
"""
if quantity <= 0.0:
print("Quantity zero — no order placed.")
return None
try:
order = client.order_market(
symbol="BTCUSDT",
side=side,
quantity=quantity,
)
print(f"Order placed: {side} {quantity:.5f} BTC @ ~${btc_price:,.2f}")
return order
except BinanceAPIException as exc:
print(f"Binance order error: {exc.message}")
return None
Étape 6: assembler la boucle de stratégie complète
Maintenant , combinez chaque pièce en une seule . run_strategy() fonction qui récupère de nouvelles données macro, calcule le score, vérifie un changement de régime et ouvre ou ferme une position BTC en conséquence.
import json
import pathlib
STATE_FILE = pathlib.Path("strategy_state.json")
def load_state() -> dict:
if STATE_FILE.exists():
return json.loads(STATE_FILE.read_text())
return {"position": 0.0, "last_score": 0.0}
def save_state(state: dict) -> None:
STATE_FILE.write_text(json.dumps(state, indent=2))
def run_strategy() -> None:
state = load_state()
# ── 1. Fetch fresh macro data ──────────────────────────────────
policy_rate_val = get_series("/announcements/usd/policy_rate")[0]["val"]
cpi_val = get_series("/announcements/usd/inflation")[0]["val"]
breakeven_val = get_series("/announcements/usd/breakeven_inflation_rate")[0]["val"]
gold_val = get_series("/commodities/gold")[0]["val"]
# ── 2. Compute macro score ─────────────────────────────────────
score = macro_score(policy_rate_val, cpi_val, breakeven_val, gold_val)
print(f"Macro score: {score:+.4f} (prev: {state['last_score']:+.4f})")
# ── 3. Fetch current Binance position and price ───────────────
account = client.get_account()
btc_held = float(next(b["free"] for b in account["balances"] if b["asset"] == "BTC"))
usdt_held = float(next(b["free"] for b in account["balances"] if b["asset"] == "USDT"))
btc_price = float(client.get_symbol_ticker(symbol="BTCUSDT")["price"])
# ── 4. Regime change logic ─────────────────────────────────────
prev_score = state["last_score"]
was_long = prev_score >= 0.30
is_long_now = score >= 0.30
was_flat = abs(prev_score) < 0.30
is_flat_now = abs(score) < 0.30
if is_long_now and (was_flat or prev_score < 0):
# Enter or increase long
qty = compute_quantity(score, usdt_held, btc_price)
place_order(SIDE_BUY, qty, btc_price)
elif is_flat_now and was_long and btc_held > 0.0001:
# Exit long — macro regime has turned neutral
exit_qty = math.floor(btc_held * 100_000) / 100_000
place_order(SIDE_SELL, exit_qty, btc_price)
elif score < -0.30 and btc_held > 0.0001:
# Hard exit on bearish signal
exit_qty = math.floor(btc_held * 100_000) / 100_000
place_order(SIDE_SELL, exit_qty, btc_price)
print("BEARISH regime — full exit.")
# ── 5. Persist state ───────────────────────────────────────────
state["last_score"] = score
save_state(state)
# ── Run once immediately, then on each scheduled release ──────────────
run_strategy()
# Keep scheduler alive
while True:
schedule.run_pending()
time.sleep(10)
Le taux de change de la banque centrale est calculé en fonction de la valeur de la transaction.
Le score macro a dépassé +0,3 au début de 2024, coïncidant avec le début du passage de BTC de ~ 40k $ à 70k $. Le score a reculé à mesure que les attentes de réduction des taux ont été évaluées et que le récit des actifs dures s'est refroidi.
Étape 7: Gestion des risques et considérations opérationnelles
Une stratégie de signaux macro a une fréquence de trading beaucoup plus faible qu'un système purement technique les entrées et les sorties se produisent généralement autour de 68 sorties d'indicateurs majeurs par an. Cette faible fréquence est une caractéristique, pas un bug: vous vous positionnez pour des changements de régime multi-semaines, pas de bruit intraday. Cependant, cela signifie également que chaque position comporte plus de risque par transaction, ce qui rend la gestion du risque disciplinée non négociable.
✓ Faire
- Les opérations de change sont effectuées sur la base d'un taux de change de 0,5% à 0,5% par transaction.
- Limiter la position maximale à 20% du compte par transaction
- Tests antérieurs d'au moins 2 cycles de taux de la Fed avant la mise en service
- Enregistrez chaque transaction et score dans un fichier pour vérification
- Utilisez le testnet Binance pour les tests à sec d'abord
Évitez
- Poursuite des mouvements intradiens de BTC avec des signaux macro
- Entrée immédiate à la sortie des données (attente 6090 secondes)
- Suradaptation des poids de score à un seul cycle
- Fonctionnement sans interrupteur de décharge quotidienne
- Codes d'API à codage dur dans les fichiers source
Une considération supplémentaire: les transactions BTC sont effectuées 24 heures sur 24, 7 jours sur 7, mais les événements macro sont programmés. Les communiqués du FOMC, de l'IPC et du NFP ont tous lieu pendant les heures de marché américaines. Votre planificateur doit être conscient du fuseau horaire utiliser UTC tout au long et ne convertir que lors de l 'affichage aux utilisateurs finaux. announcement_datetime Le champ est toujours UTC, ce qui rend cela simple.
Élargissement de la stratégie
Ce cadre est délibérément modulaire. Voici quelques extensions naturelles à explorer ensuite:
- Ajouter des données de positionnement COT Le point de fin CFTC COT de FXMacroData fournit un positionnement spéculatif hebdomadaire sur les contrats à terme en USD.
/cot/usdet ajouter un terme de positionnement net au score macro. - Score multi-monnaies incorporer les signaux macro de l'EUR, du JPY et de la GBP pour construire un score de liquidité mondial.
- Vélèbre de tendance de rupture de rentabilité au lieu du niveau de rentabilité, utiliser le taux de variation de 4 semaines.
- Intégration du calendrier de sortie demande à calendrier de sortie pour tous les événements USD d'un mois, les regrouper par poids d'impact et identifier les fenêtres où plusieurs émissions à fort impact se regroupent dans les 48 heures ce sont les périodes pour lesquelles il convient de se positionner.
Résumé
Vous avez maintenant un robot de trading Bitcoin fonctionnel axé sur des signaux macro qui relie les données de l'indicateur FXMacroData directement à l'exécution de Binance.
Le prochain article de cette série couvre le backtesting de ce cadre par rapport aux données historiques et l'étalonnage des pondérations de score en utilisant des séries temporelles réelles FXMacroData remontant à 2015.