Saltar a contenido

Conceptos básicos de Python para operaciones algorítmicas

Los operadores utilizan Python para crear robots de operaciones, indicadores técnicos, plugins y otras herramientas para ayudar en las operaciones algorítmicas o manuales, y este artículo explica los conceptos básicos de programación en Python.

Python es un lenguaje de programación popular, de alto nivel y de propósito general utilizado para desarrollo web, ciencia de datos, automatización y, más recientemente, operaciones algorítmicas en cTrader. Ya sea que esté creando robots de operaciones simples basados en señales o estrategias complejas con múltiples indicadores, Python hace que el proceso sea más rápido, más eficiente y más fácil de gestionar.

¡Algoritmos en Python en un minuto!

  • Experimente el soporte integrado de Python y el acceso perfecto a la API de cTrader Algo, siendo cTrader la única plataforma importante que ofrece integración nativa de fábrica. No se requieren plugins, adaptadores ni configuraciones complejas.
  • Concéntrese más en lo que su estrategia debe lograr, ya que la lógica se vuelve más fácil de ensamblar. Disfrute de una iteración rápida, componentes reutilizables y rendimiento escalable.
  • Desarrolle rápidamente, depure de manera más inteligente y colabore sin barreras. Su código siempre será adaptable, confiable y mantenible.
  • Acceda y benefíciese de miles de bibliotecas gratuitas/de código abierto, tutoriales detallados, ejemplos de código y un fuerte apoyo de la comunidad en todo el mundo.

Variables y tipos de datos

En Python, las variables son de tipo dinámico, lo que significa que no tiene que declarar sus tipos de datos explícitamente. El intérprete determina el tipo en tiempo de ejecución según el valor asignado.

Crea variables simplemente asignando un valor a una:

1
2
3
4
price = 105.50         # float
enabled = True         # boolean
symbol = "EURUSD"      # string
orders = []            # empty list

Tipos de datos comunes

Tipo Descripción Ejemplo
int Números enteros 5, 100, api.StepPips
float Números decimales 1.1234, 100.5, api.Symbol.Ask
bool Valores booleanos True, False
str Cadenas "EURUSD", api.Label
list Colección de elementos [1, 2, 3], [p for p in api.Positions]

En los algoritmos de cTrader, las variables almacenan datos importantes relacionados con la información del mercado, las decisiones de operación y la configuración del cBot. Estas variables a menudo representan volúmenes de operación calculados, valores de indicadores, indicadores booleanos, nombres de símbolos, etiquetas y más.

Aquí hay un ejemplo:

1
2
3
4
class GridcBot():
    def on_start(self):
        self.volumeInUnits = api.Symbol.QuantityToVolumeInUnits(api.VolumeInLots)
        self.enoughMoney = True

En este caso, self.volumeInUnits es un float utilizado para colocar órdenes, mientras que self.enoughMoney es un bool que controla si se continúa abriendo posiciones en una estrategia de cuadrícula.

Clases y objetos

Python es un lenguaje de programación orientado a objetos (POO), que proporciona herramientas para organizar el código a través de clases y objetos que modelan entidades y comportamientos del mundo real. Una clase define un plano para crear objetos, que son instancias de esa clase con sus propios datos y comportamientos únicos. Los conceptos clave se describen a continuación:

  • Una clase se define usando la palabra clave class.
  • El método __init__ (constructor) se utiliza comúnmente para inicializar el estado del objeto.
  • Los métodos de instancia definen el comportamiento.
  • Los atributos contienen datos específicos de cada objeto.

Por ejemplo, puede crear una clase Car:

1
2
3
4
5
6
7
class Car:
    def __init__(self, color, speed):
        self.color = color
        self.speed = speed

    def accelerate(self):
        self.speed += 10

Luego puede crear objetos de la clase Car e interactuar con ellos:

1
2
3
my_car = Car("red", 50)
my_car.accelerate()
print(my_car.speed)  # This returns 60

En el contexto de cTrader

En cTrader, los cBots, indicadores y plugins se implementan como clases que encapsulan toda su lógica. Los algoritmos interactúan con la plataforma cTrader utilizando un objeto api especial que proporciona acceso a símbolos, gráficos, órdenes, indicadores y más.

Puede crear una clase SimplecBot en cTrader de esta manera:

1
2
3
class SimplecBot():
    def on_start(self):
        api.Print("cBot started")

La clase SimplecBot incluye un único método (on_start(self)) que se ejecuta una vez cuando el bot de operaciones se inicia. La función api.Print escribe un mensaje en el registro, demostrando cómo los métodos de objeto encapsulan el comportamiento de operación.

