高频交易(HFT), 알고리즘 트레이딩, 시장 microstructure 분석을 위해 Binance 호가창 이력 데이터가 필요하신 적이 있으신가요? 이 튜토리얼에서는 Tardis.dev를 활용하여 Binance의 미세한 호가창 변화를 Python으로 수집하고 리플레이하는 방법부터 실제 거래 전략 검증까지循序渐进하게 설명드리겠습니다.
저는 약 3개월간 암호화폐 시장 데이터 파이프라인을 구축하며 Tardis.dev API를 실무에 적용한 경험이 있습니다. 이 과정에서 마주친 여러 난관을 함께 해결해 드릴게요.
이 튜토리얼이 다루는 내용
- Binance 호가창(orderbook) 데이터 구조 이해
- Tardis.dev API 설정 및 Python 연동
- Binance USDT-M 선물(Futures) 호가창 이력 데이터 가져오기
- Python에서 실시간/배치 데이터 리플레이 구현
- 실제 거래 전략 검증 사례
Tardis.dev란?
Tardis.dev는 Binance, Bybit, OKX, Deribit 등 주요 암호화폐 거래소의 이력 market data를 제공하는 전문 데이터 플랫폼입니다. 특히 逐笔(order-by-order) 데이터를 지원하여 밀리초 단위의 정확한 호가창 변화를 분석할 수 있습니다.
| 데이터 유형 | 기본 제공 | Tardis.dev 추가 제공 |
|---|---|---|
| 분봉/시간봉(OHLCV) | ✓ Binance 자체 제공 | ✓ 동일 |
| 트레이드 체결 데이터 | 제한적(500개) | ✓ 무제한 이력 |
| 호가창(Orderbook) 변화 | ✗ 미지원 | ✓ 완전한 이력 제공 |
| 실시간 WebSocket | ✓ 자체 지원 | ✓ 다중 거래소 통합 |
이런 팀에 적합 / 비적합
✓ 이 튜토리얼이 적합한 팀
- 암호화폐 핀텍 스타트업: 자체 거래 전략 백테스팅이 필요한 팀
- 퀀트 트레이딩 연구자: 시장 미세 구조(microstructure) 분석 연구자
- 데이터 사이언스팀: ML 모델 학습용 고품질 시계열 데이터가 필요한 경우
- 거래소 리스크 관리팀: 유동성 분석 및 시장 영향 평가
✗ 이 튜토리얼이 불필요한 경우
- 기본 캔들스틱 차트 데이터만 필요한 경우 (Binance 공식 API로 충분)
- 저주파 트레이딩 중심인 경우 (분봉 데이터로도 가능)
- 단순 자동매매 봇만 필요한 경우 (Real-time WebSocket API 사용 권장)
1단계: Tardis.dev API 설정
가장 먼저 Tardis.dev에 가입하여 API 키를 발급받아야 합니다. 아래 단계를 따라 진행해주세요.
1.1 계정 생성 및 API 키 발급
- Tardis.dev 웹사이트(tardis.dev) 방문
- "Sign Up" 버튼 클릭 후 이메일 등록
- 이메일 인증 완료
- Dashboard → "API Tokens" → "Create new token" 클릭
- 토큰 이름 입력 후 권한 선택 (Read/Write)
💡 스크린샷 힌트: Dashboard 우측 상단의 프로필 아이콘 → "API Tokens" 메뉴 위치
1.2 Python 환경 설정
Python 3.8 이상 환경에서 다음 패키지를 설치합니다:
# 필수 패키지 설치
pip install tardis-client asyncio aiohttp pandas numpy
특정 버전 고정 (안정성 보장)
pip install tardis-client==1.8.0 aiohttp==3.9.1 pandas==2.1.4
2단계: Binance 호가창(Orderbook) 데이터 구조 이해
Binance Futures USDT-M의 호가창 데이터는 다음과 같은 구조로 구성됩니다:
2.1 호가창 기초 개념
# 호가창 데이터 예시 구조
orderbook_snapshot = {
"timestamp": "2024-03-15T10:30:00.123456Z", # UTC 타임스탬프
"exchange": "binance",
"symbol": "BTCUSDT",
"type": "snapshot", # 또는 "delta" (변화분)
"bids": [ # 매수 호가 (가격, 수량)
["50123.50", "1.234"], # 가격, 수량
["50123.00", "2.567"],
["50122.50", "0.890"],
],
"asks": [ # 매도 호가
["50124.00", "1.456"],
["50124.50", "2.123"],
["50125.00", "3.789"],
]
}
- Bid (매수호가): 트레이더가 사고자 하는 가격. 낮을수록 즉시 체결 가능
- Ask (매도호가): 트레이더가 팔고자 하는 가격. 높을수록 즉시 체결 가능
- Spread: Bid와 Ask의 차이.流动성 지표
- Depth: 특정 가격 범위의 누적 수량
2.2 Binance Futures API 엔드포인트 확인
Tardis.dev에서 지원하는 Binance 데이터:
- Exchange ID:
binance-futures - Symbol:
BTCUSDT,ETHUSDT등 - Data Type:
orderbook_snapshot,orderbook_deltas
3단계: Python으로 Binance 호가창 이력 데이터 가져오기
3.1 기본 API 연동 코드
import asyncio
from tardis_client import TardisClient, Message
import os
============================================
HolySheep AI에서 관리하는 API Gateway 연동 예시
HolySheep AI는 AI API 전문 Gateway이며,
Tardis.dev API는 별도로 가입하셔야 합니다.
============================================
⚠️ 중요: 실제 사용 시 아래 YOUR_TARDIS_API_TOKEN을 교체하세요
TARDIS_API_TOKEN = os.getenv("TARDIS_API_TOKEN", "YOUR_TARDIS_API_TOKEN")
async def fetch_orderbook_history():
"""Binance USDT-M 선물 BTC/USDT 호가창 이력 데이터 가져오기"""
client = TardisClient(api_token=TARDIS_API_TOKEN)
# 조회 시간 범위 설정 (2024-03-15)
from datetime import datetime, timezone
start_date = datetime(2024, 3, 15, 0, 0, 0, tzinfo=timezone.utc)
end_date = datetime(2024, 3, 15, 1, 0, 0, tzinfo=timezone.utc)
# Binance Futures USDT-M BTC/USDT 호가창 데이터 조회
orderbook_stream = client.replay(
exchange="binance-futures",
symbols=["BTCUSDT"],
from_date=start_date,
to_date=end_date,
filters=[Message.Type.ORDERBOOK_SNAPSHOT, Message.Type.ORDERBOOK_DELTA]
)
orderbook_count = 0
async for message in orderbook_stream:
# Orderbook 스냅샷 또는 델타 메시지 처리
if message.type in [Message.Type.ORDERBOOK_SNAPSHOT, Message.Type.ORDERBOOK_DELTA]:
orderbook_count += 1
# 첫 3개 메시지만 출력 (테스트용)
if orderbook_count <= 3:
print(f"[{message.local_timestamp}]")
print(f" Type: {message.type}")
print(f" Symbol: {message.symbol}")
print(f" Bids: {len(message.data.get('bids', []))}개")
print(f" Asks: {len(message.data.get('asks', []))}개")
print()
# 전체 데이터 처리를 원하면 여기서 로직 구현
# 예: Spread 계산, Arbitrage detection 등
print(f"총 {orderbook_count}개의 호가창 메시지 처리 완료")
메인 실행
if __name__ == "__main__":
asyncio.run(fetch_orderbook_history())
💡 실행 결과 힌트: 1시간치 데이터는 수만~수십만 개의 메시지가 생성됩니다. 콘솔에 출력량이 많으므로 적절히 필터링하세요.
3.2 특정 거래대상만 필터링하기
async def fetch_specific_symbols():
"""여러 거래대상 동시에 조회 (BTC, ETH, SOL)"""
client = TardisClient(api_token=TARDIS_API_TOKEN)
# 조회 시간 범위
from datetime import datetime, timezone
start_date = datetime(2024, 3, 15, 9, 0, 0, tzinfo=timezone.utc)
end_date = datetime(2024, 3, 15, 9, 30, 0, tzinfo=timezone.utc) # 30분
# 다중 거래대상 조회
symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT"]
orderbook_stream = client.replay(
exchange="binance-futures",
symbols=symbols,
from_date=start_date,
to_date=end_date,
filters=[Message.Type.ORDERBOOK_SNAPSHOT, Message.Type.ORDERBOOK_DELTA]
)
# 거래대상별 카운트
symbol_stats = {symbol: 0 for symbol in symbols}
async for message in orderbook_stream:
if message.type in [Message.Type.ORDERBOOK_SNAPSHOT, Message.Type.ORDERBOOK_DELTA]:
symbol = message.symbol
if symbol in symbol_stats:
symbol_stats[symbol] += 1
# 결과 출력
print("=" * 50)
print("거래대상별 호가창 메시지 통계")
print("=" * 50)
for symbol, count in symbol_stats.items():
print(f"{symbol}: {count:,}개 메시지")
print("=" * 50)
asyncio.run(fetch_specific_symbols())
4단계: 호가창 데이터 리플레이 및 분석
이제 가져온 호가창 데이터를 실제 분석에 활용하는 방법을 살펴보겠습니다.
4.1 Spread 및 유동성 분석
import pandas as pd
from dataclasses import dataclass
from typing import List, Tuple
from datetime import datetime, timezone
@dataclass
class OrderbookSnapshot:
"""단일 시점 호가창 스냅샷"""
timestamp: datetime
symbol: str
best_bid: float
best_ask: float
mid_price: float
spread: float
spread_bps: float # basis points (1/10000)
total_bid_volume: float
total_ask_volume: float
class OrderbookAnalyzer:
"""호가창 분석기 - 유동성 지표 계산"""
def __init__(self, depth: int = 10):
self.depth = depth # 분석할 호가창 깊이
self.snapshots: List[OrderbookSnapshot] = []
def process_message(self, message) -> OrderbookSnapshot:
"""메시지에서 호가창 스냅샷 추출 및 분석"""
bids = message.data.get('bids', [])
asks = message.data.get('asks', [])
# 최고 매수/매도 호가
best_bid_price = float(bids[0][0]) if bids else 0.0
best_ask_price = float(asks[0][0]) if asks else 0.0
# 중간 가격 및 스프레드
mid_price = (best_bid_price + best_ask_price) / 2
spread = best_ask_price - best_bid_price
spread_bps = (spread / mid_price) * 10000 if mid_price > 0 else 0.0
# 특정 깊이까지의 누적 수량
total_bid_volume = sum(float(b[1]) for b in bids[:self.depth])
total_ask_volume = sum(float(a[1]) for a in asks[:self.depth])
snapshot = OrderbookSnapshot(
timestamp=message.local_timestamp,
symbol=message.symbol,
best_bid=best_bid_price,
best_ask=best_ask_price,
mid_price=mid_price,
spread=spread,
spread_bps=spread_bps,
total_bid_volume=total_bid_volume,
total_ask_volume=total_ask_volume
)
self.snapshots.append(snapshot)
return snapshot
def generate_report(self) -> pd.DataFrame:
"""분석 결과 DataFrame 반환"""
if not self.snapshots:
return pd.DataFrame()
df = pd.DataFrame([
{
"timestamp": s.timestamp,
"symbol": s.symbol,
"mid_price": s.mid_price,
"spread": s.spread,
"spread_bps": s.spread_bps,
"bid_volume": s.total_bid_volume,
"ask_volume": s.total_ask_volume,
"imbalance": (s.total_bid_volume - s.total_ask_volume) /
(s.total_bid_volume + s.total_ask_volume) if
(s.total_bid_volume + s.total_ask_volume) > 0 else 0
}
for s in self.snapshots
])
return df
def print_statistics(self):
"""통계 요약 출력"""
if not self.snapshots:
print("분석할 데이터가 없습니다.")
return
df = self.generate_report()
print("=" * 60)
print("호가창 분석 리포트")
print("=" * 60)
print(f"총 샘플 수: {len(df):,}개")
print(f"시간 범위: {df['timestamp'].min()} ~ {df['timestamp'].max()}")
print()
print(f"스프레드 (평균): {df['spread_bps'].mean():.2f} bps")
print(f"스프레드 (최대): {df['spread_bps'].max():.2f} bps")
print(f"스프레드 (최소): {df['spread_bps'].min():.2f} bps")
print()
print(f"호가창 불균형 (평균): {df['imbalance'].mean():.4f}")
print(f"호가창 불균형 (최대): {df['imbalance'].max():.4f}")
print(f"호가창 불균형 (최소): {df['imbalance'].min():.4f}")
print("=" * 60)
4.2 백테스팅에 활용하기
async def backtest_with_orderbook():
"""호가창 데이터를 활용한 간단한 백테스트 예시"""
client = TardisClient(api_token=TARDIS_API_TOKEN)
analyzer = OrderbookAnalyzer(depth=20) # 20단계 깊이 분석
# 테스트 기간: 2024-03-15 10:00 ~ 11:00
from datetime import datetime, timezone
start_date = datetime(2024, 3, 15, 10, 0, 0, tzinfo=timezone.utc)
end_date = datetime(2024, 3, 15, 11, 0, 0, tzinfo=timezone.utc)
orderbook_stream = client.replay(
exchange="binance-futures",
symbols=["BTCUSDT"],
from_date=start_date,
to_date=end_date,
filters=[Message.Type.ORDERBOOK_SNAPSHOT, Message.Type.ORDERBOOK_DELTA]
)
trade_signals = []
async for message in orderbook_stream:
if message.type in [Message.Type.ORDERBOOK_SNAPSHOT, Message.Type.ORDERBOOK_DELTA]:
snapshot = analyzer.process_message(message)
# 간단한 전략: 스프레드가 넓어지면 유동성 부족 신호
if snapshot.spread_bps > 5.0: # 5 bps 이상
trade_signals.append({
"timestamp": snapshot.timestamp,
"signal": "LOW_LIQUIDITY",
"spread_bps": snapshot.spread_bps,
"mid_price": snapshot.mid_price
})
# 결과 분석
analyzer.print_statistics()
print("\n⚠️ 低流动性 신호 발생 횟수:", len(trade_signals))
# 신호 상세 출력
if trade_signals:
print("\n최근 5개 신호:")
for signal in trade_signals[-5:]:
print(f" [{signal['timestamp']}] {signal['signal']}: "
f"spread={signal['spread_bps']:.2f}bps, price=${signal['mid_price']:,.2f}")
asyncio.run(backtest_with_orderbook())
5단계: 실제 활용 사례
5.1 Arbitrage Detection (차익거래 탐지)
async def detect_arbitrage():
"""Binance Futures vs Spot 간 차익거래 기회 탐지"""
client = TardisClient(api_token=TARDIS_API_TOKEN)
# Futures와 Spot 데이터 동시 조회
from datetime import datetime, timezone
start_date = datetime(2024, 3, 15, 14, 0, 0, tzinfo=timezone.utc)
end_date = datetime(2024, 3, 15, 14, 30, 0, tzinfo=timezone.utc)
# Futures 데이터
futures_stream = client.replay(
exchange="binance-futures",
symbols=["BTCUSDT"],
from_date=start_date,
to_date=end_date,
filters=[Message.Type.ORDERBOOK_SNAPSHOT]
)
# Spot 데이터
spot_stream = client.replay(
exchange="binance",
symbols=["BTCUSDT"],
from_date=start_date,
to_date=end_date,
filters=[Message.Type.ORDERBOOK_SNAPSHOT]
)
futures_prices = []
spot_prices = []
# 데이터 수집
async for msg in futures_stream:
if msg.type == Message.Type.ORDERBOOK_SNAPSHOT:
if msg.data.get('bids'):
futures_prices.append({
'time': msg.local_timestamp,
'mid': (float(msg.data['bids'][0][0]) + float(msg.data['asks'][0][0])) / 2
})
async for msg in spot_stream:
if msg.type == Message.Type.ORDERBOOK_SNAPSHOT:
if msg.data.get('bids'):
spot_prices.append({
'time': msg.local_timestamp,
'mid': (float(msg.data['bids'][0][0]) + float(msg.data['asks'][0][0])) / 2
})
# 차익거래 계산 (단순화 버전)
print(f"Futures 데이터 포인트: {len(futures_prices)}")
print(f"Spot 데이터 포인트: {len(spot_prices)}")
# NOTE: 실제 차익거래 감지에는 정교한 타임스탬프 정렬이 필요합니다
asyncio.run(detect_arbitrage())
5.2 시장 Impact 모델링
class MarketImpactModel:
"""시장 영향 모델 - 대규모 주문의 가격 이동 예측"""
def __init__(self):
self.liquidity_data = []
def estimate_impact(self, orderbook_data: dict, order_size: float) -> dict:
"""주문 크기 기반 예상 시장 영향 계산"""
bids = orderbook_data.get('bids', [])
# 누적 수량과 가격 관계 계산
cumulative_volume = 0.0
cumulative_cost = 0.0
levels = []
for i, (price, volume) in enumerate(bids):
price = float(price)
volume = float(volume)
if cumulative_volume + volume >= order_size:
# 목표 수량 도달
remaining = order_size - cumulative_volume
cumulative_cost += remaining * price
cumulative_volume = order_size
levels.append({'level': i+1, 'volume': remaining, 'price': price})
break
else:
cumulative_cost += volume * price
cumulative_volume += volume
levels.append({'level': i+1, 'volume': volume, 'price': price})
if cumulative_volume > 0:
avg_price = cumulative_cost / cumulative_volume
initial_price = float(bids[0][0]) if bids else 0
impact_bps = ((avg_price - initial_price) / initial_price) * 10000 if initial_price > 0 else 0
return {
'order_size': order_size,
'average_price': avg_price,
'initial_price': initial_price,
'price_impact_bps': impact_bps,
'levels_used': len(levels),
'vwap': avg_price
}
return None
사용 예시
model = MarketImpactModel()
sample_orderbook = {
'bids': [
["50000.00", "1.5"],
["49999.50", "2.3"],
["49999.00", "5.0"],
["49998.50", "8.0"],
["49998.00", "12.0"],
],
'asks': [
["50000.50", "1.8"],
["50001.00", "3.1"],
["50001.50", "6.0"],
]
}
5 BTC 시장가 매수 시 예상 영향
impact = model.estimate_impact(sample_orderbook, order_size=5.0)
if impact:
print(f"5 BTC 주문 예상 시장 영향:")
print(f" 평균 체결가: ${impact['average_price']:,.2f}")
print(f" 초기 가격: ${impact['initial_price']:,.2f}")
print(f" 가격 영향: {impact['price_impact_bps']:.2f} bps")
print(f" 사용 호가창 레벨: {impact['levels_used']}개")
자주 발생하는 오류와 해결책
오류 1: AuthenticationError - "Invalid API token"
증상: TardisClient 초기화 시 인증 오류 발생
# ❌ 잘못된 방법
client = TardisClient(api_token="invalid_token_123")
✅ 올바른 방법
import os
TARDIS_API_TOKEN = os.getenv("TARDIS_API_TOKEN")
if not TARDIS_API_TOKEN:
raise ValueError("TARDIS_API_TOKEN 환경변수가 설정되지 않았습니다")
client = TardisClient(api_token=TARDIS_API_TOKEN)
또는 직접 입력 (테스트용)
client = TardisClient(api_token="your_actual_token_here")
원인: API 토큰이 없거나 잘못된 형식으로 입력됨
해결: Tardis.dev Dashboard에서 새 토큰을 발급받고 환경변수로 안전하게 관리하세요.
오류 2: TimeoutError - 데이터 조회 시간 초과
증상: 장시간 데이터 조회 시 타임아웃 발생
# ❌ 기본 설정 (짧은 타임아웃)
orderbook_stream = client.replay(
exchange="binance-futures",
symbols=["BTCUSDT"],
from_date=start_date,
to_date=end_date
)
✅ 타임아웃 설정 및 배치 처리
from datetime import timedelta
async def fetch_with_retry():
"""재시도 로직 포함 데이터 조회"""
max_retries = 3
batch_duration = timedelta(hours=1) # 1시간 단위 배치
current_start = start_date
while current_start < end_date:
current_end = min(current_start + batch_duration, end_date)
for attempt in range(max_retries):
try:
stream = client.replay(
exchange="binance-futures",
symbols=["BTCUSDT"],
from_date=current_start,
to_date=current_end,
filters=[Message.Type.ORDERBOOK_SNAPSHOT]
)
# 데이터 처리...
async for msg in stream:
process_message(msg)
break # 성공 시 다음 배치로
except TimeoutError:
if attempt < max_retries - 1:
await asyncio.sleep(5) # 5초 대기 후 재시도
continue
else:
print(f"배치 [{current_start} ~ {current_end}] 조회 실패")
current_start = current_end
오류 3: MemoryError - 대용량 데이터 처리 중 메모리 부족
증상: 수백만 개의 메시지 처리 시 메모리 부족
# ❌ 전체 데이터 메모리 적재 (위험)
async def bad_approach():
all_messages = []
async for msg in stream:
all_messages.append(msg) # 메모리 초과 위험!
return all_messages
✅ 제너레이터 패턴 활용 (메모리 효율적)
async def good_approach():
"""메모리 효율적 스트리밍 처리"""
processed_count = 0
batch_size = 10000
batch_results = []
async for msg in stream:
# 메시지 처리 로직
result = process_message(msg)
if result:
batch_results.append(result)
# 배치 단위로 저장
if len(batch_results) >= batch_size:
save_to_file(batch_results)
processed_count += len(batch_results)
print(f"처리 완료: {processed_count:,}개 메시지")
batch_results = [] # 메모리 해제
# 남은 데이터 처리
if batch_results:
save_to_file(batch_results)
return processed_count
오류 4: SymbolNotFoundError - 거래대상 조회 실패
증상: BTC/USDT 심볼로 조회 시 데이터 없음
# ❌ 잘못된 심볼 형식
symbols = ["BTC/USDT", "ETH-USDT"]
✅ Tardis.dev 표준 심볼 형식 확인
Binance Futures USDT-M: BTCUSDT, ETHUSDT (합쳐진 형식)
Binance Spot: BTCUSDT (동일)
symbols = ["BTCUSDT", "ETHUSDT"]
⚠️ 거래소별 차이점 주의
Binance Futures: "BTCUSDT" ✓
Binance Spot: "BTCUSDT" ✓
Bybit: "BTCUSDT" ✓
Deribit: "BTC-PERPETUAL" (다른 형식)
가격과 ROI
| 플랜 | 월간 비용 | 데이터 한도 | 적합 대상 |
|---|---|---|---|
| Free | $0 | 제한적 (테스트용) | PoC, 학습 목적 |
| Startup | $99/월 | 일 1GB 스트리밍 | 소규모 퀀트팀 |
| Growth | $499/월 | 일 10GB 스트리밍 | 중규모 트레이딩팀 |
| Enterprise | 맞춤 견적 | 무제한 + 전용 지원 | 대규모 기관 |
ROI 고려사항:
- 시간 절약: 직접 크롤링 대비 데이터 수집 시간 80% 이상 단축
- 데이터 품질: 정규화된 1차 데이터 보장
- 인프라 비용: 자체 데이터 파이프라인 운영비 절감
왜 HolySheep를 선택해야 하나
HolySheep AI는 Tardis.dev와는 다른 영역을 전문으로 합니다:
- AI API Gateway: GPT-4.1, Claude, Gemini, DeepSeek 등 다중 모델 통합
- 비용 최적화: GPT-4.1 $8/MTok · Claude Sonnet 4.5 $15/MTok · Gemini 2.5 Flash $2.50/MTok · DeepSeek V3.2 $0.42/MTok
- 해외 신용카드 불필요: 로컬 결제 지원으로 글로벌 개발자 친화적
- 단일 API 키: 여러 AI 모델을 하나의 키로 관리
💡 팁: Tardis.dev로 수집한 호가창 데이터를 HolySheep AI의 DeepSeek V3.2($0.42/MTok)로 분석하면 비용 효율적인 시장 분석 파이프라인을 구축할 수 있습니다.
결론 및 다음 단계
이 튜토리얼에서는 Tardis.dev Python API를 사용하여 Binance USDT-M 선물 호가창 이력 데이터를 수집하고 분석하는 방법을 다루었습니다. 핵심内容包括:
- Tardis.dev API 연동: Python 환경설정부터 기본 데이터 조회까지
- 호가창 분석: 스프레드, 유동성, 시장 불균형 지표 계산
- 실전 활용: 백테스팅, 차익거래 탐지, 시장 영향 모델링
- 오류 해결: 인증, 타임아웃, 메모리, 심볼 형식 관련 문제 해결
다음 단계로 다음을 추천드립니다:
- 실시간 WebSocket 스트리밍으로 전환
- Apache Kafka, Redis 등 스트리밍 인프라 연동
- HolySheep AI와 결합한 ML 기반 거래 신호 생성
궁금한 점이 있으시면 언제든지 댓글 남겨주세요. Happy coding! 🚀