Vai al contenuto

Nozioni di base di Python per il trading algoritmico

I trader utilizzano Python per creare bot di trading, indicatori tecnici, plugin e altri strumenti per facilitare il trading algoritmico o le operazioni manuali, e questo articolo spiega le nozioni di base della programmazione in Python.

Python è un linguaggio di programmazione popolare, di alto livello e per scopi generali utilizzato per lo sviluppo web, la scienza dei dati, l'automazione e, più recentemente, il trading algoritmico in cTrader. Che tu stia creando semplici bot di trading basati su segnali o strategie complesse con più indicatori, Python rende il processo più veloce, più efficiente e più facile da gestire.

Algoritmi Python in un minuto!

  • Sperimenta il supporto integrato di Python e l'accesso perfetto all'API cTrader Algo, con cTrader che è l'unica piattaforma importante a offrire un'integrazione nativa pronta all'uso. Nessun plugin, adattatore o configurazione complessa richiesta.
  • Concentrati maggiormente su ciò che la tua strategia dovrebbe ottenere poiché la logica diventa più facile da assemblare. Goditi iterazioni rapide, componenti riutilizzabili e prestazioni scalabili.
  • Sviluppa rapidamente, esegui il debug in modo più intelligente e collabora senza barriere. Il tuo codice sarà sempre adattabile, affidabile e manutenibile.
  • Accedi e approfitta di migliaia di librerie gratuite/open-source, tutorial approfonditi, esempi di codice e un forte supporto della community in tutto il mondo.

Variabili e tipi di dati

In Python, le variabili sono tipizzate dinamicamente, il che significa che non è necessario dichiarare esplicitamente i loro tipi di dati. L'interprete determina il tipo in fase di esecuzione in base al valore assegnato.

Crei le variabili semplicemente assegnando loro un valore:

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

Tipi di dati comuni

Tipo Descrizione Esempio
int Numeri interi 5, 100, api.StepPips
float Numeri decimali 1.1234, 100.5, api.Symbol.Ask
bool Valori booleani True, False
str Stringhe "EURUSD", api.Label
list Raccolta di elementi [1, 2, 3], [p for p in api.Positions]

Negli algoritmi di cTrader, le variabili memorizzano dati importanti relativi alle informazioni di mercato, alle decisioni di trading e alla configurazione dei cBot. Queste variabili spesso rappresentano volumi di trading calcolati, valori degli indicatori, flag booleani, nomi di simboli, etichette e altro ancora.

Ecco un esempio:

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

In questo caso, self.volumeInUnits è un float utilizzato per piazzare ordini, mentre self.enoughMoney è un bool che controlla se continuare ad aprire posizioni in una strategia grid.

Classi e oggetti

Python è un linguaggio di programmazione orientato agli oggetti (OOP), che fornisce strumenti per organizzare il codice attraverso classi e oggetti che modellano entità e comportamenti del mondo reale. Una classe definisce un modello per la creazione di oggetti, che sono istanze di quella classe con i propri dati e comportamenti unici. I concetti chiave sono delineati di seguito:

  • Una classe viene definita utilizzando la parola chiave class.
  • Il metodo __init__ (costruttore) viene comunemente utilizzato per inizializzare lo stato dell'oggetto.
  • I metodi di istanza definiscono il comportamento.
  • Gli attributi contengono dati specifici per ogni oggetto.

Ad esempio, puoi creare una classe 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

Quindi puoi creare oggetti della classe Car e interagire con essi:

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

Nel contesto di cTrader

In cTrader, i cBot, gli indicatori e i plugin sono implementati come classi che incapsulano la loro intera logica. Gli algoritmi interagiscono con la piattaforma cTrader utilizzando un oggetto speciale api che fornisce accesso a simboli, grafici, ordini, indicatori e altro ancora.

Puoi creare una classe SimplecBot in cTrader in questo modo:

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

La classe SimplecBot include un singolo metodo (on_start(self)) che viene eseguito una volta all'avvio del bot di trading. La funzione api.Print scrive un messaggio nel log, dimostrando come i metodi degli oggetti incapsulano il comportamento di trading.

Ogni istanza di una classe rappresenta effettivamente un algoritmo di trading autonomo che gestisce il proprio ciclo di vita e le proprie decisioni di trading utilizzando i principi OOP. In un cBot cTrader standard, una classe potrebbe memorizzare dati di stato interni, come volume, risultati degli indicatori, ecc., e utilizzare diversi metodi per gestire la logica di trading.

Considera questo esempio di MACD crossover che inizializza l'indicatore MACD all'avvio e piazza un ordine di acquisto quando si verifica un segnale di crossover 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 parola chiave self

All'interno di una classe, self si riferisce all'istanza della classe e fornisce accesso alle sue variabili e ai suoi metodi. self viene utilizzato per:

  • Memorizzare dati come i risultati degli indicatori, i volumi delle posizioni e i valori di configurazione.
  • Tracciare lo stato interno attraverso diverse chiamate di metodo come on_start(), on_tick() o on_bar_closed().