Cada instancia de una clase representa efectivamente un algoritmo de operación autónomo que gestiona su propio ciclo de vida y decisiones de operación utilizando principios de POO. En un cBot estándar de cTrader, una clase podría almacenar datos de estado interno, como volumen, resultados de indicadores, etc., y utilizar varios métodos para gestionar la lógica de operación.

Considere este ejemplo de cruce MACD que inicializa el indicador MACD al iniciarse y coloca una orden de compra cuando ocurre una señal de cruce MACD:

1
2
3
4
5
6
7
class MACDCrossover():
    def on_start(self):
        self.macd = api.Indicators.MacdCrossOver(api.Source, api.LongCycle, api.ShortCycle, api.SignalPeriods)

    def on_bar_closed(self):
        if self.macd.MACD.Last(0) > self.macd.Signal.Last(0):
            api.ExecuteMarketOrder(TradeType.Buy, api.SymbolName, volume)

La palabra clave self

Dentro de una clase, self se refiere a la instancia de la clase y proporciona acceso a sus variables y métodos. self se utiliza para:

  • Almacenar datos como resultados de indicadores, volúmenes de posición y valores de configuración.
  • Rastrear el estado interno a través de diferentes llamadas a métodos como on_start(), on_tick() o on_bar_closed().

Este ejemplo demuestra cómo se utiliza self:

1
2
3
4
class MACDCrossover():
    def on_start(self):
        self.volume = api.Symbol.QuantityToVolumeInUnits(api.VolumeInLots)
        self.macd = api.Indicators.MacdCrossOver(api.Source, api.LongCycle, api.ShortCycle, api.SignalPeriods)

En este caso, self.volume almacena el volumen calculado para usar en las operaciones, mientras que self.macd almacena el objeto indicador MACD, permitiendo el acceso a sus valores a lo largo del ciclo de vida del cBot. Sin self, estas variables serían locales a on_start() e inaccesibles en on_bar_closed() donde se pueden tomar decisiones de operación.

Métodos y funciones

Las funciones son bloques de código reutilizables utilizados para realizar una tarea específica. Cuando las funciones se definen dentro de una clase, se llaman métodos, y operan sobre el estado interno de la clase a través del parámetro self. En el contexto de operaciones algorítmicas con cTrader, los métodos definen cómo se comporta su algoritmo de operación bajo diferentes condiciones, como ticks de mercado, cierres de barras o inicialización.

La plataforma llama automáticamente a métodos especiales (conocidos como manejadores de eventos) en los momentos apropiados, como:

on_start(self) – llamado una vez cuando se inicializa el bot.

1
2
def on_start(self):
    api.Print("cBot started")

on_tick(self) – llamado en cada tick de mercado entrante.

1
2
def on_tick(self):
    api.Print("Tick received")

on_bar_closed(self) – llamado cuando se cierra una nueva vela/barra (ideal para estrategias basadas en tiempo).

1
2
3
4
def on_bar_closed(self):
    if Functions.HasCrossedAbove(...):
        self.close_positions(TradeType.Sell)
        api.ExecuteMarketOrder(...)

Métodos personalizados o auxiliares

Para evitar reescribir y duplicar código, puede definir sus propios métodos. Estos métodos personalizados deben encapsular la lógica común que su robot de operaciones puede reutilizar.

El método a continuación devuelve todas las posiciones abiertas asociadas con la etiqueta del cBot actual, facilitando el seguimiento o cierre de posiciones de manera consistente.

1
2
def get_bot_positions(self):
    return api.Positions.FindAll(api.Label)

Considere otro método que calcula la distancia en pips entre el precio actual y la entrada de la posición, lo cual es crítico para las decisiones en estrategias basadas en cuadrícula.

1
2
def get_distance_in_pips(self, position):
    return (position.EntryPrice - api.Symbol.Ask if position.TradeType == TradeType.Buy else api.Symbol.Bid - position.EntryPrice) / api.Symbol.PipSize

Una vez que se ha definido un método, puede llamarlo desde dentro de la misma clase utilizando la palabra clave self. Este enfoque le permite reutilizar la lógica en cualquier parte de su bot sin duplicar código.

Por ejemplo, después de definir get_bot_positions(), podría llamarlo dentro de otro método para cerrar todas las posiciones:

1
2
3
4
def close_positions(self, tradeType):
    for position in self.get_bot_positions():   # Helper method call
        if position.TradeType == tradeType:
            api.ClosePosition(position)

