콘텐츠로 이동

알고리즘 트레이딩을 위한 Python 기초

트레이더들은 Python을 사용하여 트레이딩 봇, 기술적 지표, 플러그인 및 알고리즘 트레이딩이나 수동 작업을 지원하는 기타 도구를 구축하며, 이 글은 Python에서의 코딩 기초를 설명합니다.

Python은 웹 개발, 데이터 과학, 자동화 및 최근에는 cTrader에서의 알고리즘 트레이딩을 위해 사용되는 인기 있는 고수준 범용 프로그래밍 언어입니다. 단순한 신호 기반 트레이딩 봇부터 복잡한 다중 지표 전략까지, Python은 프로세스를 더 빠르고 효율적이며 관리하기 쉽게 만듭니다.

1분 만에 Python 알고리즘!

  • 내장된 Python 지원과 cTrader Algo API에 대한 원활한 접근을 경험하세요. cTrader는 기본적으로 네이티브 통합을 제공하는 유일한 주요 플랫폼입니다. 플러그인, 어댑터 또는 복잡한 설정이 필요하지 않습니다.
  • 로직을 더 쉽게 조립할 수 있으므로 전략이 달성해야 할 목표에 더 집중하세요. 빠른 반복, 재사용 가능한 구성 요소 및 확장 가능한 성능을 즐기세요.
  • 빠르게 개발하고, 더 스마트하게 디버깅하며, 장벽 없이 협업하세요. 코드는 항상 적응 가능하고, 신뢰할 수 있으며, 유지 관리가 가능합니다.
  • 전 세계 수천 개의 무료/오픈 소스 라이브러리, 심층 튜토리얼, 코드 샘플 및 강력한 커뮤니티 지원에 접근하고 이점을 누리세요.

변수 및 데이터 타입

Python에서 변수는 동적으로 타입이 지정되므로 데이터 타입을 명시적으로 선언할 필요가 없습니다. 인터프리터는 할당된 값을 기반으로 런타임에 타입을 결정합니다.

변수는 단순히 값을 할당하여 생성합니다:

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

일반적인 데이터 타입

종류 설명 예시
int 정수 5, 100, api.StepPips
float 소수 1.1234, 100.5, api.Symbol.Ask
bool 불리언 값 True, False
str 문자열 "EURUSD", api.Label
list 항목 컬렉션 [1, 2, 3], [p for p in api.Positions]

cTrader 알고리즘에서 변수는 시장 정보, 트레이딩 결정 및 cBot 구성과 관련된 중요한 데이터를 저장합니다. 이러한 변수는 종종 계산된 트레이딩 볼륨, 지표 값, 불리언 플래그, 심볼 이름, 레이블 등을 나타냅니다.

다음은 예시입니다:

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

이 경우, self.volumeInUnits는 주문을 내기 위해 사용되는 float이며, self.enoughMoney는 그리드 전략에서 포지션을 계속 열지 여부를 제어하는 bool입니다.

클래스와 객체

Python은 객체 지향 프로그래밍(OOP) 언어로, 클래스와 객체를 통해 코드를 조직화하는 도구를 제공하여 실제 세계의 엔티티와 행동을 모델링합니다. 클래스는 객체를 생성하기 위한 청사진을 정의하며, 객체는 해당 클래스의 인스턴스로 고유한 데이터와 행동을 가집니다. 주요 개념은 다음과 같습니다:

  • 클래스는 class 키워드를 사용하여 정의됩니다.
  • __init__ 메서드(생성자)는 일반적으로 객체 상태를 초기화하는 데 사용됩니다.
  • 인스턴스 메서드는 행동을 정의합니다.
  • 속성은 각 객체에 특정한 데이터를 보유합니다.

예를 들어, 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

그런 다음 Car 클래스의 객체를 생성하고 상호 작용할 수 있습니다:

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

cTrader 컨텍스트에서

cTrader에서 cBot, 지표 및 플러그인은 전체 로직을 캡슐화하는 클래스로 구현됩니다. 알고리즘은 심볼, 차트, 주문, 지표 등에 접근할 수 있는 특별한 api 객체를 사용하여 cTrader 플랫폼과 상호 작용합니다.

