init: estudio_mercados analysis from fn_registry

This commit is contained in:
2026-04-06 00:57:05 +02:00
commit 65460ca942
48 changed files with 1888583 additions and 0 deletions
@@ -0,0 +1,77 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8b2334dd",
"metadata": {},
"source": [
"# 01 — Descarga Masiva de Datos Historicos (Binance)\n",
"\n",
"Dos metodos para obtener datos historicos:\n",
"\n",
"1. **REST API** (`/api/v3/klines`) — Paginado, max 1000 velas por request. Ideal para 7 dias.\n",
"2. **Data Vision** (`data.binance.vision`) — CSVs comprimidos diarios/mensuales. Ideal para meses/anos.\n",
"\n",
"**Rate limits REST:** 6000 weight/min, klines cuesta 2 weight.\n",
"\n",
"| Endpoint | Max/req | Weight | Uso |\n",
"|---|---|---|---|\n",
"| `/api/v3/klines` | 1000 velas | 2 | Candlesticks OHLCV |\n",
"| `/api/v3/aggTrades` | 1000 trades | 2 | Trades agregados (max 1h window) |\n",
"| `/api/v3/historicalTrades` | 1000 trades | 25 | Trades individuales (requiere API key) |\n",
"| `data.binance.vision` | Sin limite | 0 | CSVs bulk diarios/mensuales |"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3fe45a0a",
"metadata": {},
"outputs": [],
"source": [
"import requests\n",
"import time\n",
"import datetime\n",
"import io\n",
"import zipfile\n",
"import pandas as pd\n",
"\n",
"BASE = \"https://api.binance.com\"\n",
"DATA_VISION = \"https://data.binance.vision\""
]
},
{
"cell_type": "markdown",
"id": "721f68a0",
"metadata": {},
"source": [
"## Metodo 1: REST API — Klines con paginacion automatica\n",
"\n",
"`GET /api/v3/klines` devuelve max 1000 velas. Paginamos con `startTime`/`endTime`.\n",
"\n",
"Para 7 dias de velas 1m: ceil(7*24*60/1000) = **11 requests** (22 weight total, trivial)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,113 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "45f026b5",
"metadata": {},
"source": [
"# 02 — Streaming de Datos en Tiempo Real (Binance WebSocket)\n",
"\n",
"Binance ofrece WebSocket streams push-based para datos de mercado en tiempo real.\n",
"\n",
"**Base URLs:**\n",
"- Produccion: `wss://stream.binance.com:9443/ws/<stream>`\n",
"- Testnet: `wss://testnet.binance.vision/ws/<stream>`\n",
"- Multi-stream: `wss://stream.binance.com:9443/stream?streams=<s1>/<s2>`\n",
"\n",
"**Streams principales:**\n",
"| Stream | Nombre | Frecuencia |\n",
"|---|---|---|\n",
"| Trades individuales | `<symbol>@trade` | Cada trade |\n",
"| Klines en vivo | `<symbol>@kline_<interval>` | Cada cambio en vela |\n",
"| Mini ticker 24h | `<symbol>@miniTicker` | ~1s |\n",
"| Book ticker (best bid/ask) | `<symbol>@bookTicker` | Cada cambio |\n",
"| Todos los tickers | `!miniTicker@arr` | ~1s |\n",
"\n",
"**Reglas de conexion:**\n",
"- Ping cada 3 min desde Binance, pong requerido\n",
"- Desconexion automatica a las 24h — reconectar periodicamente\n",
"- Se puede suscribir/desuscribir dinamicamente via JSON"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6bb48c2e",
"metadata": {},
"outputs": [],
"source": [
"import asyncio\n",
"import json\n",
"import websockets\n",
"import pandas as pd\n",
"from datetime import datetime, timezone\n",
"from collections import deque\n",
"\n",
"WS_BASE = \"wss://stream.binance.com:9443/ws\""
]
},
{
"cell_type": "markdown",
"id": "2b2d9b2d",
"metadata": {},
"source": [
"## Stream de Trades individuales\n",
"\n",
"`<symbol>@trade` — recibe cada trade ejecutado en tiempo real.\n",
"\n",
"Campos clave:\n",
"- `p` = precio, `q` = cantidad\n",
"- `m` = true si el buyer es maker (es decir, fue un sell market order que impacto un bid)\n",
"- `t` = trade ID, `T` = timestamp"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aaed9f77",
"metadata": {},
"outputs": [],
"source": [
"async def stream_trades(symbol: str, max_trades: int = 100) -> list[dict]:\n",
" \"\"\"Captura N trades en tiempo real y retorna como lista.\"\"\"\n",
" url = f\"{WS_BASE}/{symbol.lower()}@trade\"\n",
" trades = []\n",
"\n",
" async with websockets.connect(url) as ws:\n",
" while len(trades) < max_trades:\n",
" msg = json.loads(await ws.recv())\n",
" trades.append({\n",
" \"trade_id\": msg[\"t\"],\n",
" \"time\": datetime.fromtimestamp(msg[\"T\"] / 1000, tz=timezone.utc),\n",
" \"price\": float(msg[\"p\"]),\n",
" \"qty\": float(msg[\"q\"]),\n",
" \"is_buyer_maker\": msg[\"m\"],\n",
" \"side\": \"SELL\" if msg[\"m\"] else \"BUY\",\n",
" })\n",
"\n",
" return trades"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,119 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "c723d607",
"metadata": {},
"source": [
"# 03 — Libro de Ordenes en Tiempo Real (Binance)\n",
"\n",
"Dos enfoques para mantener un order book local:\n",
"\n",
"### Enfoque A: Partial Book Depth (simple)\n",
"Stream `<symbol>@depth<levels>@100ms` con levels = 5, 10, 20.\n",
"Envia snapshot completo del top N en cada update. Sin logica de sync.\n",
"\n",
"### Enfoque B: Diff Depth + REST Snapshot (completo)\n",
"1. Abrir stream `<symbol>@depth@100ms` (diffs incrementales)\n",
"2. Buffear eventos iniciales\n",
"3. Pedir snapshot REST: `GET /api/v3/depth?symbol=X&limit=1000`\n",
"4. Descartar eventos con `u <= lastUpdateId` del snapshot\n",
"5. Primer evento procesado debe tener `U <= lastUpdateId+1` AND `u >= lastUpdateId+1`\n",
"6. Aplicar: qty > 0 = update nivel, qty = 0 = eliminar nivel\n",
"7. Validar continuidad: cada evento `U` == anterior `u + 1`, si no, re-sync\n",
"\n",
"### Campos del depth update\n",
"```json\n",
"{\n",
" \"U\": 157, \"u\": 160,\n",
" \"b\": [[\"price\", \"qty\"], ...], \n",
" \"a\": [[\"price\", \"qty\"], ...] \n",
"}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "23b19294",
"metadata": {},
"outputs": [],
"source": [
"import asyncio\n",
"import json\n",
"import requests\n",
"import websockets\n",
"from decimal import Decimal\n",
"from collections import deque\n",
"import pandas as pd\n",
"\n",
"BASE = \"https://api.binance.com\"\n",
"WS_BASE = \"wss://stream.binance.com:9443/ws\""
]
},
{
"cell_type": "markdown",
"id": "cff459c9",
"metadata": {},
"source": [
"## Enfoque A: Partial Book Depth (simple, sin sync)\n",
"\n",
"Stream `<symbol>@depth<levels>@100ms` — recibe snapshot completo del top N cada 100ms.\n",
"\n",
"Ideal para monitoreo rapido sin necesidad de mantener estado."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bf70712b",
"metadata": {},
"outputs": [],
"source": [
"async def stream_top_book(symbol: str, levels: int = 10, snapshots: int = 50) -> list[dict]:\n",
" \"\"\"Captura N snapshots del top del order book.\"\"\"\n",
" url = f\"{WS_BASE}/{symbol.lower()}@depth{levels}@100ms\"\n",
" results = []\n",
"\n",
" async with websockets.connect(url) as ws:\n",
" while len(results) < snapshots:\n",
" data = json.loads(await ws.recv())\n",
" best_bid = (float(data[\"bids\"][0][0]), float(data[\"bids\"][0][1]))\n",
" best_ask = (float(data[\"asks\"][0][0]), float(data[\"asks\"][0][1]))\n",
" spread = best_ask[0] - best_bid[0]\n",
" mid = (best_bid[0] + best_ask[0]) / 2\n",
" results.append({\n",
" \"time\": pd.Timestamp.now(tz=\"UTC\"),\n",
" \"best_bid\": best_bid[0], \"bid_qty\": best_bid[1],\n",
" \"best_ask\": best_ask[0], \"ask_qty\": best_ask[1],\n",
" \"spread\": spread, \"spread_bps\": (spread / mid) * 10000,\n",
" \"bids\": [(float(p), float(q)) for p, q in data[\"bids\"]],\n",
" \"asks\": [(float(p), float(q)) for p, q in data[\"asks\"]],\n",
" })\n",
"\n",
" return results"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,137 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "c4c1bfe6",
"metadata": {},
"source": [
"# 04 — Trading Programatico (Binance API)\n",
"\n",
"Operaciones de trading via REST API con autenticacion HMAC-SHA256.\n",
"\n",
"**Autenticacion:** Cada request firmado necesita:\n",
"1. Header `X-MBX-APIKEY` con tu API key\n",
"2. Parametro `timestamp` (unix ms, dentro de 5000ms del server)\n",
"3. Parametro `signature` = HMAC-SHA256(query_string, secret_key)\n",
"\n",
"**Endpoints de trading (Spot):**\n",
"| Accion | Metodo | Endpoint | Weight |\n",
"|---|---|---|---|\n",
"| Crear orden | POST | `/api/v3/order` | 1 |\n",
"| Test orden | POST | `/api/v3/order/test` | 1 |\n",
"| Cancelar orden | DELETE | `/api/v3/order` | 1 |\n",
"| Cancelar todas | DELETE | `/api/v3/openOrders` | 1 |\n",
"| Ver orden | GET | `/api/v3/order` | 4 |\n",
"| Ordenes abiertas | GET | `/api/v3/openOrders` | 6 |\n",
"| Cuenta/balances | GET | `/api/v3/account` | 20 |\n",
"| Mis trades | GET | `/api/v3/myTrades` | 20 |\n",
"\n",
"**Tipos de orden:** MARKET, LIMIT (GTC/IOC/FOK), STOP_LOSS_LIMIT, TAKE_PROFIT_LIMIT, LIMIT_MAKER\n",
"\n",
"**TESTNET:** `https://testnet.binance.vision` — mismo API, balances gratis, keys via GitHub login"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "599a54e7",
"metadata": {},
"outputs": [],
"source": [
"import hashlib\n",
"import hmac\n",
"import time\n",
"import math\n",
"import requests\n",
"import pandas as pd\n",
"\n",
"# --- CONFIGURACION ---\n",
"# Para testnet (seguro para pruebas):\n",
"BASE = \"https://testnet.binance.vision\"\n",
"# Para produccion (dinero real):\n",
"# BASE = \"https://api.binance.com\"\n",
"\n",
"# Crea tus keys en https://testnet.binance.vision (login con GitHub)\n",
"API_KEY = \"\" # <-- tu API key aqui\n",
"API_SECRET = \"\" # <-- tu secret aqui"
]
},
{
"cell_type": "markdown",
"id": "acc0b354",
"metadata": {},
"source": [
"## Firma HMAC-SHA256\n",
"\n",
"Toda request autenticada requiere `timestamp` + `signature`. La firma es HMAC del query string completo."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3fa09567",
"metadata": {},
"outputs": [],
"source": [
"def signed_request(method: str, endpoint: str, params: dict | None = None) -> dict:\n",
" \"\"\"Request firmado a Binance API (funciona con testnet y produccion).\"\"\"\n",
" if params is None:\n",
" params = {}\n",
"\n",
" params[\"timestamp\"] = int(time.time() * 1000)\n",
" params[\"recvWindow\"] = 5000\n",
"\n",
" query_string = \"&\".join(f\"{k}={v}\" for k, v in params.items())\n",
" signature = hmac.new(\n",
" API_SECRET.encode(), query_string.encode(), hashlib.sha256\n",
" ).hexdigest()\n",
" params[\"signature\"] = signature\n",
"\n",
" headers = {\"X-MBX-APIKEY\": API_KEY}\n",
"\n",
" if method == \"GET\":\n",
" resp = requests.get(f\"{BASE}{endpoint}\", params=params, headers=headers)\n",
" elif method == \"POST\":\n",
" resp = requests.post(f\"{BASE}{endpoint}\", params=params, headers=headers)\n",
" elif method == \"DELETE\":\n",
" resp = requests.delete(f\"{BASE}{endpoint}\", params=params, headers=headers)\n",
" else:\n",
" raise ValueError(f\"Metodo no soportado: {method}\")\n",
"\n",
" resp.raise_for_status()\n",
" return resp.json()"
]
},
{
"cell_type": "markdown",
"id": "725c3d14",
"metadata": {},
"source": [
"## Consultar informacion del simbolo (filtros de ordenes)\n",
"\n",
"Antes de operar, hay que conocer los filtros: `LOT_SIZE` (min/max qty, step), `PRICE_FILTER` (tick size), `NOTIONAL` (min valor en quote)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,127 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8b2334dd",
"metadata": {},
"source": [
"# 01 — Descarga Masiva de Datos Historicos (Binance)\n",
"\n",
"Dos metodos para obtener datos historicos:\n",
"\n",
"1. **REST API** (`/api/v3/klines`) — Paginado, max 1000 velas por request. Ideal para 7 dias.\n",
"2. **Data Vision** (`data.binance.vision`) — CSVs comprimidos diarios/mensuales. Ideal para meses/anos.\n",
"\n",
"**Rate limits REST:** 6000 weight/min, klines cuesta 2 weight.\n",
"\n",
"| Endpoint | Max/req | Weight | Uso |\n",
"|---|---|---|---|\n",
"| `/api/v3/klines` | 1000 velas | 2 | Candlesticks OHLCV |\n",
"| `/api/v3/aggTrades` | 1000 trades | 2 | Trades agregados (max 1h window) |\n",
"| `/api/v3/historicalTrades` | 1000 trades | 25 | Trades individuales (requiere API key) |\n",
"| `data.binance.vision` | Sin limite | 0 | CSVs bulk diarios/mensuales |"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3fe45a0a",
"metadata": {},
"outputs": [],
"source": [
"import requests\n",
"import time\n",
"import datetime\n",
"import io\n",
"import zipfile\n",
"import pandas as pd\n",
"\n",
"BASE = \"https://api.binance.com\"\n",
"DATA_VISION = \"https://data.binance.vision\""
]
},
{
"cell_type": "markdown",
"id": "721f68a0",
"metadata": {},
"source": [
"## Metodo 1: REST API — Klines con paginacion automatica\n",
"\n",
"`GET /api/v3/klines` devuelve max 1000 velas. Paginamos con `startTime`/`endTime`.\n",
"\n",
"Para 7 dias de velas 1m: ceil(7*24*60/1000) = **11 requests** (22 weight total, trivial)."
]
},
{
"cell_type": "code",
"id": "24c183b7",
"source": "def parse_kline(k: list) -> dict:\n \"\"\"Parsea una vela raw de Binance a dict con tipos correctos.\"\"\"\n return {\n \"open_time\": pd.Timestamp(k[0], unit=\"ms\", tz=\"UTC\"),\n \"open\": float(k[1]),\n \"high\": float(k[2]),\n \"low\": float(k[3]),\n \"close\": float(k[4]),\n \"volume\": float(k[5]),\n \"close_time\": pd.Timestamp(k[6], unit=\"ms\", tz=\"UTC\"),\n \"quote_volume\": float(k[7]),\n \"trades\": int(k[8]),\n \"taker_buy_base_vol\": float(k[9]),\n \"taker_buy_quote_vol\": float(k[10]),\n }\n\n\ndef fetch_klines(symbol: str, interval: str, start_ms: int, end_ms: int, limit: int = 1000) -> list[dict]:\n \"\"\"Descarga klines con paginacion automatica.\"\"\"\n all_klines = []\n current = start_ms\n\n while current < end_ms:\n resp = requests.get(f\"{BASE}/api/v3/klines\", params={\n \"symbol\": symbol, \"interval\": interval,\n \"startTime\": current, \"endTime\": end_ms, \"limit\": limit,\n })\n resp.raise_for_status()\n data = resp.json()\n if not data:\n break\n\n all_klines.extend(parse_kline(k) for k in data)\n current = data[-1][6] + 1 # close_time + 1ms\n\n if len(data) < limit:\n break\n time.sleep(0.1)\n\n return all_klines",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "e3280dc5",
"source": "### Ejemplo: 7 dias de velas 1m para BTCUSDT",
"metadata": {}
},
{
"cell_type": "code",
"id": "b0b389b8",
"source": "now_ms = int(datetime.datetime.now(datetime.timezone.utc).timestamp() * 1000)\nseven_days_ago = now_ms - 7 * 24 * 60 * 60 * 1000\n\nklines = fetch_klines(\"BTCUSDT\", \"1m\", seven_days_ago, now_ms)\ndf_klines = pd.DataFrame(klines)\nprint(f\"Descargadas {len(df_klines)} velas de 1m ({len(df_klines)/1440:.1f} dias)\")\ndf_klines.head()",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "e52d66d1",
"source": "## Metodo 2: Data Vision — CSVs bulk (meses/anos de datos)\n\n`data.binance.vision` publica CSVs comprimidos diarios y mensuales. Sin API key, sin rate limits.\n\nIdeal para backtesting con historicos largos. Disponible para spot y futures.",
"metadata": {}
},
{
"cell_type": "code",
"id": "a42f986c",
"source": "def download_data_vision(symbol: str, data_type: str, date_str: str,\n interval: str = \"1m\", market: str = \"spot\") -> str | None:\n \"\"\"\n Descarga CSV diario/mensual desde data.binance.vision.\n date_str: \"2024-01-15\" (diario) o \"2024-01\" (mensual)\n data_type: \"klines\", \"trades\", \"aggTrades\"\n \"\"\"\n granularity = \"daily\" if len(date_str) > 7 else \"monthly\"\n\n if data_type == \"klines\":\n path = f\"data/{market}/{granularity}/{data_type}/{symbol}/{interval}/{symbol}-{interval}-{date_str}.zip\"\n else:\n path = f\"data/{market}/{granularity}/{data_type}/{symbol}/{symbol}-{data_type}-{date_str}.zip\"\n\n resp = requests.get(f\"{DATA_VISION}/{path}\")\n if resp.status_code == 404:\n return None\n resp.raise_for_status()\n\n with zipfile.ZipFile(io.BytesIO(resp.content)) as zf:\n return zf.read(zf.namelist()[0]).decode(\"utf-8\")\n\n\ndef download_klines_bulk(symbol: str, days: int = 7, interval: str = \"1m\") -> pd.DataFrame:\n \"\"\"Descarga N dias de klines desde Data Vision y retorna DataFrame.\"\"\"\n from datetime import date, timedelta\n\n cols = [\"open_time\", \"open\", \"high\", \"low\", \"close\", \"volume\",\n \"close_time\", \"quote_volume\", \"trades\", \"taker_buy_base_vol\",\n \"taker_buy_quote_vol\", \"ignore\"]\n all_rows = []\n today = date.today()\n\n for i in range(2, days + 2): # empezar 2 dias atras (hoy puede no estar disponible)\n d = today - timedelta(days=i)\n csv_text = download_data_vision(symbol, \"klines\", d.isoformat(), interval)\n if csv_text:\n for line in csv_text.strip().split(\"\\n\"):\n all_rows.append(line.split(\",\"))\n\n df = pd.DataFrame(all_rows, columns=cols)\n for col in [\"open\", \"high\", \"low\", \"close\", \"volume\", \"quote_volume\",\n \"taker_buy_base_vol\", \"taker_buy_quote_vol\"]:\n df[col] = df[col].astype(float)\n df[\"open_time\"] = pd.to_datetime(df[\"open_time\"].astype(int), unit=\"ms\", utc=True)\n df[\"close_time\"] = pd.to_datetime(df[\"close_time\"].astype(int), unit=\"ms\", utc=True)\n df[\"trades\"] = df[\"trades\"].astype(int)\n return df.drop(columns=[\"ignore\"]).sort_values(\"open_time\").reset_index(drop=True)",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "89438f51",
"source": "### Ejemplo: 7 dias de BTCUSDT via Data Vision (mas rapido, sin rate limits)",
"metadata": {}
},
{
"cell_type": "code",
"id": "5368c4b7",
"source": "df_bulk = download_klines_bulk(\"BTCUSDT\", days=7, interval=\"1m\")\nprint(f\"Data Vision: {len(df_bulk)} velas ({len(df_bulk)/1440:.1f} dias)\")\nprint(f\"Rango: {df_bulk['open_time'].min()} -> {df_bulk['open_time'].max()}\")\nprint(f\"Precio: {df_bulk['close'].min():.2f} - {df_bulk['close'].max():.2f}\")\ndf_bulk.describe()",
"metadata": {},
"execution_count": null,
"outputs": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,169 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "45f026b5",
"metadata": {},
"source": [
"# 02 — Streaming de Datos en Tiempo Real (Binance WebSocket)\n",
"\n",
"Binance ofrece WebSocket streams push-based para datos de mercado en tiempo real.\n",
"\n",
"**Base URLs:**\n",
"- Produccion: `wss://stream.binance.com:9443/ws/<stream>`\n",
"- Testnet: `wss://testnet.binance.vision/ws/<stream>`\n",
"- Multi-stream: `wss://stream.binance.com:9443/stream?streams=<s1>/<s2>`\n",
"\n",
"**Streams principales:**\n",
"| Stream | Nombre | Frecuencia |\n",
"|---|---|---|\n",
"| Trades individuales | `<symbol>@trade` | Cada trade |\n",
"| Klines en vivo | `<symbol>@kline_<interval>` | Cada cambio en vela |\n",
"| Mini ticker 24h | `<symbol>@miniTicker` | ~1s |\n",
"| Book ticker (best bid/ask) | `<symbol>@bookTicker` | Cada cambio |\n",
"| Todos los tickers | `!miniTicker@arr` | ~1s |\n",
"\n",
"**Reglas de conexion:**\n",
"- Ping cada 3 min desde Binance, pong requerido\n",
"- Desconexion automatica a las 24h — reconectar periodicamente\n",
"- Se puede suscribir/desuscribir dinamicamente via JSON"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6bb48c2e",
"metadata": {},
"outputs": [],
"source": [
"import asyncio\n",
"import json\n",
"import websockets\n",
"import pandas as pd\n",
"from datetime import datetime, timezone\n",
"from collections import deque\n",
"\n",
"WS_BASE = \"wss://stream.binance.com:9443/ws\""
]
},
{
"cell_type": "markdown",
"id": "2b2d9b2d",
"metadata": {},
"source": [
"## Stream de Trades individuales\n",
"\n",
"`<symbol>@trade` — recibe cada trade ejecutado en tiempo real.\n",
"\n",
"Campos clave:\n",
"- `p` = precio, `q` = cantidad\n",
"- `m` = true si el buyer es maker (es decir, fue un sell market order que impacto un bid)\n",
"- `t` = trade ID, `T` = timestamp"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aaed9f77",
"metadata": {},
"outputs": [],
"source": [
"async def stream_trades(symbol: str, max_trades: int = 100) -> list[dict]:\n",
" \"\"\"Captura N trades en tiempo real y retorna como lista.\"\"\"\n",
" url = f\"{WS_BASE}/{symbol.lower()}@trade\"\n",
" trades = []\n",
"\n",
" async with websockets.connect(url) as ws:\n",
" while len(trades) < max_trades:\n",
" msg = json.loads(await ws.recv())\n",
" trades.append({\n",
" \"trade_id\": msg[\"t\"],\n",
" \"time\": datetime.fromtimestamp(msg[\"T\"] / 1000, tz=timezone.utc),\n",
" \"price\": float(msg[\"p\"]),\n",
" \"qty\": float(msg[\"q\"]),\n",
" \"is_buyer_maker\": msg[\"m\"],\n",
" \"side\": \"SELL\" if msg[\"m\"] else \"BUY\",\n",
" })\n",
"\n",
" return trades"
]
},
{
"cell_type": "markdown",
"id": "5dc19bb4",
"source": "### Ejemplo: Capturar 100 trades de BTCUSDT",
"metadata": {}
},
{
"cell_type": "code",
"id": "2ad6bc1c",
"source": "trades = await stream_trades(\"BTCUSDT\", max_trades=100)\ndf_trades = pd.DataFrame(trades)\nprint(f\"Capturados {len(df_trades)} trades\")\nprint(f\"Rango de tiempo: {df_trades['time'].min()} -> {df_trades['time'].max()}\")\nprint(f\"Precio: {df_trades['price'].min():.2f} - {df_trades['price'].max():.2f}\")\nprint(f\"BUY: {(df_trades['side'] == 'BUY').sum()}, SELL: {(df_trades['side'] == 'SELL').sum()}\")\ndf_trades.head(10)",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "65a65f5b",
"source": "## Stream de Klines en vivo\n\n`<symbol>@kline_<interval>` — recibe actualizaciones de la vela actual y velas cerradas.\n\nCampo clave: `k.x` = true cuando la vela esta cerrada (final). Mientras `x=false`, los valores OHLCV son parciales.",
"metadata": {}
},
{
"cell_type": "code",
"id": "99c11390",
"source": "async def stream_klines(symbol: str, interval: str = \"1m\", max_closed: int = 5) -> list[dict]:\n \"\"\"Captura N velas CERRADAS en tiempo real (espera a que x=true).\"\"\"\n url = f\"{WS_BASE}/{symbol.lower()}@kline_{interval}\"\n closed_candles = []\n current = None\n\n async with websockets.connect(url) as ws:\n while len(closed_candles) < max_closed:\n msg = json.loads(await ws.recv())\n k = msg[\"k\"]\n current = {\n \"open_time\": pd.Timestamp(k[\"t\"], unit=\"ms\", tz=\"UTC\"),\n \"open\": float(k[\"o\"]),\n \"high\": float(k[\"h\"]),\n \"low\": float(k[\"l\"]),\n \"close\": float(k[\"c\"]),\n \"volume\": float(k[\"v\"]),\n \"trades\": k[\"n\"],\n \"is_closed\": k[\"x\"],\n }\n if k[\"x\"]: # vela cerrada\n closed_candles.append(current)\n print(f\"Vela cerrada #{len(closed_candles)}: {current['close']:.2f} | vol={current['volume']:.4f} | trades={current['trades']}\")\n\n return closed_candles",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "ddab999b",
"source": "### Ejemplo: Capturar 3 velas cerradas de 1m de BTCUSDT\n\n**Nota:** Esto tarda hasta 3 minutos esperando que se cierren las velas.",
"metadata": {}
},
{
"cell_type": "code",
"id": "5e4b017f",
"source": "candles = await stream_klines(\"BTCUSDT\", interval=\"1m\", max_closed=3)\ndf_candles = pd.DataFrame(candles)\ndf_candles",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "b067572a",
"source": "## Multi-stream: multiples feeds en una conexion\n\nCombinar trades + klines + book ticker de varios pares en un solo WebSocket.",
"metadata": {}
},
{
"cell_type": "code",
"id": "b4763056",
"source": "async def stream_multiple(streams: list[str], max_messages: int = 100) -> list[dict]:\n \"\"\"Captura N mensajes de multiples streams combinados.\"\"\"\n combined = \"/\".join(streams)\n url = f\"wss://stream.binance.com:9443/stream?streams={combined}\"\n messages = []\n\n async with websockets.connect(url) as ws:\n while len(messages) < max_messages:\n raw = json.loads(await ws.recv())\n messages.append({\n \"stream\": raw[\"stream\"],\n \"data\": raw[\"data\"],\n })\n\n return messages\n\n\n# Ejemplo: trades de BTC + ETH + book ticker de BTC\nmulti = await stream_multiple([\n \"btcusdt@trade\",\n \"ethusdt@trade\",\n \"btcusdt@bookTicker\",\n], max_messages=50)\n\n# Contar mensajes por stream\nfrom collections import Counter\ncounts = Counter(m[\"stream\"] for m in multi)\nprint(\"Mensajes por stream:\")\nfor stream, count in counts.most_common():\n print(f\" {stream}: {count}\")",
"metadata": {},
"execution_count": null,
"outputs": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+175
View File
@@ -0,0 +1,175 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "c723d607",
"metadata": {},
"source": [
"# 03 — Libro de Ordenes en Tiempo Real (Binance)\n",
"\n",
"Dos enfoques para mantener un order book local:\n",
"\n",
"### Enfoque A: Partial Book Depth (simple)\n",
"Stream `<symbol>@depth<levels>@100ms` con levels = 5, 10, 20.\n",
"Envia snapshot completo del top N en cada update. Sin logica de sync.\n",
"\n",
"### Enfoque B: Diff Depth + REST Snapshot (completo)\n",
"1. Abrir stream `<symbol>@depth@100ms` (diffs incrementales)\n",
"2. Buffear eventos iniciales\n",
"3. Pedir snapshot REST: `GET /api/v3/depth?symbol=X&limit=1000`\n",
"4. Descartar eventos con `u <= lastUpdateId` del snapshot\n",
"5. Primer evento procesado debe tener `U <= lastUpdateId+1` AND `u >= lastUpdateId+1`\n",
"6. Aplicar: qty > 0 = update nivel, qty = 0 = eliminar nivel\n",
"7. Validar continuidad: cada evento `U` == anterior `u + 1`, si no, re-sync\n",
"\n",
"### Campos del depth update\n",
"```json\n",
"{\n",
" \"U\": 157, \"u\": 160,\n",
" \"b\": [[\"price\", \"qty\"], ...], \n",
" \"a\": [[\"price\", \"qty\"], ...] \n",
"}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "23b19294",
"metadata": {},
"outputs": [],
"source": [
"import asyncio\n",
"import json\n",
"import requests\n",
"import websockets\n",
"from decimal import Decimal\n",
"from collections import deque\n",
"import pandas as pd\n",
"\n",
"BASE = \"https://api.binance.com\"\n",
"WS_BASE = \"wss://stream.binance.com:9443/ws\""
]
},
{
"cell_type": "markdown",
"id": "cff459c9",
"metadata": {},
"source": [
"## Enfoque A: Partial Book Depth (simple, sin sync)\n",
"\n",
"Stream `<symbol>@depth<levels>@100ms` — recibe snapshot completo del top N cada 100ms.\n",
"\n",
"Ideal para monitoreo rapido sin necesidad de mantener estado."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bf70712b",
"metadata": {},
"outputs": [],
"source": [
"async def stream_top_book(symbol: str, levels: int = 10, snapshots: int = 50) -> list[dict]:\n",
" \"\"\"Captura N snapshots del top del order book.\"\"\"\n",
" url = f\"{WS_BASE}/{symbol.lower()}@depth{levels}@100ms\"\n",
" results = []\n",
"\n",
" async with websockets.connect(url) as ws:\n",
" while len(results) < snapshots:\n",
" data = json.loads(await ws.recv())\n",
" best_bid = (float(data[\"bids\"][0][0]), float(data[\"bids\"][0][1]))\n",
" best_ask = (float(data[\"asks\"][0][0]), float(data[\"asks\"][0][1]))\n",
" spread = best_ask[0] - best_bid[0]\n",
" mid = (best_bid[0] + best_ask[0]) / 2\n",
" results.append({\n",
" \"time\": pd.Timestamp.now(tz=\"UTC\"),\n",
" \"best_bid\": best_bid[0], \"bid_qty\": best_bid[1],\n",
" \"best_ask\": best_ask[0], \"ask_qty\": best_ask[1],\n",
" \"spread\": spread, \"spread_bps\": (spread / mid) * 10000,\n",
" \"bids\": [(float(p), float(q)) for p, q in data[\"bids\"]],\n",
" \"asks\": [(float(p), float(q)) for p, q in data[\"asks\"]],\n",
" })\n",
"\n",
" return results"
]
},
{
"cell_type": "markdown",
"id": "99c8d974",
"source": "### Ejemplo: Capturar 50 snapshots del top-10 del book de BTCUSDT",
"metadata": {}
},
{
"cell_type": "code",
"id": "855fc378",
"source": "snapshots = await stream_top_book(\"BTCUSDT\", levels=10, snapshots=50)\ndf_book = pd.DataFrame([{k: v for k, v in s.items() if k not in (\"bids\", \"asks\")} for s in snapshots])\nprint(f\"Capturados {len(df_book)} snapshots del order book\")\nprint(f\"Spread medio: {df_book['spread'].mean():.2f} USD ({df_book['spread_bps'].mean():.2f} bps)\")\nprint(f\"Spread min: {df_book['spread'].min():.2f}, max: {df_book['spread'].max():.2f}\")\ndf_book.head(10)",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "271ea442",
"source": "## Enfoque B: Order Book completo con diff depth + REST sync\n\nMantiene un order book local completo sincronizado via WebSocket diffs.\nMas complejo pero da acceso a todos los niveles de precio.",
"metadata": {}
},
{
"cell_type": "code",
"id": "79701eff",
"source": "class LocalOrderBook:\n \"\"\"Order book local sincronizado con Binance via WebSocket diffs.\"\"\"\n\n def __init__(self, symbol: str, depth_limit: int = 1000):\n self.symbol = symbol.upper()\n self.depth_limit = depth_limit\n self.bids: dict[Decimal, Decimal] = {} # price -> qty\n self.asks: dict[Decimal, Decimal] = {}\n self.last_update_id: int = 0\n self._synced = False\n\n def _get_snapshot(self) -> dict:\n resp = requests.get(f\"{BASE}/api/v3/depth\",\n params={\"symbol\": self.symbol, \"limit\": self.depth_limit})\n resp.raise_for_status()\n return resp.json()\n\n def _apply_update(self, sides: list[tuple[str, dict]]):\n for price_str, qty_str in sides:\n price, qty = Decimal(price_str), Decimal(qty_str)\n return price, qty\n\n def apply_event(self, event: dict):\n for price_str, qty_str in event.get(\"b\", []):\n p, q = Decimal(price_str), Decimal(qty_str)\n if q == 0:\n self.bids.pop(p, None)\n else:\n self.bids[p] = q\n for price_str, qty_str in event.get(\"a\", []):\n p, q = Decimal(price_str), Decimal(qty_str)\n if q == 0:\n self.asks.pop(p, None)\n else:\n self.asks[p] = q\n self.last_update_id = event[\"u\"]\n\n def best_bid(self) -> tuple[float, float] | None:\n if not self.bids:\n return None\n p = max(self.bids)\n return (float(p), float(self.bids[p]))\n\n def best_ask(self) -> tuple[float, float] | None:\n if not self.asks:\n return None\n p = min(self.asks)\n return (float(p), float(self.asks[p]))\n\n def spread(self) -> float | None:\n b, a = self.best_bid(), self.best_ask()\n return a[0] - b[0] if b and a else None\n\n def top_n(self, n: int = 5) -> dict:\n return {\n \"bids\": [(float(p), float(q)) for p, q in sorted(self.bids.items(), reverse=True)[:n]],\n \"asks\": [(float(p), float(q)) for p, q in sorted(self.asks.items())[:n]],\n }\n\n async def run(self, duration_seconds: int = 30):\n \"\"\"Sincroniza y mantiene el book durante N segundos.\"\"\"\n import time as _time\n url = f\"{WS_BASE}/{self.symbol.lower()}@depth@100ms\"\n buffer = []\n start = _time.time()\n\n async with websockets.connect(url) as ws:\n while _time.time() - start < duration_seconds:\n msg = json.loads(await asyncio.wait_for(ws.recv(), timeout=5))\n\n if not self._synced:\n buffer.append(msg)\n if len(buffer) >= 3:\n snap = self._get_snapshot()\n # Inicializar desde snapshot\n self.bids = {Decimal(p): Decimal(q) for p, q in snap[\"bids\"]}\n self.asks = {Decimal(p): Decimal(q) for p, q in snap[\"asks\"]}\n self.last_update_id = snap[\"lastUpdateId\"]\n\n # Procesar buffer\n for evt in buffer:\n if evt[\"u\"] <= self.last_update_id:\n continue\n if not self._synced and evt[\"U\"] <= self.last_update_id + 1:\n self._synced = True\n if self._synced:\n self.apply_event(evt)\n buffer.clear()\n if self._synced:\n print(f\"Book sincronizado! Levels: {len(self.bids)} bids, {len(self.asks)} asks\")\n else:\n if msg[\"U\"] != self.last_update_id + 1:\n print(\"Gap detectado, re-sync...\")\n self._synced = False\n buffer = [msg]\n continue\n self.apply_event(msg)",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "7370d164",
"source": "### Ejemplo: Correr el order book 30 segundos y ver el estado",
"metadata": {}
},
{
"cell_type": "code",
"id": "ae407462",
"source": "book = LocalOrderBook(\"BTCUSDT\")\nawait book.run(duration_seconds=30)\n\nprint(f\"\\nBest bid: {book.best_bid()}\")\nprint(f\"Best ask: {book.best_ask()}\")\nprint(f\"Spread: {book.spread():.2f} USD\")\nprint(f\"\\nTop 5:\")\ntop = book.top_n(5)\nprint(\"BIDS:\", top[\"bids\"])\nprint(\"ASKS:\", top[\"asks\"])",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "c11ab0fb",
"source": "## Snapshot REST directo (alternativa simple)\n\nPara consultas puntuales sin mantener estado, un GET directo al depth endpoint.",
"metadata": {}
},
{
"cell_type": "code",
"id": "9a31a0ba",
"source": "def get_depth_snapshot(symbol: str, limit: int = 20) -> dict:\n \"\"\"Snapshot directo del order book via REST.\"\"\"\n resp = requests.get(f\"{BASE}/api/v3/depth\", params={\"symbol\": symbol, \"limit\": limit})\n resp.raise_for_status()\n data = resp.json()\n return {\n \"bids\": [(float(p), float(q)) for p, q in data[\"bids\"]],\n \"asks\": [(float(p), float(q)) for p, q in data[\"asks\"]],\n \"last_update_id\": data[\"lastUpdateId\"],\n }\n\nsnap = get_depth_snapshot(\"BTCUSDT\", limit=10)\nprint(f\"Top 10 bids y asks (update_id={snap['last_update_id']}):\")\nprint(\"\\nBIDS (compra):\")\nfor p, q in snap[\"bids\"]:\n print(f\" {p:>12.2f} | {q:.6f}\")\nprint(\"\\nASKS (venta):\")\nfor p, q in snap[\"asks\"]:\n print(f\" {p:>12.2f} | {q:.6f}\")",
"metadata": {},
"execution_count": null,
"outputs": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,205 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "c4c1bfe6",
"metadata": {},
"source": [
"# 04 — Trading Programatico (Binance API)\n",
"\n",
"Operaciones de trading via REST API con autenticacion HMAC-SHA256.\n",
"\n",
"**Autenticacion:** Cada request firmado necesita:\n",
"1. Header `X-MBX-APIKEY` con tu API key\n",
"2. Parametro `timestamp` (unix ms, dentro de 5000ms del server)\n",
"3. Parametro `signature` = HMAC-SHA256(query_string, secret_key)\n",
"\n",
"**Endpoints de trading (Spot):**\n",
"| Accion | Metodo | Endpoint | Weight |\n",
"|---|---|---|---|\n",
"| Crear orden | POST | `/api/v3/order` | 1 |\n",
"| Test orden | POST | `/api/v3/order/test` | 1 |\n",
"| Cancelar orden | DELETE | `/api/v3/order` | 1 |\n",
"| Cancelar todas | DELETE | `/api/v3/openOrders` | 1 |\n",
"| Ver orden | GET | `/api/v3/order` | 4 |\n",
"| Ordenes abiertas | GET | `/api/v3/openOrders` | 6 |\n",
"| Cuenta/balances | GET | `/api/v3/account` | 20 |\n",
"| Mis trades | GET | `/api/v3/myTrades` | 20 |\n",
"\n",
"**Tipos de orden:** MARKET, LIMIT (GTC/IOC/FOK), STOP_LOSS_LIMIT, TAKE_PROFIT_LIMIT, LIMIT_MAKER\n",
"\n",
"**TESTNET:** `https://testnet.binance.vision` — mismo API, balances gratis, keys via GitHub login"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "599a54e7",
"metadata": {},
"outputs": [],
"source": [
"import hashlib\n",
"import hmac\n",
"import time\n",
"import math\n",
"import requests\n",
"import pandas as pd\n",
"\n",
"# --- CONFIGURACION ---\n",
"# Para testnet (seguro para pruebas):\n",
"BASE = \"https://testnet.binance.vision\"\n",
"# Para produccion (dinero real):\n",
"# BASE = \"https://api.binance.com\"\n",
"\n",
"# Crea tus keys en https://testnet.binance.vision (login con GitHub)\n",
"API_KEY = \"\" # <-- tu API key aqui\n",
"API_SECRET = \"\" # <-- tu secret aqui"
]
},
{
"cell_type": "markdown",
"id": "acc0b354",
"metadata": {},
"source": [
"## Firma HMAC-SHA256\n",
"\n",
"Toda request autenticada requiere `timestamp` + `signature`. La firma es HMAC del query string completo."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3fa09567",
"metadata": {},
"outputs": [],
"source": [
"def signed_request(method: str, endpoint: str, params: dict | None = None) -> dict:\n",
" \"\"\"Request firmado a Binance API (funciona con testnet y produccion).\"\"\"\n",
" if params is None:\n",
" params = {}\n",
"\n",
" params[\"timestamp\"] = int(time.time() * 1000)\n",
" params[\"recvWindow\"] = 5000\n",
"\n",
" query_string = \"&\".join(f\"{k}={v}\" for k, v in params.items())\n",
" signature = hmac.new(\n",
" API_SECRET.encode(), query_string.encode(), hashlib.sha256\n",
" ).hexdigest()\n",
" params[\"signature\"] = signature\n",
"\n",
" headers = {\"X-MBX-APIKEY\": API_KEY}\n",
"\n",
" if method == \"GET\":\n",
" resp = requests.get(f\"{BASE}{endpoint}\", params=params, headers=headers)\n",
" elif method == \"POST\":\n",
" resp = requests.post(f\"{BASE}{endpoint}\", params=params, headers=headers)\n",
" elif method == \"DELETE\":\n",
" resp = requests.delete(f\"{BASE}{endpoint}\", params=params, headers=headers)\n",
" else:\n",
" raise ValueError(f\"Metodo no soportado: {method}\")\n",
"\n",
" resp.raise_for_status()\n",
" return resp.json()"
]
},
{
"cell_type": "markdown",
"id": "725c3d14",
"metadata": {},
"source": [
"## Consultar informacion del simbolo (filtros de ordenes)\n",
"\n",
"Antes de operar, hay que conocer los filtros: `LOT_SIZE` (min/max qty, step), `PRICE_FILTER` (tick size), `NOTIONAL` (min valor en quote)."
]
},
{
"cell_type": "code",
"id": "f7dd53ba",
"source": "def get_symbol_filters(symbol: str) -> dict:\n \"\"\"Obtiene filtros de trading para un simbolo (no requiere auth).\"\"\"\n resp = requests.get(f\"{BASE}/api/v3/exchangeInfo\", params={\"symbol\": symbol})\n resp.raise_for_status()\n info = resp.json()[\"symbols\"][0]\n filters = {f[\"filterType\"]: f for f in info[\"filters\"]}\n return {\n \"status\": info[\"status\"],\n \"base\": info[\"baseAsset\"],\n \"quote\": info[\"quoteAsset\"],\n \"lot_size\": {\n \"min_qty\": float(filters[\"LOT_SIZE\"][\"minQty\"]),\n \"max_qty\": float(filters[\"LOT_SIZE\"][\"maxQty\"]),\n \"step\": float(filters[\"LOT_SIZE\"][\"stepSize\"]),\n },\n \"price_filter\": {\n \"min_price\": float(filters[\"PRICE_FILTER\"][\"minPrice\"]),\n \"max_price\": float(filters[\"PRICE_FILTER\"][\"maxPrice\"]),\n \"tick_size\": float(filters[\"PRICE_FILTER\"][\"tickSize\"]),\n },\n \"notional\": {\n \"min_notional\": float(filters[\"NOTIONAL\"][\"minNotional\"]),\n },\n }\n\n\ndef round_step(value: float, step: float) -> float:\n \"\"\"Redondea hacia abajo al step size mas cercano.\"\"\"\n precision = len(str(step).rstrip('0').split('.')[-1]) if '.' in str(step) else 0\n return math.floor(value / step) * step\n\n\n# Ejemplo: ver filtros de BTCUSDT\nfilters = get_symbol_filters(\"BTCUSDT\")\nfilters",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "53578416",
"source": "## Operaciones de Trading\n\nFunciones para market buy/sell, limit orders, stop-loss, y gestion de ordenes.\n\n**IMPORTANTE:** Estas funciones operan contra el endpoint configurado en `BASE`. Asegurate de usar testnet mientras pruebas.",
"metadata": {}
},
{
"cell_type": "code",
"id": "d4d33fb0",
"source": "def get_account() -> dict:\n \"\"\"Obtiene info de cuenta y balances no-cero.\"\"\"\n account = signed_request(\"GET\", \"/api/v3/account\")\n balances = {b[\"asset\"]: {\"free\": float(b[\"free\"]), \"locked\": float(b[\"locked\"])}\n for b in account[\"balances\"] if float(b[\"free\"]) > 0 or float(b[\"locked\"]) > 0}\n return balances\n\n\ndef market_buy(symbol: str, quote_qty: float) -> dict:\n \"\"\"Market buy: gasta X cantidad de quote asset (ej: 100 USDT).\"\"\"\n return signed_request(\"POST\", \"/api/v3/order\", {\n \"symbol\": symbol, \"side\": \"BUY\", \"type\": \"MARKET\",\n \"quoteOrderQty\": quote_qty, \"newOrderRespType\": \"FULL\",\n })\n\n\ndef market_sell(symbol: str, quantity: float) -> dict:\n \"\"\"Market sell: vende X cantidad de base asset (ej: 0.001 BTC).\"\"\"\n return signed_request(\"POST\", \"/api/v3/order\", {\n \"symbol\": symbol, \"side\": \"SELL\", \"type\": \"MARKET\",\n \"quantity\": quantity, \"newOrderRespType\": \"FULL\",\n })\n\n\ndef limit_buy(symbol: str, quantity: float, price: float) -> dict:\n \"\"\"Limit buy: compra X a precio Y o mejor.\"\"\"\n return signed_request(\"POST\", \"/api/v3/order\", {\n \"symbol\": symbol, \"side\": \"BUY\", \"type\": \"LIMIT\",\n \"timeInForce\": \"GTC\", \"quantity\": quantity, \"price\": price,\n \"newOrderRespType\": \"FULL\",\n })\n\n\ndef limit_sell(symbol: str, quantity: float, price: float) -> dict:\n \"\"\"Limit sell: vende X a precio Y o mejor.\"\"\"\n return signed_request(\"POST\", \"/api/v3/order\", {\n \"symbol\": symbol, \"side\": \"SELL\", \"type\": \"LIMIT\",\n \"timeInForce\": \"GTC\", \"quantity\": quantity, \"price\": price,\n \"newOrderRespType\": \"FULL\",\n })\n\n\ndef stop_loss_sell(symbol: str, quantity: float, stop_price: float, limit_price: float) -> dict:\n \"\"\"Stop-loss limit: se activa en stop_price, coloca limit en limit_price.\"\"\"\n return signed_request(\"POST\", \"/api/v3/order\", {\n \"symbol\": symbol, \"side\": \"SELL\", \"type\": \"STOP_LOSS_LIMIT\",\n \"timeInForce\": \"GTC\", \"quantity\": quantity,\n \"stopPrice\": stop_price, \"price\": limit_price,\n \"newOrderRespType\": \"FULL\",\n })\n\n\ndef cancel_order(symbol: str, order_id: int) -> dict:\n \"\"\"Cancela una orden especifica.\"\"\"\n return signed_request(\"DELETE\", \"/api/v3/order\", {\n \"symbol\": symbol, \"orderId\": order_id,\n })\n\n\ndef cancel_all_orders(symbol: str) -> dict:\n \"\"\"Cancela todas las ordenes abiertas de un simbolo.\"\"\"\n return signed_request(\"DELETE\", \"/api/v3/openOrders\", {\"symbol\": symbol})\n\n\ndef get_open_orders(symbol: str) -> list[dict]:\n \"\"\"Lista ordenes abiertas.\"\"\"\n return signed_request(\"GET\", \"/api/v3/openOrders\", {\"symbol\": symbol})\n\n\ndef get_my_trades(symbol: str, limit: int = 50) -> list[dict]:\n \"\"\"Historial de mis trades.\"\"\"\n return signed_request(\"GET\", \"/api/v3/myTrades\", {\"symbol\": symbol, \"limit\": limit})",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "9adf7577",
"source": "## Ejemplos de uso (TESTNET)\n\nConfigurar `API_KEY` y `API_SECRET` arriba antes de ejecutar estas celdas.\nKeys de testnet se crean en `testnet.binance.vision` con login de GitHub.",
"metadata": {}
},
{
"cell_type": "code",
"id": "ce167a84",
"source": "# 1. Ver balances de la cuenta testnet\nbalances = get_account()\nprint(\"Balances:\")\nfor asset, vals in sorted(balances.items()):\n print(f\" {asset}: free={vals['free']}, locked={vals['locked']}\")",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"id": "b4bc87f5",
"source": "# 2. Market buy — comprar $10 de BTC en testnet\norder = market_buy(\"BTCUSDT\", 10)\nprint(f\"Order ID: {order['orderId']}\")\nprint(f\"Status: {order['status']}\")\nprint(f\"Fills: {order.get('fills', [])}\")",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"id": "9ae35b32",
"source": "# 3. Limit buy — comprar 0.001 BTC a un precio bajo\n# Primero obtener precio actual para poner una limit por debajo\ncurrent_price = float(requests.get(f\"{BASE}/api/v3/ticker/price\",\n params={\"symbol\": \"BTCUSDT\"}).json()[\"price\"])\nlimit_price = round_step(current_price * 0.95, filters[\"price_filter\"][\"tick_size\"]) # 5% debajo\nqty = round_step(0.001, filters[\"lot_size\"][\"step\"])\n\nprint(f\"Precio actual: {current_price:.2f}\")\nprint(f\"Limit buy a: {limit_price:.2f} ({qty} BTC)\")\norder = limit_buy(\"BTCUSDT\", qty, limit_price)\nprint(f\"Order ID: {order['orderId']}, Status: {order['status']}\")",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"id": "f3b60aa0",
"source": "# 4. Ver ordenes abiertas y cancelar\nopen_orders = get_open_orders(\"BTCUSDT\")\nprint(f\"Ordenes abiertas: {len(open_orders)}\")\nfor o in open_orders:\n print(f\" ID={o['orderId']} {o['side']} {o['type']} qty={o['origQty']} price={o['price']} status={o['status']}\")\n\n# Cancelar todas\nif open_orders:\n cancel_all_orders(\"BTCUSDT\")\n print(\"Todas las ordenes canceladas\")",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"id": "a61bf0e9",
"source": "# 5. Historial de trades ejecutados\nmy_trades = get_my_trades(\"BTCUSDT\", limit=10)\nif my_trades:\n df_my = pd.DataFrame(my_trades)\n df_my[\"time\"] = pd.to_datetime(df_my[\"time\"], unit=\"ms\", utc=True)\n df_my[\"price\"] = df_my[\"price\"].astype(float)\n df_my[\"qty\"] = df_my[\"qty\"].astype(float)\n df_my[\"quoteQty\"] = df_my[\"quoteQty\"].astype(float)\n print(f\"Ultimos {len(df_my)} trades:\")\n display(df_my[[\"time\", \"price\", \"qty\", \"quoteQty\", \"isBuyer\", \"isMaker\"]])\nelse:\n print(\"Sin trades ejecutados aun\")",
"metadata": {},
"execution_count": null,
"outputs": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}