Questo esempio dimostra come viene utilizzato 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)

In questo caso, self.volume memorizza il volume calcolato da utilizzare per le operazioni, mentre self.macd memorizza l'oggetto indicatore MACD, consentendo l'accesso ai suoi valori durante l'intero ciclo di vita del cBot. Senza self, queste variabili sarebbero locali a on_start() e inaccessibili in on_bar_closed() dove potrebbero essere prese decisioni di trading.

Metodi e funzioni

Le funzioni sono blocchi di codice riutilizzabile utilizzati per eseguire un'attività specifica. Quando le funzioni sono definite all'interno di una classe, vengono chiamate metodi e operano sullo stato interno della classe tramite il parametro self. Nel contesto del trading algoritmico con cTrader, i metodi definiscono come si comporta il tuo algoritmo di trading in diverse condizioni, come tick di mercato, chiusure di barre o inizializzazione.

La piattaforma chiama automaticamente metodi speciali (noti come gestori di eventi) nei momenti appropriati, come:

on_start(self) – chiamato una volta quando il bot viene inizializzato.

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

on_tick(self) – chiamato ad ogni tick di mercato in arrivo.

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

on_bar_closed(self) – chiamato quando una nuova candela/barra si chiude (ideale per strategie basate sul tempo).

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

Metodi personalizzati o di supporto

Per evitare di riscrivere e duplicare il codice, puoi definire i tuoi metodi. Tali metodi personalizzati dovrebbero incapsulare la logica comune che il tuo robot di trading può riutilizzare.

Il metodo seguente restituisce tutte le posizioni aperte associate all'etichetta del cBot corrente, rendendo facile tracciare o chiudere le posizioni in modo coerente.

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

Considera un altro metodo che calcola la distanza in pip tra il prezzo attuale e l'entrata della posizione, che è fondamentale per le decisioni nelle strategie basate su griglia.

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 volta definito un metodo, puoi chiamarlo dall'interno della stessa classe utilizzando la parola chiave self. Questo approccio ti consente di riutilizzare la logica ovunque nel tuo bot senza duplicare il codice.

Ad esempio, dopo aver definito get_bot_positions(), potresti chiamarlo all'interno di un altro metodo per chiudere tutte le posizioni:

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)

Qui, self.get_bot_positions() invoca il metodo di supporto, restituendo solo le posizioni legate all'etichetta del bot corrente.

Allo stesso modo, il metodo get_distance_in_pips() può essere riutilizzato per prendere decisioni di trading. Ad esempio, in una strategia a griglia, potresti voler aprire un nuovo ordine solo se il mercato si è spostato abbastanza lontano dall'ultima posizione:

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()

Altri concetti

Namespace e importazioni

I namespace in Python aiutano a organizzare il codice ed evitare collisioni di nomi raggruppando identificatori correlati (variabili, funzioni, classi). Le importazioni ti consentono di importare moduli, librerie o pacchetti esterni per estendere le funzionalità oltre le caratteristiche standard di Python.

In Python, puoi importare moduli standard o pacchetti personalizzati:

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

Puoi anche importare funzioni o classi specifiche:

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

Quando scrivi algoritmi per cTrader in Python, le importazioni funzionano in modo leggermente diverso perché cTrader si integra con l'API cTrader Algo basata su .NET. Questa configurazione coinvolge un bridge che consente al codice Python di interagire con gli assembly .NET, e questo spiega perché un tipico cBot inizia 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 *

Dove:

  • import clr carica il Common Language Runtime (CLR), consentendo a Python di interoperare con le librerie .NET.
  • clr.AddReference("cAlgo.API") fa riferimento all'assembly dell'API cAlgo, che contiene tutte le classi, i metodi e gli oggetti relativi al trading.
  • from cAlgo.API import * importa tutti i tipi dall'API (ad es., TradeType, Color, Position).
  • from robot_wrapper import * carica funzioni di supporto e wrapper che semplificano le operazioni di trading comuni.

Il sistema di namespace garantisce che:

  • I nomi integrati di Python (come print) evitino conflitti con i nomi di cTrader (come api.Print).
  • Tutti gli oggetti specifici del trading (api.Symbol, api.Indicators, api.ExecuteMarketOrder) provengano dal namespace dell'API cTrader Algo, mantenendoli organizzati e distinti.
  • Puoi estendere le funzionalità importando i tuoi pacchetti personalizzati per lavorare insieme ai tipi integrati di cTrader.

Cicli

I cicli ti consentono di ripetere operazioni su una sequenza di valori o fino a quando non viene soddisfatta una condizione. Nel trading algoritmico, i cicli vengono spesso utilizzati per elaborare posizioni (ad es., chiudere, filtrare o aggiornarle), verificare condizioni su più asset o indicatori, implementare strategie a griglia o martingala dove è richiesto il piazzamento ripetuto di ordini e altro ancora.