다음과 같이 cTrader에서 SimplecBot 클래스를 만들 수 있습니다:

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

SimplecBot 클래스는 트레이딩 봇이 시작될 때 한 번 실행되는 단일 메서드(on_start(self))를 포함합니다. api.Print 함수는 로그에 메시지를 기록하며, 객체 메서드가 트레이딩 행동을 어떻게 캡슐화하는지 보여줍니다.

클래스의 각 인스턴스는 효과적으로 자체 포함된 트레이딩 알고리즘을 나타내며, OOP 원칙을 사용하여 자체 라이프사이클과 트레이딩 결정을 관리합니다. 표준 cTrader cBot에서 클래스는 볼륨, 지표 결과 등과 같은 내부 상태 데이터를 저장하고 여러 메서드를 사용하여 트레이딩 로직을 관리할 수 있습니다.

MACD 크로스오버 예시를 고려해 보세요. 시작할 때 MACD 지표를 초기화하고 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)

self 키워드

클래스 내부에서 self는 클래스의 인스턴스를 참조하며 해당 변수와 메서드에 접근할 수 있게 합니다. self는 다음을 위해 사용됩니다:

  • 지표 결과, 포지션 볼륨 및 구성 값과 같은 데이터를 저장합니다.
  • on_start(), on_tick() 또는 on_bar_closed()와 같은 다양한 메서드 호출에서 내부 상태를 추적합니다.

다음은 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)

이 경우, self.volume은 거래에 사용할 계산된 볼륨을 저장하고, self.macd는 MACD 지표 객체를 저장하여 cBot의 라이프사이클 동안 해당 값에 접근할 수 있게 합니다. self가 없으면 이러한 변수는 on_start()에 로컬로 존재하며 트레이딩 결정이 이루어질 수 있는 on_bar_closed()에서는 접근할 수 없습니다.

메서드와 함수

함수는 특정 작업을 수행하기 위해 재사용 가능한 코드 블록입니다. 함수가 클래스 내부에 정의되면 메서드라고 하며, self 매개변수를 통해 클래스의 내부 상태에서 작동합니다. cTrader와 함께 알고리즘 트레이딩을 하는 컨텍스트에서 메서드는 시장 틱, 바 종료 또는 초기화와 같은 다양한 조건에서 트레이딩 알고리즘이 어떻게 행동하는지 정의합니다.

플랫폼은 적절한 시점에 특수 메서드(이벤트 핸들러라고 함)를 자동으로 호출합니다. 예를 들어:

on_start(self) – 봇이 초기화될 때 한 번 호출됩니다.

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

on_tick(self) – 모든 시장 틱이 들어올 때마다 호출됩니다.

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

on_bar_closed(self) – 새로운 캔들/바가 닫힐 때 호출됩니다(시간 기반 전략에 이상적).

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

사용자 정의 또는 헬퍼 메서드

코드를 다시 작성하고 중복을 피하기 위해 자신만의 메서드를 정의할 수 있습니다. 이러한 사용자 정의 메서드는 트레이딩 로봇이 재사용할 수 있는 공통 로직을 캡슐화해야 합니다.

아래 메서드는 현재 cBot의 레이블과 연결된 모든 오픈 포지션을 반환하여 포지션을 일관되게 추적하거나 종료하기 쉽게 합니다.

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

그리드 기반 전략에서 결정을 내리는 데 중요한 현재 가격과 포지션 진입 가격 간의 거리를 핍 단위로 계산하는 또 다른 메서드를 고려해 보세요.

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

메서드가 정의되면 self 키워드를 사용하여 동일한 클래스 내에서 호출할 수 있습니다. 이 접근 방식을 통해 코드를 중복하지 않고 봇 내 어디에서나 로직을 재사용할 수 있습니다.

예를 들어, get_bot_positions()를 정의한 후 다른 메서드 내에서 모든 포지션을 종료하기 위해 호출할 수 있습니다:

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)

