Lo que vamos a construir
Al final de este tutorial tendrás una aplicación Delphi VCL funcionando que recibe en streaming datos en vivo de trades y order book de Binance, ejecuta una estrategia de breakout sencilla, coloca órdenes reales vía la API REST y aplica controles de riesgo (tamaño máximo de posición, límite diario de pérdidas, kill switch). La misma fontanería funciona para cualquier exchange soportado por sgcWebSockets — Coinbase, Kraken, OKX, Bybit, Bitfinex — cambiando sólo el mapeo de credenciales y símbolos.
Es la misma arquitectura que usa nuestro sample de referencia sgcTrader. Si quieres un punto de partida mayor con UI completa, charting y routing multi-exchange, cógelo. El recorrido de abajo muestra lo que ocurre por debajo en unas 300 líneas de código.
Dos prerrequisitos antes de empezar. Primero, consigue una API key de Binance (Account > API Management). Para desarrollo, genera una key que tenga sólo "Enable Reading" y "Enable Spot Trading" y mete tu IP en la whitelist. Nunca pongas en código una key con withdrawal activado. Segundo, hazlo todo primero en el testnet de Binance (testnet.binance.vision). Los endpoints, formatos de mensaje y algoritmo de firma son idénticos a producción, pero los fondos son falsos. Hemos perdido dinero real con "seguro que mi estrategia es correcta" exactamente el número de veces que no probamos primero en el testnet.
Arquitectura en un diagrama
Tres hilos, dos componentes, un gatekeeper de riesgo:
| Componente | Rol | Hilo |
TsgcWSAPI_Binance |
Stream WebSocket: trades, depth, klines, user data | Worker de I/O |
TsgcHTTP_API_Binance |
REST: colocación de órdenes, cancelar, snapshot de cuenta | Worker trader |
| Cola de estrategia | Desacoplamiento: eventos de mercado → decisiones → órdenes | Worker de estrategia |
| Puerta de riesgo | Bloquear / reducir / permitir cada orden | En línea en el trader |
Paso 1: stream de datos de mercado
Arrastra un TsgcWSAPI_Binance al form. Ya habla el protocolo combinado de streams de Binance — sólo te suscribes a los canales que quieras.
uses
sgcWSAPI_Binance;
procedure TForm1.FormCreate(Sender: TObject);
begin
oBinance := TsgcWSAPI_Binance.Create(Self);
oBinance.WatchDog.Enabled := True;
oBinance.WatchDog.Interval := 5;
oBinance.HeartBeat.Enabled := True;
oBinance.OnBinanceMessage := DoStream;
oBinance.OnDisconnect := DoDisconnect;
// Subscribe to 1-minute klines and aggregated trades for BTCUSDT
oBinance.Streams.Add('btcusdt@kline_1m');
oBinance.Streams.Add('btcusdt@aggTrade');
oBinance.Active := True;
end;
procedure TForm1.DoStream(Sender: TObject; const aStream, aData: string);
begin
// Fire-and-forget: push to the strategy queue
oStrategyQueue.Push(TMarketEvent.Create(aStream, aData));
end;
Esa es toda la capa de ingesta de datos de mercado. Reconexión en caídas, heartbeat para detectar enlaces muertos y un push no bloqueante a la cola de estrategia.
Una cosa que el componente hace y que de otro modo tendrías que escribir tú: la URL del combined stream de Binance es /stream?streams=name1/name2/name3, y si quieres añadir o quitar streams sin tirar la conexión tienes que enviar un mensaje JSON-RPC subscribe/unsubscribe sobre el mismo socket. TsgcWSAPI_Binance expone métodos SubscribeStream y UnsubscribeStream que manejan por ti el handshake JSON-RPC. Útil cuando el usuario elige un nuevo ticker en la UI — sin reconexión, sin mensajes perdidos.
Además: Binance impone límites por-IP y por-stream. Para profundidad completa en cada par USDT alcanzarás el límite de tasa de mensajes rápidamente. Suscríbete sólo a lo que realmente necesites y prefiere streams agregados (!miniTicker@arr) sobre streams por símbolo cuando necesites una vista amplia de mercado.
Paso 2: una estrategia mínima
La estrategia corre en su propio hilo. Mantiene una ventana rodante de los últimos N cierres del stream de klines de 1 minuto y se pone larga cuando el precio rompe por encima del máximo de 20 períodos. Pura ilustración — por favor no pongas esto delante de dinero real.
procedure TStrategyThread.Execute;
var
oEvent : TMarketEvent;
oJSON : TsgcJSONObject;
vClose : Double;
vHigh : Double;
begin
while not Terminated do
begin
if not oStrategyQueue.Pop(oEvent, 100) then Continue;
try
oJSON := TsgcJSONObject.Parse(oEvent.Data);
try
if oEvent.Stream.EndsWith('@kline_1m') then
begin
vClose := oJSON.O['k'].F['c'];
FCloses.Append(vClose);
if FCloses.Count >= 21 then
begin
vHigh := FCloses.Max(20); // prior 20-bar high
if (FPosition = 0) and (vClose > vHigh) then
oOrderQueue.Push(TIntent.New(siBuy, 'BTCUSDT', 0.001))
else if (FPosition > 0) and (vClose < FCloses.MA(20)) then
oOrderQueue.Push(TIntent.New(siSell, 'BTCUSDT', FPosition));
end;
end;
finally
oJSON.Free;
end;
finally
oEvent.Free;
end;
end;
end;
Paso 3: puerta de riesgo
Nunca dejes que una estrategia hable directamente con el exchange. Hace pasar cada intención por una puerta que conoce tus límites.
function TRiskGate.Validate(const aIntent: TIntent;
out aReason: string): Boolean;
begin
Result := False;
if FKillSwitch then
Exit(False);
if Abs(FDailyPnL) > FConfig.MaxDailyLoss then
begin
aReason := 'Daily loss limit hit';
Exit;
end;
if (aIntent.Side = siBuy)
and (FPositionUSD + aIntent.Qty * FLastPrice > FConfig.MaxPositionUSD) then
begin
aReason := 'Max position size';
Exit;
end;
Result := True;
end;
Paso 4: coloca la orden vía REST
El worker de órdenes saca intenciones validadas, firma la petición y hace POST a Binance.
procedure TTraderThread.Execute;
var
oIntent : TIntent;
vReason : string;
oResponse: TsgcBinanceClass_Response_NewOrder;
begin
while not Terminated do
begin
if not oOrderQueue.Pop(oIntent, 100) then Continue;
try
if not FRisk.Validate(oIntent, vReason) then
begin
Log(Format('REJECT %s %s qty=%.6f reason=%s',
[SideName(oIntent.Side), oIntent.Symbol, oIntent.Qty, vReason]));
Continue;
end;
oResponse := oHttp.NewOrder(
oIntent.Symbol,
IfThen(oIntent.Side = siBuy, 'BUY', 'SELL'),
'MARKET',
oIntent.Qty,
0 // price ignored for MARKET
);
try
Log(Format('FILL %s qty=%.6f price=%.2f id=%d',
[oIntent.Symbol, oResponse.ExecutedQty,
oResponse.AvgPrice, oResponse.OrderId]));
FRisk.OnFill(oIntent.Side, oResponse.ExecutedQty, oResponse.AvgPrice);
finally
oResponse.Free;
end;
finally
oIntent.Free;
end;
end;
end;
Paso 4b: autenticación REST y firma
El componente TsgcHTTP_API_Binance firma las peticiones por ti usando el secreto de la API. Entre bambalinas construye la query string canónica, calcula un HMAC-SHA256 con tu secreto y lo añade como parámetro signature. Aportas la key y el secreto una vez al arranque.
oHttp := TsgcHTTP_API_Binance.Create(Self);
oHttp.BinanceOptions.ApiKey := FConfig.ApiKey;
oHttp.BinanceOptions.ApiSecret := FConfig.ApiSecret;
oHttp.BinanceOptions.RecvWindow := 5000; // ms tolerance for signed requests
// Test connectivity and confirm your IP whitelist
ShowMessage('Server time: ' + IntToStr(oHttp.GetServerTime));
Si quieres correr contra el testnet, fija BinanceOptions.Testnet := True — el componente cambia automáticamente tanto la URL base REST como el host WebSocket. Construye y prueba contra el testnet, cambia un único flag, despliega a producción. La documentación de la API de Binance es por lo demás idéntica entre los dos entornos.
Paso 5: stream de datos de usuario
El mismo componente WebSocket también se suscribe a tu stream privado de datos de usuario — actualizaciones de cuenta, eventos de órdenes, cambios de posición. Es así como concilias fills que ocurrieron fuera de tu bot (cancelación manual desde la web UI, liquidación, etc.).
oBinance.AuthOptions.ApiKey := FConfig.ApiKey;
oBinance.AuthOptions.ApiSecret := FConfig.ApiSecret;
oBinance.Streams.Add('!userData');
procedure TForm1.DoStream(Sender: TObject; const aStream, aData: string);
var
oJSON: TsgcJSONObject;
begin
if aStream = '!userData' then
begin
oJSON := TsgcJSONObject.Parse(aData);
try
if oJSON.S['e'] = 'executionReport' then
FRisk.ReconcileExternalFill(oJSON);
finally
oJSON.Free;
end;
end;
end;
Una nota sobre backtesting
Nada de lo de arriba responde a "¿es esta estrategia realmente rentable?". Para eso está el backtesting — reproducir la misma estrategia contra datos históricos para estimar su rendimiento futuro. La arquitectura de arriba lo hace casi gratis: al hilo de estrategia no le importa si los eventos de mercado vienen de un WebSocket en vivo o de un lector CSV. Construye una fuente sintética de eventos que lea klines de disco y los alimente a la misma cola, y tu código de estrategia corre sin cambios contra años de datos históricos.
Dos pitfalls que evitar. Look-ahead bias: no dejes que la estrategia espíe ningún punto de dato que no habría estado disponible en el timestamp que está procesando. Y survivorship bias: entrena y testea con el universo de símbolos que existían en su momento, no con la lista curada de símbolos "exitosos" que sobrevivieron hasta hoy. Ambos han matado más estrategias en producción que todos los bugs de código juntos.
Checklist operativo
| Preocupación | Dónde manejarla |
| Reconexión en caída de Wi-Fi | WatchDog.Enabled := True |
| Detección de enlace muerto | HeartBeat.Enabled := True |
| Sincronía de tiempo (Binance rechaza firmas desfasadas) | NTP en el SO, más una llamada diaria al endpoint de server time |
| Idempotencia de órdenes | Usa newClientOrderId en cada orden |
| Límites de tasa | Trackea cabeceras; haz back off cuando estés al 90% del límite |
| Kill switch | Un único booleano, conmutado desde UI o un proceso watchdog |
| Log de auditoría | Cada intención, cada fill, cada rechazo, append-only |
Más allá de Binance
Cambia TsgcWSAPI_Binance por TsgcWSAPI_Coinbase, TsgcWSAPI_Kraken o cualquier otro de los 20+ componentes de exchange. La estrategia, puerta de riesgo y worker de órdenes no cambian — sólo cambian la configuración de credenciales y la nomenclatura de símbolos. Para un trader multi-exchange de nivel producción con charts, gestión de posición y UI de routing de órdenes de fábrica, mira el sample sgcTrader.
Los sistemas reales multi-exchange añaden una capa más por encima de lo que viste aquí: un normalizador de símbolos. Binance lo llama BTCUSDT, Coinbase lo llama BTC-USD, Kraken lo llama XBT/USD. Construye un modelo interno de símbolos con un nombre canónico y alias por exchange, y traduce en el límite de la API. Cinco minutos de trabajo por adelantado, infinitos bugs ahorrados aguas abajo.
La otra cosa a añadir para operaciones multi-exchange es una comprobación de clock-skew al arrancar. Binance, Coinbase y los demás rechazan peticiones firmadas con un timestamp con más de 1000 ms de desfase respecto a su servidor. NTP normalmente te mantiene bien dentro de eso, pero un VPS mal configurado puede derivar segundos en una hora. Consulta el endpoint de server time al arrancar, loguea el offset, niégate a tradear si está >500 ms.
¿Por qué Delphi para esto?
"¿Por qué no escribirlo en Python?" es la pregunta obvia. Tres respuestas desde producción. Primero, el warm-up del JIT y el GIL hacen que CPython encaje mal para event loops de baja latencia — la misma estrategia que alcanza 0,8 ms de latencia mediana en Delphi tarda 6 ms en CPython sin esfuerzo serio. Segundo, la historia de despliegue es más simple: un exe firmado vs un virtualenv con un centenar de wheels, la mitad de las cuales requieren un compilador C en tiempo de instalación. Tercero, el back office existente está en Delphi. Reutilizar esas clases — ledger de cuenta, calculadora P&L, journal, log de auditoría — en el nuevo bot en lugar de reimplementarlas en otro lenguaje elimina toda una categoría de bugs de conciliación.
Para investigación pura y backtests estilo notebook, Python gana fácilmente — el ecosistema de pandas, statsmodels, vectorbt y amigos es inigualable. El reparto que funciona para la mayoría de tiendas: investigación en Python, producción en Delphi. Exporta la lógica de la estrategia como una pequeña máquina de estados, pórtala una vez, ejecútala sobre un runtime Delphi probado en batalla. Las dos mitades no tienen que compartir lenguaje para compartir resultados.
Qué leer a continuación
Si planeas correr esto 24/7 en un VPS, lee a continuación Ajuste de rendimiento. Para evitar los pitfalls más comunes, mira 10 errores comunes. Y si todavía no has instalado sgcWebSockets, el hub de Primeros Pasos te pone en marcha en cinco minutos.
Aviso: la estrategia de este post es con fines educativos. Operar con criptomonedas implica un riesgo sustancial. No despliegues código sin probar con capital real.