Bỏ qua

Kiến thức cơ bản về Python cho giao dịch thuật toán

Các nhà giao dịch sử dụng Python để xây dựng bot giao dịch, chỉ báo kỹ thuật, plugin và các công cụ khác để hỗ trợ giao dịch thuật toán hoặc thao tác thủ công, và bài viết này giải thích các kiến thức cơ bản về lập trình bằng Python.

Python là một ngôn ngữ lập trình đa năng, cấp cao, phổ biến được sử dụng cho phát triển web, khoa học dữ liệu, tự động hóa và gần đây là giao dịch thuật toán trong cTrader. Cho dù bạn đang tạo bot giao dịch đơn giản dựa trên tín hiệu hay chiến lược phức tạp với nhiều chỉ báo, Python giúp quá trình này nhanh hơn, hiệu quả hơn và dễ quản lý hơn.

Thuật toán Python trong một phút!

  • Trải nghiệm hỗ trợ Python tích hợp sẵn và truy cập liền mạch vào API cTrader Algo, với cTrader là nền tảng lớn duy nhất cung cấp tích hợp gốc ngay từ đầu. Không cần plugin, bộ điều hợp hoặc cài đặt phức tạp.
  • Tập trung nhiều hơn vào những gì chiến lược của bạn nên đạt được khi logic trở nên dễ dàng hơn để lắp ráp. Tận hưởng quá trình lặp lại nhanh chóng, các thành phần có thể tái sử dụng và hiệu suất có thể mở rộng.
  • Phát triển nhanh chóng, gỡ lỗi thông minh hơn và cộng tác không có rào cản. Mã của bạn sẽ luôn có thể điều chỉnh, đáng tin cậy và dễ bảo trì.
  • Truy cập và hưởng lợi từ hàng nghìn thư viện miễn phí/mã nguồn mở, hướng dẫn chi tiết, mẫu mã và hỗ trợ cộng đồng mạnh mẽ trên toàn thế giới.

Biến và kiểu dữ liệu

Trong Python, biến được gán kiểu động, nghĩa là bạn không cần phải khai báo kiểu dữ liệu của chúng một cách rõ ràng. Trình thông dịch xác định kiểu tại thời điểm chạy dựa trên giá trị được gán.

Bạn tạo biến bằng cách chỉ cần gán một giá trị cho nó:

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

Các kiểu dữ liệu phổ biến

Loại Mô tả Ví dụ
int Số nguyên 5, 100, api.StepPips
float Số thập phân 1.1234, 100.5, api.Symbol.Ask
bool Giá trị Boolean True, False
str Chuỗi "EURUSD", api.Label
list Tập hợp các mục [1, 2, 3], [p for p in api.Positions]

Trong các thuật toán cTrader, biến lưu trữ dữ liệu quan trọng liên quan đến thông tin thị trường, quyết định giao dịch và cấu hình cBot. Các biến này thường đại diện cho khối lượng giao dịch được tính toán, giá trị chỉ báo, cờ Boolean, tên ký hiệu, nhãn và nhiều hơn nữa.

Đây là một ví dụ:

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

Trong trường hợp này, self.volumeInUnits là một float được sử dụng để đặt lệnh, trong khi self.enoughMoney là một bool kiểm soát việc có tiếp tục mở vị thế trong chiến lược lưới hay không.

Lớp và đối tượng

Python là một ngôn ngữ lập trình hướng đối tượng (OOP), cung cấp các công cụ để tổ chức mã thông qua các lớp và đối tượng mô hình hóa các thực thể và hành vi trong thế giới thực. Một lớp định nghĩa một bản thiết kế để tạo ra các đối tượng, là các thể hiện của lớp đó với dữ liệu và hành vi riêng biệt. Các khái niệm chính được nêu dưới đây:

  • Một lớp được định nghĩa bằng từ khóa class.
  • Phương thức __init__ (hàm khởi tạo) thường được sử dụng để khởi tạo trạng thái đối tượng.
  • Các phương thức của thể hiện định nghĩa hành vi.
  • Các thuộc tính chứa dữ liệu cụ thể cho mỗi đối tượng.

Ví dụ, bạn có thể tạo một lớp 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

Sau đó, bạn có thể tạo các đối tượng của lớp Car và tương tác với chúng:

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

Trong ngữ cảnh cTrader

