콘텐츠로 이동

cTrader API에 메서드를 추가하는 방법

cTrader API에 새로운 기능을 추가하려는 경우 확장 메서드는 중요한 도구입니다. 상대적으로 간단한 구문을 사용하여 Symbol 또는 Position과 같은 미리 정의된 API 클래스에 새로운 동작을 추가할 수 있습니다. 확장 메서드를 정의한 후에는 확장한 클래스의 모든 객체에서 이를 호출할 수 있습니다.

확장 메서드의 사용 사례

먼저, 확장 메서드를 사용하고 싶은 이유에 대해 간단히 설명하겠습니다.

cBot을 사용하여 주어진 포지션의 크기를 로트 단위로 접근할 수 있기를 원합니다. 이 정보는 우리가 선호하는 거래 전략에 직접적인 영향을 미치기 때문입니다. 이를 위해 Position 클래스의 변수를 초기화하고 Lots() 메서드에 접근하려고 시도할 수 있습니다.

1
2
Position position = Positions.Last();
Print(position.Lots());

코드를 그대로 입력하면 API에 Lots() 접근자가 존재하지 않는다는 오류가 발생할 것입니다. 하지만 기존 API 멤버에 새로운 메서드를 추가하면서 다른 기능에 영향을 미치지 않는 방법이 있다면 어떨까요?

이 메서드가 존재한다고 가정하면, 매 바마다 현재 모든 포지션 목록을 반복하고 로트 단위로 그 크기를 로그에 출력하는 간단한 cBot을 만들 수 있습니다. OnBar() 메서드를 다음과 같이 정의할 것입니다.

1
2
3
4
5
6
protected override void OnBar()
{
    foreach (var position in Positions) {
        Print(position.Lots());
    }
}

확장 메서드를 사용하면 Lots() 기능을 몇 줄의 코드로 추가하고, Position 클래스의 어떤 객체에서든 재사용할 수 있습니다. 아래에서는 이를 생성하는 방법과 이를 사용한 알고리즘의 몇 가지 예를 제공합니다.

확장 메서드의 작동 방식

확장 메서드를 사용할 때는 다음 규칙을 염두에 두세요.

  • 확장 메서드는 항상 정적입니다.

정적 메서드를 선언하려면 static 키워드를 사용하기만 하면 됩니다. 아래에서 Lots() 메서드를 빈 본문으로 선언합니다.

1
2
3
4
public static class MyExtensions {
    public static double Lots() {}

}
  • 확장 메서드는 임의의 수의 인수를 가질 수 있지만 첫 번째 인수는 항상 메서드가 호출될 데이터 타입/클래스를 지정해야 하며 this 키워드가 앞에 옵니다.

Position 클래스의 객체를 Lots() 메서드의 첫 번째이자 유일한 인수로 추가할 것입니다. Position 객체는 포지션이 열린 심볼에 대한 정보도 포함하고 있으므로 다른 인수가 필요하지 않습니다.

1
2
3
public static class MyExtensions {
    public static double Lots(this Position position) {}
}
  • 확장 메서드는 제공된 인수에 적합한 어떤 로직도 포함할 수 있습니다.

확장 메서드의 본문을 정의할 때 특별한 구문을 사용할 필요가 없습니다. 다른 메서드처럼 취급할 수 있으므로 본문을 다음과 같이 정의할 수 있습니다.

1
2
3
4
5
public static class MyExtensions {
    public static double Lots(this Position position) {
        return position.VolumeInUnits / position.Symbol.LotSize;
    }
}
  • 확장 메서드는 인스턴스 메서드 또는 정적 메서드로 호출할 수 있습니다.

cBot의 코드에서 확장 메서드를 호출하는 두 가지 방법이 있습니다.

인스턴스 메서드 구문을 사용할 때는 Position 타입의 적합한 객체에서 메서드를 호출합니다.

1
2
var position = Positions.Last();
Print(position.Lots());

정적 메서드 구문을 사용할 때는 해당 정적 클래스를 완전히 지정한 후 확장 메서드를 호출할 수 있습니다.

1
2
var position = Positions.Last();
Print(MyExtensions.Lots(position));

어떤 방법으로 확장 메서드를 호출하는 것이 가장 편리한지는 여러분이 결정할 문제입니다.

메서드 시그니처

인스턴스 구문을 사용할 때, 확장 메서드의 시그니처가 내장 API 메서드(예: Position.Close())와 동일한 경우를 피하세요. 이러한 상황에서는 일치하는 시그니처로 확장 메서드를 호출하려고 할 때마다 내장 메서드가 호출됩니다.

IntelliSense

확장 메서드를 호출하려고 할 때, IntelliSense는 내장 API 멤버와 구별하기 위해 특별한 아이콘을 사용합니다.

