{ "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 }