Aquí, self.get_bot_positions() invoca el método auxiliar, devolviendo solo las posiciones vinculadas a la etiqueta del bot actual.

De manera similar, el método get_distance_in_pips() puede ser reutilizado para tomar decisiones de operación. Por ejemplo, en una estrategia de cuadrícula, es posible que desee abrir una nueva orden solo si el mercado se ha movido lo suficiente desde la última posición:

1
2
3
4
5
6
7
def on_tick(self):
    grid_positions = self.get_bot_positions()
    if len(grid_positions) > 0:
        position_with_highest_pips = sorted(grid_positions, key=lambda pos: pos.Pips, reverse=True)[0]
        distance = self.get_distance_in_pips(position_with_highest_pips)  # Helper method call
        if distance >= api.StepPips:
            self.open_position()

Otros conceptos

Espacios de nombres e importaciones

Los espacios de nombres en Python ayudan a organizar el código y evitar colisiones de nombres agrupando identificadores relacionados (variables, funciones, clases). Las importaciones le permiten incorporar módulos externos, bibliotecas o paquetes para extender la funcionalidad más allá de las características estándar de Python.

En Python, puede importar módulos estándar o paquetes personalizados:

1
2
import math
print(math.sqrt(16))  # 4.0

También puede importar funciones o clases específicas:

1
2
from math import sqrt
print(sqrt(25))  # 5.0

Al escribir algoritmos para cTrader en Python, las importaciones funcionan un poco diferente porque cTrader se integra con la API de cTrader Algo basada en .NET. Esta configuración implica un puente que permite que el código Python interactúe con ensamblados .NET, y esto explica por qué un cBot típico comienza con:

1
2
3
4
5
6
7
8
9
import clr

clr.AddReference("cAlgo.API")

# Import cAlgo API types
from cAlgo.API import *

# Import trading wrapper functions
from robot_wrapper import *

Donde:

  • import clr carga el Common Language Runtime (CLR), permitiendo que Python interopere con bibliotecas .NET.
  • clr.AddReference("cAlgo.API") hace referencia al ensamblado de la API de cAlgo, que contiene todas las clases, métodos y objetos relacionados con las operaciones.
  • from cAlgo.API import * importa todos los tipos de la API (por ejemplo, TradeType, Color, Position).
  • from robot_wrapper import * carga funciones auxiliares y envoltorios que simplifican las operaciones de trading comunes.

El sistema de espacios de nombres asegura que:

  • Los nombres incorporados de Python (como print) eviten conflictos con los nombres de cTrader (como api.Print).
  • Todos los objetos específicos de trading (api.Symbol, api.Indicators, api.ExecuteMarketOrder) provienen del espacio de nombres de la API de cTrader Algo, manteniéndolos organizados y distintos.
  • Puede extender la funcionalidad importando sus propios paquetes personalizados para trabajar junto con los tipos incorporados de cTrader.

Bucles

Los bucles le permiten repetir operaciones sobre una secuencia de valores o hasta que se cumpla una condición. En las operaciones algorítmicas, los bucles se utilizan a menudo para procesar posiciones (por ejemplo, cerrarlas, filtrarlas o actualizarlas), verificar condiciones en múltiples activos o indicadores, implementar estrategias de cuadrícula o martingala donde se requiere la colocación repetida de órdenes y más.

En Python, la iteración es sencilla y admite tanto bucles for (para iterar a través de colecciones) como bucles while (para repetir hasta que cambie una condición).

Puede escribir un bucle para cerrar posiciones:

1
2
3
4
def close_positions(self, tradeType):
    for position in self.get_bot_positions():
        if position.TradeType == tradeType:
            api.ClosePosition(position)

En este caso:

  • self.get_bot_positions() devuelve todas las posiciones pertenecientes al cBot actual.
  • El bucle for recorre cada posición.
  • Las posiciones coincidentes se cierran con api.ClosePosition.

Considere un bucle diferente que itera sobre todas las posiciones de cuadrícula abiertas, las cierra una por una y verifica recursivamente si quedan posiciones (seguridad en caso de cierres parciales).

1
2
3
4
5
6
def close_grid_positions(self):
    for position in self.get_grid_positions():
        position.Close()

    if len(self.get_grid_positions()) > 0:
        self.close_grid_positions()

Declaraciones condicionales

Las declaraciones condicionales controlan el flujo de ejecución en Python al permitir que su algoritmo tome decisiones basadas en expresiones lógicas. Los condicionales son la columna vertebral de la lógica de estrategia, determinando cuándo entrar, salir o gestionar posiciones.