여기서 self.get_bot_positions()는 헬퍼 메서드를 호출하여 현재 봇 레이블에 연결된 포지션만 반환합니다.

마찬가지로, get_distance_in_pips() 메서드는 트레이딩 결정을 내리기 위해 재사용할 수 있습니다. 예를 들어, 그리드 전략에서 마지막 포지션에서 충분히 멀리 이동한 경우에만 새로운 주문을 열고 싶을 수 있습니다:

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

다른 개념

네임스페이스와 임포트

Python의 네임스페이스는 관련 식별자(변수, 함수, 클래스)를 그룹화하여 코드를 구성하고 이름 충돌을 방지하는 데 도움을 줍니다. 임포트를 통해 외부 모듈, 라이브러리 또는 패키지를 가져와 Python의 표준 기능을 넘어 기능을 확장할 수 있습니다.

Python에서는 표준 모듈이나 사용자 정의 패키지를 임포트할 수 있습니다:

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

특정 함수나 클래스를 임포트할 수도 있습니다:

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

Python으로 cTrader 알고리즘을 작성할 때, 임포트는 약간 다르게 작동합니다. 왜냐하면 cTrader는 .NET 기반의 cTrader Algo API와 통합되기 때문입니다. 이 설정은 Python 코드가 .NET 어셈블리와 상호 작용할 수 있도록 하는 브리지를 포함하며, 이는 일반적인 cBot이 다음과 같이 시작하는 이유를 설명합니다:

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 *

여기서:

  • import clr은 공용 언어 런타임(CLR)을 로드하여 Python이 .NET 라이브러리와 상호 작용할 수 있도록 합니다.
  • clr.AddReference("cAlgo.API")는 모든 트레이딩 관련 클래스, 메서드 및 객체를 포함하는 cAlgo API 어셈블리를 참조합니다.
  • from cAlgo.API import *는 API의 모든 타입(예: TradeType, Color, Position)을 가져옵니다.
  • from robot_wrapper import *는 일반적인 트레이딩 작업을 단순화하는 헬퍼 함수와 래퍼를 로드합니다.

네임스페이스 시스템은 다음을 보장합니다:

  • Python의 내장 이름(예: print)이 cTrader의 이름(예: api.Print)과 충돌하지 않습니다.
  • 모든 트레이딩 관련 객체(api.Symbol, api.Indicators, api.ExecuteMarketOrder)는 cTrader Algo API 네임스페이스에서 가져와 조직적이고 구별됩니다.
  • 사용자 정의 패키지를 임포트하여 cTrader의 내장 타입과 함께 작업할 수 있습니다.

루프

루프를 사용하면 값 시퀀스에 대해 작업을 반복하거나 조건이 충족될 때까지 반복할 수 있습니다. 알고리즘 트레이딩에서 루프는 종종 포지션을 처리(예: 종료, 필터링 또는 업데이트), 여러 자산 또는 지표에 걸쳐 조건을 확인, 반복적인 주문 배치가 필요한 그리드 또는 마틴게일 전략을 구현하는 데 사용됩니다.

Python에서는 반복이 간단하며 for 루프(컬렉션을 반복하기 위해)와 while 루프(조건이 변경될 때까지 반복하기 위해)를 모두 지원합니다.

포지션을 종료하기 위해 루프를 작성할 수 있습니다:

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

이 경우:

  • self.get_bot_positions()는 현재 cBot에 속한 모든 포지션을 반환합니다.
  • for 루프는 각 포지션을 순회합니다.
  • 일치하는 포지션은 api.ClosePosition으로 종료됩니다.

모든 오픈 그리드 포지션을 반복하고 하나씩 종료한 후 남은 포지션이 있는지 재귀적으로 확인하는 다른 루프를 고려해 보세요(부분 종료 시 안전을 위해).

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

조건문

조건문은 Python에서 실행 흐름을 제어하여 알고리즘이 논리 표현식에 따라 결정을 내릴 수 있도록 합니다. 조건문은 전략 로직의 핵심으로, 포지션을 진입, 종료 또는 관리할 시기를 결정합니다.

