AI를 활용한 투자전략 구축 1탄

소개

해당 사례글은 시도한 내용을 정리하였습니다.
주요한 내용은 아래 사례 글을 활용해서 하시면 더 쉽게 하실 수 있습니다.

https://www.gpters.org/wealth/post/creating-program-automatically-copies-LhCkmYJIiUHhwUQ

진행 방법

  • 준비물

  • 진행한 것

    • MT4 설치 프로그램을 다운 받아 설치하고 계정을 로그인하였습니다.

    • EA(간단한 전략)을 만들고 테스트를 해보았습니다.

  • MT4 프로그램 설치

    프로그램 설치는 간단했습니다.

    개인용 컴퓨터 windows mac os linux

    지원하는 OS에 맞춰 다운받아 설치하면 됩니다.

    Mac 과 Linux의 경우 문제가 있다곤 하지만, UI적인면을 제외하곤 동작은 잘 되는 것 같습니다.

  • EA 간단한 전략 만들기

    전략 만들기는 참고 사례글을 참고하여 만들었습니다.

    저는 Bollinger Band를 활용한 매매 방식을 채택하였고 Claude Code를 활용해서 MQL4 버전(MT4)의 EA를 만들어서 파일로 작성했습니다.

mq4는 아래처럼 나왔습니다.

//+------------------------------------------------------------------+
//|                                           BollingerBand_EA.mq4   |
//|                        Copyright 2025, AI Investment Quant Team  |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, AI Investment Quant Team"
#property link      ""
#property version   "1.00"
#property strict

//+------------------------------------------------------------------+
//| 입력 파라미터 (사용자가 쉽게 수정 가능)                              |
//+------------------------------------------------------------------+
input double   LotSize = 0.01;              // 거래 로트 사이즈 (0.01 = 1,000통화)
input int      BollingerPeriod = 20;        // 볼린저밴드 기간
input double   BollingerDeviation = 2.0;    // 볼린저밴드 표준편차 배수
input int      MagicNumber = 20250115;      // EA 식별 매직넘버
input double   StopLossPercent = 2.0;       // 손절 비율 (%)
input double   TakeProfitPercent = 5.0;     // 익절 비율 (%)
input int      Slippage = 3;                // 슬리피지 (포인트)
input bool     UseMoneyManagement = false;  // 자금관리 사용 여부
input double   RiskPercent = 1.0;           // 자금관리 시 리스크 비율 (%)

//+------------------------------------------------------------------+
//| 글로벌 변수                                                        |
//+------------------------------------------------------------------+
int lastBarTime = 0;  // 마지막 캔들 시간 (중복 주문 방지)

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   Print("========================================");
   Print("볼린저밴드 EA 초기화 완료");
   Print("통화쌍: ", Symbol());
   Print("로트 사이즈: ", LotSize);
   Print("볼린저밴드 기간: ", BollingerPeriod);
   Print("손절: ", StopLossPercent, "% | 익절: ", TakeProfitPercent, "%");
   Print("========================================");

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   Print("볼린저밴드 EA 종료 - 이유: ", reason);
}