Python admite tres construcciones condicionales principales:

  • if – ejecuta código cuando la condición es verdadera.
  • elif (else if) – verifica otra condición si la primera era falsa.
  • else – proporciona una alternativa cuando no se cumplen las condiciones.

Considere las condiciones de cruce MACD expresadas en este código:

1
2
3
4
if self.macd.MACD.Last(0) > self.macd.Signal.Last(0):
    api.ExecuteMarketOrder(TradeType.Buy, api.SymbolName, self.volumeInUnits, api.Label, api.StopLossInPips, api.TakeProfitInPips)
elif self.macd.MACD.Last(0) < self.macd.Signal.Last(0):
    api.ExecuteMarketOrder(TradeType.Sell, api.SymbolName, self.volumeInUnits, api.Label, api.StopLossInPips, api.TakeProfitInPips)

Esta estructura:

  • Coloca una orden de compra cuando la línea MACD está por encima de la línea de señal.
  • Coloca una orden de venta cuando la línea MACD está por debajo de la línea de señal.
  • Omite la operación si no hay una señal clara.

Listas

Python proporciona herramientas poderosas y concisas para trabajar con colecciones de datos, como operaciones, precios o indicadores. Dos de las más comunes son las comprensiones de lista y las funciones de agregación incorporadas como sum().

Por ejemplo, puede escribir un código que itera sobre numbers y mantiene solo los números pares de esta manera:

1
2
numbers = [1, 2, 3, 4, 5]
squares = [n**2 for n in numbers if n % 2 == 0]

Puede usar una comprensión de lista para seleccionar solo las posiciones del símbolo actual, produciendo una lista filtrada que es útil en entornos multi-símbolo.

1
positions = [p for p in api.Positions if p.SymbolName == api.SymbolName]

Agregar ganancias o volumen usando sum() es un enfoque estándar en estrategias de operación en cuadrícula para determinar cuándo la ganancia total alcanza el objetivo.

1
net_profit_sum = sum([p.NetProfit for p in grid_positions])

Puede combinar el filtrado y la agregación de esta manera:

1
2
buy_positions = [p for p in api.Positions if p.TradeType == TradeType.Buy]
avg_buy_price = sum([p.EntryPrice * p.VolumeInUnits for p in buy_positions]) / sum([p.VolumeInUnits for p in buy_positions])

Operaciones síncronas y asíncronas

Python sigue un modelo de ejecución síncrona por defecto. Los métodos del ciclo de vida de su algoritmo, como on_start, on_tick y on_bar_closed, son activados por el bucle de eventos de la plataforma, uno a la vez.

Aquí hay una representación básica de un flujo de operación síncrono:

graph TD
    A(Encontrar una señal) ==> B(Ejecutar una orden)
    B ==> C(La posición alcanza un TP/SL)
    C ==> D(Cerrar la posición)
    D ==> A

En flujos síncronos, cualquier operación de larga duración puede bloquear el hilo, potencialmente retrasando el siguiente evento. Sin embargo, en estrategias del mundo real, los algoritmos típicamente inician acciones no bloqueantes, como enviar una orden o establecer stop-loss y take-profit, luego esperan eventos futuros (como un nuevo tick o barra) para manejar los resultados. Esta configuración resulta en una orquestación similar a la asíncrona, aunque no se utilizan las construcciones async/await de Python.

Nota

Para la mayoría de los algoritmos de operación, la programación asíncrona explícita es innecesaria.

cTrader gestiona la concurrencia serializando sus devoluciones de llamada basadas en eventos. Cada método se invoca independientemente y nunca en paralelo, eliminando la necesidad de salvaguardas de multihilo. Este diseño permite que las estrategias respondan a múltiples condiciones de operación a la vez, en un sentido lógico, sin bloquear.

En el ejemplo a continuación, la estrategia evalúa señales de compra y venta independientemente y responde en consecuencia, sin ninguna superposición en el contexto de ejecución.

graph TD 
    A([Encontrar una señal]) ==> B([Abrir una orden de compra]) & C([Abrir una orden de venta <br>para cobertura]) ==> D([Una orden alcanza su<br>take profit o stop loss])
    D ==> E([Cerrar la posición])
    E ==> A

Incluso cuando las decisiones parezcan ser paralelas, se manejan secuencialmente mediante el sistema de colas interno de cTrader. La arquitectura de la plataforma busca prevenir problemas de concurrencia mientras mantiene los robots de operaciones receptivos a los cambios del mercado.

Image title