Trong cTrader, cBot, chỉ báo và plugin được triển khai dưới dạng các lớp đóng gói toàn bộ logic của chúng. Các thuật toán tương tác với nền tảng cTrader bằng cách sử dụng đối tượng api đặc biệt cung cấp quyền truy cập vào các ký hiệu, biểu đồ, lệnh, chỉ báo và nhiều thứ khác.

Bạn có thể tạo một lớp SimplecBot trong cTrader theo cách sau:

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

Lớp SimplecBot bao gồm một phương thức duy nhất (on_start(self)) được thực thi một lần khi robot giao dịch bắt đầu. Hàm api.Print ghi một thông báo vào nhật ký, thể hiện cách các phương thức của đối tượng đóng gói hành vi giao dịch.

Mỗi thể hiện của một lớp thực sự đại diện cho một thuật toán giao dịch độc lập quản lý vòng đời và quyết định giao dịch của riêng nó bằng cách sử dụng các nguyên tắc OOP. Trong một cBot cTrader tiêu chuẩn, một lớp có thể lưu trữ dữ liệu trạng thái nội bộ, chẳng hạn như khối lượng, kết quả chỉ báo, v.v., và sử dụng một số phương thức để quản lý logic giao dịch.

Hãy xem xét ví dụ về giao cắt MACD này, khởi tạo chỉ báo MACD khi bắt đầu và đặt lệnh mua khi xảy ra tín hiệu giao cắt 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)

Từ khóa self

Bên trong một lớp, self đề cập đến thể hiện của lớp đó và cung cấp quyền truy cập vào các biến và phương thức của nó. self được sử dụng để:

  • Lưu trữ dữ liệu như kết quả chỉ báo, khối lượng vị thế và giá trị cấu hình.
  • Theo dõi trạng thái nội bộ qua các lệnh gọi phương thức khác nhau như on_start(), on_tick() hoặc on_bar_closed().

Ví dụ này minh họa cách sử dụng 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)

Trong trường hợp này, self.volume lưu trữ khối lượng đã tính toán để sử dụng cho các giao dịch, trong khi self.macd lưu trữ đối tượng chỉ báo MACD, cho phép truy cập vào các giá trị của nó trong suốt vòng đời của cBot. Nếu không có self, các biến này sẽ chỉ cục bộ trong on_start() và không thể truy cập được trong on_bar_closed() nơi có thể đưa ra quyết định giao dịch.

Phương thức và hàm

Hàm là các khối mã có thể tái sử dụng được sử dụng để thực hiện một tác vụ cụ thể. Khi các hàm được định nghĩa bên trong một lớp, chúng được gọi là phương thức và chúng hoạt động trên trạng thái nội bộ của lớp thông qua tham số self. Trong bối cảnh giao dịch thuật toán với cTrader, các phương thức xác định cách thuật toán giao dịch của bạn hoạt động trong các điều kiện khác nhau, chẳng hạn như tick thị trường, đóng nến hoặc khởi tạo.

Nền tảng tự động gọi các phương thức đặc biệt (được gọi là trình xử lý sự kiện) vào thời điểm thích hợp, chẳng hạn như:

on_start(self) – được gọi một lần khi bot được khởi tạo.

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

on_tick(self) – được gọi trên mỗi tick thị trường đến.

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

on_bar_closed(self) – được gọi khi một nến/thanh mới đóng (lý tưởng cho các chiến lược dựa trên thời gian).

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

Phương thức tùy chỉnh hoặc trợ giúp

Để tránh viết lại và trùng lặp mã, bạn có thể định nghĩa các phương thức của riêng mình. Các phương thức tùy chỉnh như vậy nên đóng gói logic chung mà robot giao dịch của bạn có thể sử dụng lại.

Phương thức dưới đây trả về tất cả các vị thế mở được liên kết với nhãn cBot hiện tại, giúp dễ dàng theo dõi hoặc đóng các vị thế một cách nhất quán.

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

Hãy xem xét một phương thức khác tính toán khoảng cách tính bằng pip giữa giá hiện tại và giá vào lệnh của vị thế, điều này rất quan trọng đối với các quyết định trong các chiến lược dựa trên lưới.

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

Sau khi một phương thức đã được định nghĩa, bạn có thể gọi nó từ bên trong cùng một lớp bằng cách sử dụng từ khóa self. Cách tiếp cận này cho phép bạn sử dụng lại logic ở bất kỳ đâu trong bot của mình mà không cần sao chép mã.