새로운 메서드를 실제로 보여주기 위해 시작 시 각각 다른 볼륨으로 세 개의 주문을 하는 cBot을 만들 수 있습니다. 모든 바에서 cBot은 현재 열린 모든 포지션의 볼륨을 로트 단위로 출력합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
protected override void OnStart()
{
    ExecuteMarketOrder(TradeType.Buy, SymbolName, 10000);
    ExecuteMarketOrder(TradeType.Buy, SymbolName, 100000);
    ExecuteMarketOrder(TradeType.Buy, SymbolName, 50000);
}

protected override void OnBar()
{
    foreach (var position in Positions) {
        Print(position.Lots());
    }
}

cBot을 빌드하고 실행한 후 로그에 올바른 값이 출력되는 것을 볼 수 있습니다.

cBot에서 확장 메서드 사용

이제 더 복잡한 cBot을 만들어 보겠습니다. 모든 바에서 우리의 알고리즘은 현재 열린 포지션 목록을 검토하고 손절매 수준을 손익분기점에 맞게 조정합니다. 이를 위해 Position 클래스에 대한 BreakEven() 확장 메서드를 생성할 필요가 있습니다.

새 cBot을 만들고 이름을 변경합니다. 그 후 필요 없는 모든 코드를 삭제하고 MyExtensions 클래스를 추가합니다.

1
2
3
public static class MyExtensions {

}

BreakEven() 메서드에 대한 코드는 비교적 간단합니다. 포지션에 손절매가 있는지, 총 이익이 0보다 큰지, 현재 설정된 손절매가 포지션 진입 가격과 동일하지 않은지 확인합니다. 이 모든 조건이 참이면 포지션의 손절매를 포지션 진입 가격과 동일하게 수정합니다.

1
2
3
4
5
6
7
public static class MyExtensions {
    public static void BreakEven(this Position position) {
        if (position.StopLoss is not null && position.GrossProfit > 0 && position.StopLoss != position.EntryPrice) {
            position.ModifyStopLossPrice(position.EntryPrice);
        }
    }
}

cBot 자체에서는 OnBar() 메서드 외에 다른 메서드를 사용할 필요가 없습니다. 모든 바에서 cBot에게 간단한 작업을 수행하도록 요청합니다. 즉, Positions 컬렉션을 반복하고 각 요소에 대해 새로운 BreakEven() 메서드를 호출합니다.

1
2
3
4
5
6
protected override void OnBar() 
{
    foreach (var position in Positions) {
        position.BreakEven();
    }
}

cBot을 빌드하고 실행한 후 실제로 작동하는 것을 볼 수 있습니다. 이는 특히 많은 오픈 포지션을 관리할 때 유용한 트레이딩 도우미가 될 수 있습니다.

지표에서 확장 메서드 사용

확장 메서드에 의존하는 유용한 지표도 만들 것입니다. 이 지표는 각 바의 시가와 비교하여 심볼 가격이 얼마나 변했는지 백분율로 표시하여 변동성을 측정합니다.

이를 위해 새 지표를 만들고 이름을 변경합니다. 코드 편집기 창에서 Bar 클래스를 확장하는 MyExtensions 클래스를 생성합니다.

1
2
3
public static class MyExtensions {

}

또한 PercentageChange() 메서드를 추가합니다. priceChange 변수에서 바의 종가에서 시가를 뺍니다. 이 메서드는 가격 변동을 시가로 나누고 100을 곱한 값을 반환합니다.

1
2
3
4
5
6
7
8
public static class MyExtensions 
{
    public static double PercentageChange(this Bar bar) 
    {
        var priceChange = bar.Open - bar.Close;
        return priceChange / bar.Open * 100;
    }
}

지표 코드 자체에서는 Initialize() 메서드와 불필요한 매개변수가 필요하지 않습니다. Calculate() 메서드의 본문에서는 모든 바에서 새로운 PercentageChange() 메서드를 호출합니다.

1
2
3
4
5
6
7
8
[Output("Main")]
public IndicatorDataSeries Result { get; set; }


public override void Calculate(int index)
{
    Result[index] = Bars[index].PercentageChange();
}

그 후 지표를 저장하고 빌드합니다. 지표의 인스턴스를 생성한 후 올바른 백분율 변화가 표시되는 것을 볼 수 있습니다. 이를 사용하여 단기 및 장기 변동성을 결정할 수 있으며 모든 종류의 트레이딩 전략에 도움이 됩니다.

요약

결론적으로, 확장 메서드는 cTrader API에 새로운 기능을 추가하는 재사용 가능한 코드를 만들고자 할 때 귀중한 도구입니다. 확장 메서드를 실험해 보는 것을 적극 권장합니다. 이를 통해 알고리즘을 더 효율적이고 유지 관리하기 쉽게 만들 수 있습니다.