소개
해당 사례글은 시도한 내용을 정리하였습니다.
주요한 내용은 아래 사례 글을 활용해서 하시면 더 쉽게 하실 수 있습니다.
https://www.gpters.org/wealth/post/creating-program-automatically-copies-LhCkmYJIiUHhwUQ
진행 방법
준비물
Claude Code
Vantage 계정 (거래를 위한 브로커 계정)
MetaEditor 4
진행한 것
MT4 설치 프로그램을 다운 받아 설치하고 계정을 로그인하였습니다.
EA(간단한 전략)을 만들고 테스트를 해보았습니다.
MT4 프로그램 설치
프로그램 설치는 간단했습니다.
지원하는 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를 테스트하는 것이 너무 신기했습니다.
지난 한 달간의 데이터를 가지 고 외환거래를 시뮬레이션 했을 때 우하향을 맛보았습니다.
결과와 배운 점
그간, 주식거래, 코인거래, 코인 선물 거래 등 많은 경험을 해보았지만
전략을 짜서 거래를 하는 시스템 거래는 첫 경험으로 너무 신기합니다.
모르는 것이 너무 많아서 실거래까지는 많은 우여곡절이 있겠지만
재미있는 영역을 알게 되어 기쁩니다.