Przejdź do treści

Podstawy języka Python do handlu algorytmicznego

Traderzy używają języka Python do budowania botów handlowych, wskaźników technicznych, wtyczek i innych narzędzi wspomagających handel algorytmiczny lub operacje manualne, a ten artykuł wyjaśnia podstawy kodowania w języku Python.

Python to popularny, wysokopoziomowy, ogólnego przeznaczenia język programowania używany do tworzenia stron internetowych, nauki o danych, automatyzacji, a ostatnio także do handlu algorytmicznego w cTrader. Niezależnie od tego, czy tworzysz proste boty handlowe oparte na sygnałach, czy złożone strategie wielowskaźnikowe, Python sprawia, że proces jest szybszy, bardziej wydajny i łatwiejszy w zarządzaniu.

Algorytmy w języku Python w jedną minutę!

  • Korzystaj z wbudowanego wsparcia języka Python i bezproblemowego dostępu do cTrader Algo API, przy czym cTrader jest jedyną dużą platformą oferującą natywną integrację od razu po instalacji. Nie są wymagane żadne wtyczki, adaptery ani skomplikowane konfiguracje.
  • Skup się bardziej na tym, co Twoja strategia powinna osiągnąć, ponieważ logika staje się łatwiejsza do złożenia. Ciesz się szybką iteracją, komponentami wielokrotnego użytku i skalowalną wydajnością.
  • Rozwijaj szybko, debuguj mądrzej i współpracuj bez barier. Twój kod zawsze będzie adaptowalny, niezawodny i łatwy w utrzymaniu.
  • Uzyskaj dostęp i korzystaj z tysięcy darmowych/open-source bibliotek, szczegółowych samouczków, przykładów kodu i silnego wsparcia społeczności na całym świecie.

Zmienne i typy danych

W języku Python zmienne są dynamicznie typowane, co oznacza, że nie musisz jawnie deklarować ich typów danych. Interpreter określa typ w czasie wykonywania na podstawie przypisanej wartości.

Zmienne tworzy się po prostu przypisując wartość do jednej z nich:

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

Popularne typy danych

Typ Opis Przykład
int Liczby całkowite 5, 100, api.StepPips
float Liczby dziesiętne 1.1234, 100.5, api.Symbol.Ask
bool Wartości logiczne True, False
str Ciągi znaków "EURUSD", api.Label
list Kolekcja elementów [1, 2, 3], [p for p in api.Positions]

W algorytmach cTrader zmienne przechowują ważne dane związane z informacjami rynkowymi, decyzjami handlowymi i konfiguracją cBota. Te zmienne często reprezentują obliczone wolumeny handlowe, wartości wskaźników, flagi logiczne, nazwy symboli, etykiety i więcej.

Oto przykład:

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

W tym przypadku self.volumeInUnits to float używany do składania zleceń, podczas gdy self.enoughMoney to bool kontrolujący, czy kontynuować otwieranie pozycji w strategii gridowej.

Klasy i obiekty

Python to obiektowy język programowania (OOP), zapewniający narzędzia do organizowania kodu poprzez klasy i obiekty, które modelują rzeczywiste encje i zachowania. Klasa definiuje schemat tworzenia obiektów, które są wystąpieniami tej klasy z własnymi unikalnymi danymi i zachowaniami. Kluczowe koncepcje przedstawiono poniżej:

  • Klasa jest definiowana za pomocą słowa kluczowego class.
  • Metoda __init__ (konstruktor) jest powszechnie używana do inicjalizacji stanu obiektu.
  • Metody instancji definiują zachowanie.
  • Atrybuty przechowują dane specyficzne dla każdego obiektu.

Na przykład możesz utworzyć klasę 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

Następnie możesz tworzyć obiekty klasy Car i wchodzić z nimi w interakcje:

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

W kontekście cTrader

W cTrader cBoty, wskaźniki i wtyczki są implementowane jako klasy, które hermetyzują całą swoją logikę. Algorytmy wchodzą w interakcję z platformą cTrader za pomocą specjalnego obiektu api, który zapewnia dostęp do symboli, wykresów, zleceń, wskaźników i więcej.