//+------------------------------------------------------------------+
//| Expert tick function (매 틱마다 실행)                              |
//+------------------------------------------------------------------+
void OnTick()
{
   // 새로운 캔들이 생성될 때만 실행 (중복 주문 방지)
   if(Time[0] == lastBarTime)
      return;

   lastBarTime = Time[0];

   // 볼린저밴드 값 계산
   double upperBand = iBands(Symbol(), 0, BollingerPeriod, BollingerDeviation, 0, PRICE_CLOSE, MODE_UPPER, 1);
   double lowerBand = iBands(Symbol(), 0, BollingerPeriod, BollingerDeviation, 0, PRICE_CLOSE, MODE_LOWER, 1);
   double middleBand = iBands(Symbol(), 0, BollingerPeriod, BollingerDeviation, 0, PRICE_CLOSE, MODE_MAIN, 1);

   // 이전 캔들의 종가
   double closePrice = Close[1];

   // 현재 포지션 확인
   bool hasBuyPosition = HasOpenPosition(OP_BUY);
   bool hasSellPosition = HasOpenPosition(OP_SELL);

   // 실제 사용할 로트 사이즈 계산
   double lots = LotSize;
   if(UseMoneyManagement)
   {
      lots = CalculateLotSize(StopLossPercent);
   }

   // 로트 사이즈 검증
   lots = NormalizeLotSize(lots);

   //+------------------------------------------------------------------+
   //| 매수 시그널: 가격이 하단 밴드 터치 또는 돌파                         |
   //+------------------------------------------------------------------+
   if(closePrice <= lowerBand && !hasBuyPosition)
   {
      // 기존 매도 포지션이 있다면 청산
      if(hasSellPosition)
         CloseAllPositions(OP_SELL);

      // 매수 주문 실행
      double stopLoss = CalculateStopLoss(OP_BUY, StopLossPercent);
      double takeProfit = CalculateTakeProfit(OP_BUY, TakeProfitPercent);

      int ticket = OrderSend(Symbol(), OP_BUY, lots, Ask, Slippage, stopLoss, takeProfit,
                            "BB Buy Signal", MagicNumber, 0, clrBlue);

      if(ticket > 0)
      {
         Print(">>> 매수 주문 성공 | Ticket: ", ticket, " | Lots: ", lots,
               " | Price: ", Ask, " | SL: ", stopLoss, " | TP: ", takeProfit);
      }
      else
      {
         Print(">>> 매수 주문 실패 | Error: ", GetLastError());
      }
   }

   //+------------------------------------------------------------------+
   //| 매도 시그널: 가격이 상단 밴드 터치 또는 돌파                         |
   //+------------------------------------------------------------------+
   if(closePrice >= upperBand && !hasSellPosition)
   {
      // 기존 매수 포지션이 있다면 청산
      if(hasBuyPosition)
         CloseAllPositions(OP_BUY);

      // 매도 주문 실행
      double stopLoss = CalculateStopLoss(OP_SELL, StopLossPercent);
      double takeProfit = CalculateTakeProfit(OP_SELL, TakeProfitPercent);

      int ticket = OrderSend(Symbol(), OP_SELL, lots, Bid, Slippage, stopLoss, takeProfit,
                            "BB Sell Signal", MagicNumber, 0, clrRed);

      if(ticket > 0)
      {
         Print(">>> 매도 주문 성공 | Ticket: ", ticket, " | Lots: ", lots,
               " | Price: ", Bid, " | SL: ", stopLoss, " | TP: ", takeProfit);
      }
      else
      {
         Print(">>> 매도 주문 실패 | Error: ", GetLastError());
      }
   }
}

//+------------------------------------------------------------------+
//| 특정 타입의 포지션이 열려있는지 확인                                 |
//+------------------------------------------------------------------+
bool HasOpenPosition(int orderType)
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == orderType)
         {
            return true;
         }
      }
   }
   return false;
}