In Python, l'iterazione è semplice e supporta sia i cicli for (per iterare attraverso le collezioni) che i cicli while (per ripetere fino a quando una condizione cambia).

Puoi scrivere un ciclo per chiudere le posizioni:

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

In questo caso:

  • self.get_bot_positions() restituisce tutte le posizioni appartenenti al cBot corrente.
  • Il ciclo for passa attraverso ogni posizione.
  • Le posizioni corrispondenti vengono chiuse con api.ClosePosition.

Considera un ciclo diverso che itera su tutte le posizioni a griglia aperte, le chiude una per una e verifica ricorsivamente se rimangono posizioni (sicurezza in caso di chiusure parziali).

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()

Istruzioni condizionali

Le istruzioni condizionali controllano il flusso di esecuzione in Python consentendo al tuo algoritmo di prendere decisioni basate su espressioni logiche. Le condizionali sono la spina dorsale della logica di strategia, determinando quando entrare, uscire o gestire le posizioni.

Python supporta tre costrutti condizionali principali:

  • if – esegue il codice quando la condizione è vera.
  • elif (else if) – verifica un'altra condizione se la prima era falsa.
  • else – fornisce un'alternativa quando nessuna condizione è soddisfatta.

Considera le condizioni di crossover MACD espresse in questo codice:

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)

Questa struttura:

  • Piazza un ordine di acquisto quando la linea MACD è sopra la linea del segnale.
  • Piazza un ordine di vendita quando la linea MACD è sotto la linea del segnale.
  • Salta il trading se non c'è un segnale chiaro.

Liste

Python fornisce strumenti potenti e concisi per lavorare con collezioni di dati, come operazioni, prezzi o indicatori. Due dei più comuni sono le list comprehension e le funzioni di aggregazione integrate come sum().

Ad esempio, puoi scrivere un codice che itera su numbers e mantiene solo i numeri pari in questo modo:

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

Puoi utilizzare una list comprehension per selezionare solo le posizioni per il simbolo corrente, producendo un elenco filtrato che è utile in ambienti multi-simbolo.

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

Aggregare profitto o volume utilizzando sum() è un approccio standard nelle strategie di trading a griglia per determinare quando il profitto totale raggiunge l'obiettivo.

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

Puoi combinare filtraggio e aggregazione in questo modo:

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])

Operazioni sincrone e asincrone

Python segue un modello di esecuzione sincrono per impostazione predefinita. I metodi del ciclo di vita del tuo algoritmo, come on_start, on_tick e on_bar_closed, vengono attivati dal ciclo di eventi della piattaforma, uno alla volta.

Ecco una rappresentazione di base di un flusso di trading sincrono:

graph TD
    A(Rilevare<br>un segnale) ==> B(Eseguire un ordine)
    B ==> C(La posizione<br>raggiunge TP/SL)
    C ==> D(Chiudere la<br>posizione)
    D ==> A

Nei flussi sincroni, qualsiasi operazione di lunga durata può bloccare il thread, ritardando potenzialmente l'evento successivo. Tuttavia, nelle strategie del mondo reale, gli algoritmi tipicamente avviano azioni non bloccanti, come l'invio di un ordine o l'impostazione di stop-loss e take-profit, quindi attendono eventi futuri (come un nuovo tick o barra) per gestire i risultati. Questa configurazione risulta in un'orchestrazione simile all'asincrona, anche se i costrutti async/await di Python non vengono utilizzati.

Nota

Per la maggior parte degli algoritmi di trading, la programmazione asincrona esplicita non è necessaria.

cTrader gestisce la concorrenza serializzando i tuoi callback basati su eventi. Ogni metodo viene invocato indipendentemente e mai in parallelo, eliminando la necessità di protezioni per il multithreading. Questo design consente alle strategie di rispondere a più condizioni di trading contemporaneamente, in senso logico, senza blocchi.

Nell'esempio seguente, la strategia valuta i segnali di acquisto e vendita in modo indipendente e risponde di conseguenza, senza sovrapposizioni nel contesto di esecuzione.

graph TD
    A([Rilevare<br>un segnale]) ==> B([Aprire un ordine<br>di acquisto]) & C([Aprire un<br>ordine di<br>vendita di<br>copertura]) ==> D([Un ordine<br>raggiunge<br>il take profit o<br>lo stop loss])
    D ==> E([Chiudere la<br>posizione])
    E ==> A

Anche se le decisioni possono sembrare parallele, vengono gestite in sequenza dal sistema di accodamento interno di cTrader. L'architettura della piattaforma mira a prevenire problemi di concorrenza mantenendo i robot di trading reattivi ai cambiamenti del mercato.

Image title