Możesz utworzyć klasę SimplecBot w cTrader w ten sposób:

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

Klasa SimplecBot zawiera pojedynczą metodę (on_start(self)), która wykonuje się raz, gdy bot handlowy się uruchamia. Funkcja api.Print zapisuje wiadomość do dziennika, demonstrując, jak metody obiektów hermetyzują zachowanie handlowe.

Każde wystąpienie klasy skutecznie reprezentuje samodzielny algorytm handlowy, który zarządza własnym cyklem życia i decyzjami handlowymi przy użyciu zasad OOP. W standardowym cBocie cTrader klasa może przechowywać wewnętrzne dane stanu, takie jak wolumen, wyniki wskaźników itp., i używać kilku metod do zarządzania logiką handlową.

Rozważ ten przykład MACD crossover, który inicjalizuje wskaźnik MACD po uruchomieniu i składa zlecenie kupna, gdy wystąpi sygnał MACD crossover:

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)

Słowo kluczowe self

Wewnątrz klasy self odnosi się do wystąpienia klasy i zapewnia dostęp do jej zmiennych i metod. self jest używane do:

  • Przechowywania danych, takich jak wyniki wskaźników, wolumeny pozycji i wartości konfiguracyjne.
  • Śledzenia stanu wewnętrznego w różnych wywołaniach metod, takich jak on_start(), on_tick() lub on_bar_closed().

Ten przykład demonstruje, jak używane jest 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)

W tym przypadku self.volume przechowuje obliczony wolumen do użycia w transakcjach, podczas gdy self.macd przechowuje obiekt wskaźnika MACD, umożliwiając dostęp do jego wartości przez cały cykl życia cBota. Bez self te zmienne byłyby lokalne dla on_start() i niedostępne w on_bar_closed(), gdzie mogą być podejmowane decyzje handlowe.

Metody i funkcje

Funkcje to bloki kodu wielokrotnego użytku używane do wykonywania określonego zadania. Gdy funkcje są definiowane wewnątrz klasy, nazywane są metodami i działają na wewnętrznym stanie klasy za pomocą parametru self. W kontekście handlu algorytmicznego z cTrader metody definiują, jak Twój algorytm handlowy zachowuje się w różnych warunkach, takich jak tiki rynkowe, zamknięcia słupków lub inicjalizacja.

Platforma automatycznie wywołuje specjalne metody (znane jako procedury obsługi zdarzeń) w odpowiednich momentach, takie jak:

on_start(self) – wywoływana raz, gdy bot jest inicjalizowany.

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

on_tick(self) – wywoływana przy każdym przychodzącym tiku rynkowym.

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

on_bar_closed(self) – wywoływana, gdy zamyka się nowa świeca/słupek (idealna dla strategii opartych na czasie).

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

Metody niestandardowe lub pomocnicze

Aby uniknąć przepisywania i duplikowania kodu, możesz definiować własne metody. Takie metody niestandardowe powinny hermetyzować wspólną logikę, którą Twój robot handlowy może ponownie wykorzystać.

Poniższa metoda zwraca wszystkie otwarte pozycje powiązane z etykietą bieżącego cBota, ułatwiając konsekwentne śledzenie lub zamykanie pozycji.

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

Rozważ inną metodę, która oblicza odległość w pipsach między bieżącą ceną a ceną otwarcia pozycji, co jest kluczowe dla decyzji w strategiach opartych na gridzie.

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

Po zdefiniowaniu metody możesz ją wywołać z poziomu tej samej klasy za pomocą słowa kluczowego self. To podejście pozwala ponownie wykorzystać logikę w dowolnym miejscu w Twoim bocie bez duplikowania kodu.

Na przykład, po zdefiniowaniu get_bot_positions(), możesz wywołać tę funkcję wewnątrz innej metody, aby zamknąć wszystkie pozycje:

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)