Ví dụ: sau khi định nghĩa get_bot_positions(), bạn có thể gọi nó bên trong một phương thức khác để đóng tất cả các vị thế:

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)

Ở đây, self.get_bot_positions() gọi phương thức trợ giúp, chỉ trả về các vị thế được liên kết với nhãn bot hiện tại.

Tương tự, phương thức get_distance_in_pips() có thể được sử dụng lại để đưa ra quyết định giao dịch. Ví dụ: trong chiến lược lưới, bạn có thể muốn mở một lệnh mới chỉ khi thị trường đã di chuyển đủ xa so với vị thế cuối cùng:

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

Các khái niệm khác

Không gian tên và import

Không gian tên trong Python giúp tổ chức mã và tránh xung đột tên bằng cách nhóm các định danh liên quan (biến, hàm, lớp). Import cho phép bạn đưa vào các mô-đun, thư viện hoặc gói bên ngoài để mở rộng chức năng ngoài các tính năng tiêu chuẩn của Python.

Trong Python, bạn có thể import các mô-đun tiêu chuẩn hoặc gói tùy chỉnh:

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

Bạn cũng có thể import các hàm hoặc lớp cụ thể:

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

Khi viết thuật toán cho cTrader bằng Python, import hoạt động hơi khác một chút vì cTrader tích hợp với cTrader Algo API dựa trên .NET. Thiết lập này liên quan đến một cầu nối cho phép mã Python tương tác với các assembly .NET, và điều này giải thích lý do tại sao một cBot điển hình bắt đầu bằng:

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 *

Trong đó:

  • import clr tải Common Language Runtime (CLR), cho phép Python tương tác với các thư viện .NET.
  • clr.AddReference("cAlgo.API") tham chiếu đến assembly cAlgo API, chứa tất cả các lớp, phương thức và đối tượng liên quan đến giao dịch.
  • from cAlgo.API import * đưa vào tất cả các kiểu từ API (ví dụ: TradeType, Color, Position).
  • from robot_wrapper import * tải các hàm trợ giúp và wrapper đơn giản hóa các hoạt động giao dịch thông thường.

Hệ thống không gian tên đảm bảo rằng:

  • Các tên tích hợp sẵn của Python (như print) tránh xung đột với các tên của cTrader (như api.Print).
  • Tất cả các đối tượng dành riêng cho giao dịch (api.Symbol, api.Indicators, api.ExecuteMarketOrder) đến từ không gian tên cTrader Algo API, giữ chúng được tổ chức và riêng biệt.
  • Bạn có thể mở rộng chức năng bằng cách import các gói tùy chỉnh của riêng bạn để làm việc cùng với các kiểu tích hợp sẵn của cTrader.

Vòng lặp

Vòng lặp cho phép bạn lặp lại các thao tác trên một chuỗi giá trị hoặc cho đến khi một điều kiện được đáp ứng. Trong giao dịch thuật toán, vòng lặp thường được sử dụng để xử lý các vị thế (ví dụ: đóng, lọc hoặc cập nhật chúng), kiểm tra các điều kiện trên nhiều tài sản hoặc chỉ báo, triển khai các chiến lược lưới hoặc martingale trong đó yêu cầu đặt lệnh lặp đi lặp lại và nhiều hơn nữa.

Trong Python, lặp lại rất đơn giản và hỗ trợ cả vòng lặp for (để lặp qua các bộ sưu tập) và vòng lặp while (để lặp lại cho đến khi một điều kiện thay đổi).

Bạn có thể viết một vòng lặp để đóng các vị thế:

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

Trong trường hợp này:

  • self.get_bot_positions() trả về tất cả các vị thế thuộc về cBot hiện tại.
  • Vòng lặp for đi qua từng vị thế.
  • Các vị thế phù hợp được đóng bằng api.ClosePosition.

Hãy xem xét một vòng lặp khác lặp qua tất cả các vị thế lưới mở, đóng chúng từng cái một và kiểm tra đệ quy xem có còn vị thế nào không (an toàn trong trường hợp đóng một phần).

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

Câu lệnh điều kiện

Các câu lệnh điều kiện kiểm soát luồng thực thi trong Python bằng cách cho phép thuật toán của bạn đưa ra quyết định dựa trên các biểu thức logic. Điều kiện là xương sống của logic chiến lược, xác định khi nào nên vào, thoát hoặc quản lý các vị thế.