//+------------------------------------------------------------------+
//| 특정 타입의 모든 포지션 청산                                        |
//+------------------------------------------------------------------+
void CloseAllPositions(int orderType)
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == orderType)
         {
            bool result = false;
            if(orderType == OP_BUY)
            {
               result = OrderClose(OrderTicket(), OrderLots(), Bid, Slippage, clrOrange);
            }
            else if(orderType == OP_SELL)
            {
               result = OrderClose(OrderTicket(), OrderLots(), Ask, Slippage, clrOrange);
            }

            if(result)
            {
               Print(">>> 포지션 청산 성공 | Ticket: ", OrderTicket());
            }
            else
            {
               Print(">>> 포지션 청산 실패 | Error: ", GetLastError());
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 손절가 계산 (현재가 대비 % 기준)                                    |
//+------------------------------------------------------------------+
double CalculateStopLoss(int orderType, double stopLossPercent)
{
   double stopLoss = 0;
   double currentPrice = (orderType == OP_BUY) ? Ask : Bid;

   if(stopLossPercent > 0)
   {
      if(orderType == OP_BUY)
      {
         // 매수: 현재가보다 낮은 가격
         stopLoss = currentPrice * (1 - stopLossPercent / 100.0);
      }
      else if(orderType == OP_SELL)
      {
         // 매도: 현재가보다 높은 가격
         stopLoss = currentPrice * (1 + stopLossPercent / 100.0);
      }

      // 가격 정규화 (브로커의 Digits에 맞춤)
      stopLoss = NormalizeDouble(stopLoss, Digits);
   }

   return stopLoss;
}

//+------------------------------------------------------------------+
//| 익절가 계산 (현재가 대비 % 기준)                                    |
//+------------------------------------------------------------------+
double CalculateTakeProfit(int orderType, double takeProfitPercent)
{
   double takeProfit = 0;
   double currentPrice = (orderType == OP_BUY) ? Ask : Bid;

   if(takeProfitPercent > 0)
   {
      if(orderType == OP_BUY)
      {
         // 매수: 현재가보다 높은 가격
         takeProfit = currentPrice * (1 + takeProfitPercent / 100.0);
      }
      else if(orderType == OP_SELL)
      {
         // 매도: 현재가보다 낮은 가격
         takeProfit = currentPrice * (1 - takeProfitPercent / 100.0);
      }

      // 가격 정규화 (브로커의 Digits에 맞춤)
      takeProfit = NormalizeDouble(takeProfit, Digits);
   }

   return takeProfit;
}

//+------------------------------------------------------------------+
//| 자금관리 기반 로트 사이즈 계산                                       |
//+------------------------------------------------------------------+
double CalculateLotSize(double stopLossPercent)
{
   // 계좌 잔고의 일정 비율을 리스크로 설정
   double accountBalance = AccountBalance();
   double riskAmount = accountBalance * (RiskPercent / 100.0);

   // 손절 거리 (포인트)
   double stopLossDistance = Ask * (stopLossPercent / 100.0);
   double stopLossPoints = stopLossDistance / Point;

   // 1 로트당 포인트 가치
   double pointValue = MarketInfo(Symbol(), MODE_TICKVALUE);

   // 로트 사이즈 계산
   double lots = 0;
   if(stopLossPoints > 0 && pointValue > 0)
   {
      lots = riskAmount / (stopLossPoints * pointValue);
   }
   else
   {
      lots = LotSize; // 계산 실패 시 기본값 사용
   }

   return lots;
}

//+------------------------------------------------------------------+
//| 로트 사이즈 정규화 (브로커 제한에 맞춤)                              |
//+------------------------------------------------------------------+
double NormalizeLotSize(double lots)
{
   double minLot = MarketInfo(Symbol(), MODE_MINLOT);
   double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
   double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);

   // 최소/최대 범위 제한
   if(lots < minLot) lots = minLot;
   if(lots > maxLot) lots = maxLot;

   // LotStep에 맞춰 정규화
   lots = MathFloor(lots / lotStep) * lotStep;
   lots = NormalizeDouble(lots, 2);

   return lots;
}

//+------------------------------------------------------------------+

만들어진 파일을 Metaeditor를 활용해 ex4파일로 전환 하였고

컴퓨터 화면의 스크린샷

화면에 다양한 숫자가 표시됩니다.

이렇게 ex4파일이 되면

시스템 트레이딩을 할 수 있는 EA가 만들어집니다.

  • 테스트하기

만들어진 EA를 테스트하는 것이 너무 신기했습니다.

지난 한 달간의 데이터를 가지고 외환거래를 시뮬레이션 했을 때 우하향을 맛보았습니다.

결과와 배운 점

그간, 주식거래, 코인거래, 코인 선물 거래 등 많은 경험을 해보았지만
전략을 짜서 거래를 하는 시스템 거래는 첫 경험으로 너무 신기합니다.

모르는 것이 너무 많아서 실거래까지는 많은 우여곡절이 있겠지만
재미있는 영역을 알게 되어 기쁩니다.

뉴스레터 무료 구독

👉 이 게시글도 읽어보세요