Tutaj self.get_bot_positions() wywołuje metodę pomocniczą, zwracając tylko pozycje powiązane z bieżącą etykietą bota.

Podobnie metoda get_distance_in_pips() może być ponownie wykorzystana do podejmowania decyzji handlowych. Na przykład w strategii siatki możesz chcieć otworzyć nowe zlecenie tylko wtedy, gdy rynek przesunął się wystarczająco daleko od ostatniej pozycji:

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

Inne koncepcje

Przestrzenie nazw i importy

Przestrzenie nazw w Pythonie pomagają organizować kod i unikać kolizji nazw poprzez grupowanie powiązanych identyfikatorów (zmiennych, funkcji, klas). Importy pozwalają na wprowadzenie zewnętrznych modułów, bibliotek lub pakietów w celu rozszerzenia funkcjonalności poza standardowe możliwości Pythona.

W Pythonie możesz importować standardowe moduły lub niestandardowe pakiety:

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

Możesz również importować określone funkcje lub klasy:

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

Podczas pisania algorytmów dla cTrader w Pythonie importy działają nieco inaczej, ponieważ cTrader integruje się z opartym na .NET API cTrader Algo. Ta konfiguracja obejmuje mostek, który umożliwia kodowi Pythona interakcję z zestawami .NET, co wyjaśnia, dlaczego typowy cBot zaczyna się od:

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 *

Gdzie:

  • import clr ładuje Common Language Runtime (CLR), umożliwiając Pythonowi współpracę z bibliotekami .NET.
  • clr.AddReference("cAlgo.API") odwołuje się do zestawu cAlgo API, który zawiera wszystkie klasy, metody i obiekty związane z handlem.
  • from cAlgo.API import * wprowadza wszystkie typy z API (np. TradeType, Color, Position).
  • from robot_wrapper import * ładuje funkcje pomocnicze i wrappery, które upraszczają typowe operacje handlowe.

System przestrzeni nazw zapewnia, że:

  • Wbudowane nazwy Pythona (takie jak print) unikają konfliktów z nazwami cTrader (takimi jak api.Print).
  • Wszystkie obiekty specyficzne dla handlu (api.Symbol, api.Indicators, api.ExecuteMarketOrder) pochodzą z przestrzeni nazw cTrader Algo API, utrzymując je zorganizowane i odrębne.
  • Możesz rozszerzyć funkcjonalność poprzez importowanie własnych niestandardowych pakietów, aby działały razem z wbudowanymi typami cTrader.

Pętle

Pętle pozwalają na powtarzanie operacji na sekwencji wartości lub do momentu spełnienia warunku. W handlu algorytmicznym pętle są często używane do przetwarzania pozycji (np. zamykania, filtrowania lub aktualizowania ich), sprawdzania warunków dla wielu aktywów lub wskaźników, implementowania strategii siatki lub martingale, gdzie wymagane jest powtarzalne składanie zleceń, i więcej.

W Pythonie iteracja jest prosta i obsługuje zarówno pętle for (do iterowania przez kolekcje), jak i pętle while (do powtarzania do momentu zmiany warunku).

Możesz napisać pętlę do zamykania pozycji:

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

W tym przypadku:

  • self.get_bot_positions() zwraca wszystkie pozycje należące do bieżącego cBota.
  • Pętla for przechodzi przez każdą pozycję.
  • Pasujące pozycje są zamykane za pomocą api.ClosePosition.

Rozważ inną pętlę, która iteruje po wszystkich otwartych pozycjach siatki, zamyka je jedną po drugiej i rekurencyjnie sprawdza, czy pozostały jakieś pozycje (zabezpieczenie na wypadek częściowych zamknięć).

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

Instrukcje warunkowe

Instrukcje warunkowe kontrolują przepływ wykonania w Pythonie, umożliwiając algorytmowi podejmowanie decyzji na podstawie wyrażeń logicznych. Warunki są podstawą logiki strategii, określając, kiedy wejść, wyjść lub zarządzać pozycjami.