Python hỗ trợ ba cấu trúc điều kiện chính:

  • if – thực thi mã khi điều kiện đúng.
  • elif (else if) – kiểm tra một điều kiện khác nếu điều kiện đầu tiên sai.
  • else – cung cấp một phương án dự phòng khi không có điều kiện nào được đáp ứng.

Hãy xem xét các điều kiện giao cắt MACD được thể hiện trong mã này:

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)

Cấu trúc này:

  • Đặt lệnh mua khi đường MACD nằm trên đường tín hiệu.
  • Đặt lệnh bán khi đường MACD nằm dưới đường tín hiệu.
  • Bỏ qua giao dịch nếu không có tín hiệu rõ ràng.

Danh sách

Python cung cấp các công cụ mạnh mẽ và súc tích để làm việc với các bộ sưu tập dữ liệu, chẳng hạn như các giao dịch, giá cả hoặc chỉ báo. Hai trong số những công cụ phổ biến nhất là list comprehension và các hàm tổng hợp tích hợp như sum().

Ví dụ, bạn có thể viết một đoạn mã lặp qua numbers và chỉ giữ lại các số chẵn theo cách sau:

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

Bạn có thể sử dụng list comprehension để chỉ chọn các vị thế cho biểu tượng hiện tại, tạo ra một danh sách đã được lọc hữu ích trong môi trường đa biểu tượng.

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

Tổng hợp lợi nhuận hoặc khối lượng bằng cách sử dụng sum() là một phương pháp tiêu chuẩn trong các chiến lược giao dịch lưới để xác định khi nào tổng lợi nhuận đạt mục tiêu.

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

Bạn có thể kết hợp lọc và tổng hợp theo cách sau:

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

Các hoạt động đồng bộ và bất đồng bộ

Python theo mô hình thực thi đồng bộ theo mặc định. Các phương thức vòng đời của thuật toán của bạn, chẳng hạn như on_start, on_tickon_bar_closed, được kích hoạt bởi vòng lặp sự kiện của nền tảng, từng cái một.

Đây là biểu diễn cơ bản của luồng giao dịch đồng bộ:

graph TD
    A(Gặp tín hiệu) ==> B(Thực hiện lệnh)
    B ==> C(Vị thế chạm TP/SL)
    C ==> D(Đóng vị thế)
    D ==> A

Trong các luồng đồng bộ, bất kỳ hoạt động chạy lâu nào cũng có thể chặn luồng, có khả năng trì hoãn sự kiện tiếp theo. Tuy nhiên, trong các chiến lược thực tế, các thuật toán thường khởi tạo các hành động không chặn, chẳng hạn như gửi lệnh hoặc đặt cắt lỗ và chốt lời, sau đó chúng đợi các sự kiện trong tương lai (chẳng hạn như một tick hoặc nến mới) để xử lý kết quả. Thiết lập này dẫn đến một sự điều phối giống như bất đồng bộ, mặc dù các cấu trúc async/await của Python không được sử dụng.

Ghi chú

Đối với hầu hết các thuật toán giao dịch, lập trình bất đồng bộ rõ ràng là không cần thiết.

cTrader quản lý đồng thời bằng cách tuần tự hóa các callback dựa trên sự kiện của bạn. Mỗi phương thức được gọi độc lập và không bao giờ song song, loại bỏ nhu cầu về các biện pháp bảo vệ đa luồng. Thiết kế này cho phép các chiến lược phản hồi đồng thời nhiều điều kiện giao dịch, theo nghĩa logic, mà không bị chặn.

Trong ví dụ dưới đây, chiến lược đánh giá các tín hiệu mua và bán độc lập và phản hồi tương ứng, mà không có bất kỳ sự chồng chéo nào trong ngữ cảnh thực thi.

graph TD
    A([Gặp tín hiệu]) ==> B([Mở lệnh mua]) & C([Mở lệnh bán để phòng <br>ngừa rủi ro]) ==> D([Một lệnh chạm mức<br>chốt lời hoặc cắt lỗ])
    D ==> E([Đóng vị thế])
    E ==> A

Mặc dù các quyết định có vẻ song song, chúng được xử lý tuần tự bởi hệ thống xếp hàng nội bộ của cTrader. Kiến trúc nền tảng nhằm mục đích ngăn chặn các vấn đề đồng thời trong khi giữ cho các robot giao dịch phản hồi nhanh với những thay đổi của thị trường.

Image title