Python은 세 가지 주요 조건 구문을 지원합니다:

  • if – 조건이 참일 때 코드를 실행합니다.
  • elif (else if) – 첫 번째 조건이 거짓일 때 다른 조건을 확인합니다.
  • else – 조건이 충족되지 않을 때 대체 코드를 제공합니다.

MACD 크로스오버 조건을 표현한 이 코드를 고려해 보세요:

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)

이 구조는:

  • MACD 라인이 시그널 라인 위에 있을 때 매수 주문을 합니다.
  • MACD 라인이 시그널 라인 아래에 있을 때 매도 주문을 합니다.
  • 명확한 신호가 없으면 트레이딩을 건너뜁니다.

리스트

Python은 거래, 가격 또는 지표와 같은 데이터 컬렉션을 작업하기 위한 강력하고 간결한 도구를 제공합니다. 가장 일반적인 두 가지는 리스트 컴프리헨션과 sum()과 같은 내장 집계 함수입니다.

예를 들어, numbers를 반복하고 짝수만 유지하는 코드를 다음과 같이 작성할 수 있습니다:

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

리스트 컴프리헨션을 사용하여 현재 심볼에 대한 포지션만 선택하여 다중 심볼 환경에서 유용한 필터링된 리스트를 생성할 수 있습니다.

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

sum()을 사용하여 이익 또는 볼륨을 집계하는 것은 총 이익이 목표에 도달했는지 결정하기 위해 그리드 트레이딩 전략에서 표준적인 접근 방식입니다.

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

필터링과 집계를 다음과 같이 결합할 수 있습니다:

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

동기 및 비동기 작업

Python은 기본적으로 동기 실행 모델을 따릅니다. 알고리즘의 라이프사이클 메서드, 예를 들어 on_start, on_tick, on_bar_closed는 플랫폼의 이벤트 루프에 의해 한 번에 하나씩 트리거됩니다.

다음은 동기 트레이딩 흐름의 기본적인 표현입니다:

graph TD
    A(신호 발생) ==> B(주문 실행)
    B ==> C(포지션 TP/SL 도달)
    C ==> D(포지션 종료)
    D ==> A

동기 흐름에서는 장시간 실행되는 작업이 스레드를 차단할 수 있으며, 이는 다음 이벤트를 지연시킬 가능성이 있습니다. 그러나 실제 전략에서는 알고리즘이 일반적으로 비차단 작업을 시작합니다. 예를 들어 주문을 제출하거나 손절매 및 이익실현을 설정한 후, 결과를 처리하기 위해 미래 이벤트(예: 새로운 틱 또는 바)를 기다립니다. 이 설정은 Python의 async/await 구문을 사용하지 않더라도 비동기식 오케스트레이션과 유사한 결과를 가져옵니다.

참고

대부분의 트레이딩 알고리즘에서는 명시적인 비동기 프로그래밍이 필요하지 않습니다.

cTrader는 이벤트 기반 콜백을 직렬화하여 동시성을 관리합니다. 각 메서드는 독립적으로 호출되며 병렬로 호출되지 않기 때문에 멀티스레딩 보호 장치가 필요하지 않습니다. 이 설계는 전략이 논리적으로 여러 트레이딩 조건에 동시에 응답할 수 있도록 하며, 차단 없이 작동합니다.

아래 예제에서 전략은 매수 및 매도 신호를 독립적으로 평가하고 그에 따라 응답하며, 실행 컨텍스트에서 중복이 발생하지 않습니다.

graph TD
    A([신호를 포착]) ==> B([매수 주문 열기]) & C([헤지를 위한 매도 주문 열기]) ==> D([주문이 테이크프로핏<br>또는 스톱로스에 도달])
    D ==> E([포지션 닫기])
    E ==> A

결정이 병렬로 이루어지는 것처럼 보일 수 있지만, 실제로는 cTrader의 내부 큐잉 시스템에 의해 순차적으로 처리됩니다. 플랫폼 아키텍처는 동시성 문제를 방지하면서도 트레이딩 로봇이 시장 변화에 신속하게 대응할 수 있도록 합니다.

Image title