Python obsługuje trzy główne konstrukcje warunkowe:

  • if – wykonuje kod, gdy warunek jest prawdziwy.
  • elif (else if) – sprawdza inny warunek, jeśli pierwszy był fałszywy.
  • else – zapewnia rozwiązanie awaryjne, gdy żaden warunek nie został spełniony.

Rozważ warunki przecięcia MACD wyrażone w tym kodzie:

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)

Ta struktura:

  • Składa zlecenie kupna, gdy linia MACD jest powyżej linii sygnału.
  • Składa zlecenie sprzedaży, gdy linia MACD jest poniżej linii sygnału.
  • Pomija handel, jeśli nie ma wyraźnego sygnału.

Listy

Python zapewnia potężne i zwięzłe narzędzia do pracy z kolekcjami danych, takimi jak transakcje, ceny lub wskaźniki. Dwa z najczęstszych to list comprehensions i wbudowane funkcje agregujące, takie jak sum().

Na przykład możesz napisać kod, który iteruje po numbers i zachowuje tylko liczby parzyste w ten sposób:

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

Możesz użyć list comprehension, aby wybrać tylko pozycje dla bieżącego symbolu, tworząc przefiltrowaną listę, która jest przydatna w środowiskach wielosymbolowych.

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

Agregowanie zysku lub wolumenu za pomocą sum() to standardowe podejście w strategiach handlu siatką do określania, kiedy całkowity zysk osiąga cel.

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

Możesz połączyć filtrowanie i agregację w ten sposób:

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

Operacje synchroniczne i asynchroniczne

Python domyślnie stosuje synchroniczny model wykonania. Metody cyklu życia Twojego algorytmu, takie jak on_start, on_tick i on_bar_closed, są wyzwalane przez pętlę zdarzeń platformy, jedna na raz.

Oto podstawowa reprezentacja synchronicznego przepływu handlowego:

graph TD
    A(Wykrycie sygnału) ==> B(Wykonanie zlecenia)
    B ==> C(Pozycja osiąga<br>TP/SL)
    C ==> D(Zamknięcie pozycji)
    D ==> A

W przepływach synchronicznych każda długotrwała operacja może zablokować wątek, potencjalnie opóźniając następne zdarzenie. Jednak w rzeczywistych strategiach algorytmy zazwyczaj inicjują nieblokujące akcje, takie jak składanie zlecenia lub ustawianie stop loss i take profit, a następnie czekają na przyszłe zdarzenia (takie jak nowy tick lub słupek), aby obsłużyć wyniki. Ta konfiguracja skutkuje orkiestracją podobną do asynchronicznej, mimo że konstrukcje async/await Pythona nie są używane.

Uwaga

W przypadku większości algorytmów handlowych jawne programowanie asynchroniczne nie jest konieczne.

cTrader zarządza współbieżnością poprzez serializację wywołań zwrotnych opartych na zdarzeniach. Każda metoda jest wywoływana niezależnie i nigdy równolegle, eliminując potrzebę zabezpieczeń wielowątkowych. Ten projekt umożliwia strategiom reagowanie na wiele warunków handlowych jednocześnie, w sensie logicznym, bez blokowania.

W poniższym przykładzie strategia ocenia sygnały kupna i sprzedaży niezależnie i odpowiednio reaguje, bez żadnego nakładania się kontekstu wykonania.

graph TD
    A([Wykrycie sygnału]) ==> B([Otwarcie<br>zlecenia kupna]) & C([Otwarcie zlecenia<br>sprzedaży jako<br>zabezpieczenia]) ==> D([Zlecenie osiąga<br>take profit lub<br>stop loss])
    D ==> E([Zamknięcie pozycji])
    E ==> A

Mimo że decyzje mogą wydawać się równoległe, są obsługiwane sekwencyjnie przez wewnętrzny system kolejkowania cTrader. Architektura platformy ma na celu zapobieganie problemom ze współbieżnością, jednocześnie utrzymując responsywność robotów handlowych na zmiany rynkowe.

Image title