English Русский 中文 Español Deutsch 日本語 Português Italiano Türkçe
preview
MetaTrader 5를 이용한 Python 고빈도 차익 거래 시스템

MetaTrader 5를 이용한 Python 고빈도 차익 거래 시스템

MetaTrader 5트레이딩 |
54 7
Yevgeniy Koshtenko
Yevgeniy Koshtenko

소개

외환시장. 알고리즘 전략. Python과 MetaTrader 5. 제가 차익 거래 시스템을 시작했을 때 이런 생각이 떠올랐습니다. 아이디어는 간단했습니다. 가격 불균형을 찾기 위해 고빈도 시스템을 만드는 것입니다. 결국 이 모든 일은 어떻게 되었을까요?

이 기간 동안 저는 MetaTrader 5 API를 가장 자주 사용했습니다. 저는 합성 교차율을 계산하기로 했습니다. 저는 열 개나 백 개로 제한하지 않기로 결심했습니다. 그 숫자는 천을 넘었습니다.

위험 관리 업무는 별개의 업무였습니다. 시스템 아키텍처, 알고리즘, 의사결정 등 - 우리는 모든 것을 여기서 분석해 보겠습니다. 저는 백테스팅과 실시간 거래의 결과를 보여드리겠습니다. 물론 미래에 대한 아이디어도 공유하겠습니다. 혹시 여러분 중에 이 주제를 더 발전시키고 싶은 분이 있을지 누가 알까요? 이 결과물이 많은 사람들에게 인기를 끌기를 바랍니다. 또한 알고리즘 트레이딩의 발전에 기여할 수 있을 것으로 믿고 싶습니다. 어쩌면 누군가가 이를 기초로 삼아 고빈도 차익거래의 세계에서 훨씬 더 효과적인 무언가를 만들어낼 지도 모릅니다. 결국 - 과학의 본질은 선행자들의 경험을 바탕으로 전진하는 것입니다. 바로 요점으로 들어가겠습니다.


외환 차익거래 소개

그것이 실제로 무엇인지 알아보도록 합시다.

환전에도 동일하게 적용할 수 있습니다. 예를 들어 한 곳에서 USD를 EUR로 매수한 후 다른 곳에서 바로 GBP로 매도하고 GBP를 다시 EUR로 환전해서 수익을 낼 수 있다고 가정해 보겠습니다. 이것이 가장 단순한 형태의 차익거래입니다.

사실은, 좀 더 복잡합니다. 외환은 거대하고 분산된 시장입니다. 여기에는 많은 수의 은행, 브로커, 펀드가 있습니다. 그리고 각자 주체마다 환율이 다릅니다. 대부분의 경우에 이 환율들이 같지 않습니다. 여기서 우리는 차익의 기회를 얻습니다. 하지만 이것이 쉽다고 생각하지 마세요. 일반적으로 이러한 가격 차이는 몇 초 동안만 지속됩니다. 혹은 밀리초 단위이기도 하고요. 그 시간에 맞추는 것은 거의 불가능합니다. 이를 위해서는 강력한 컴퓨터와 빠른 알고리즘이 필요합니다.

차익에도 여러 유형이 있습니다. 간단한 예는 서로 다른 지역의 환율의 차이를 이용해 이익을 얻는 경우입니다. 교차 환율을 사용하는 경우는 복잡합니다. 예를 들어 우리는 GBP가 USD와 EUR로 얼마인지 계산하고 이를 GBP/EUR 환율과 비교합니다.

리스트는 여기서 끝나지 않습니다. 시간 차익거래도 있습니다. 시간대별로 다른 가격 차이를 이용해 이익을 얻는 것입니다. 매수하고 1분 안에 매도합니다. 물론 그 과정은 간단해 보입니다. 하지만 가장 큰 문제는 우리는 가격이 1분 후에 어떻게 될지 알 수 없다는 것입니다. 주요 위험은 다음과 같습니다. 원하는 주문을 실행하기 전에 시장이 빠르게 반전될 수 있습니다. 또는 브로커가 주문의 실행을 지연할 수도 있습니다. 어려움과 위험의 종류는 매우 많습니다. 이들 어려움에도 불구하고 외환 차익거래는 꽤 인기 있는 시스템입니다. 여기에는 상당한 재정 자원이 투입되고 이런 유형의 거래에만 전문화된 트레이더들이 많이 있습니다.

이상 차익거래에 대해 간단한 소개를 했습니다. 이제 우리의 전략에 대해 이야기해 보겠습니다.


사용된 기술 개요: Python과 MetaTrader 5

Python과 MetaTrader 5. 

Python은 다재다능 하고 이해하기 쉬운 프로그래밍 언어입니다. 초보자와 숙련된 개발자 모두가 선호하는 데는 이유가 있는 것입니다. 데이터 분석에 가장 적합합니다.

반면, MetaTrader 5. 이는 모든 외환 트레이더들에게 친숙한 플랫폼입니다. 신뢰할 수 있고 복잡하지 않습니다. 그리고 실시간 시세, 거래 로봇, 기술 분석 등 기능도 매우 뛰어납니다. 모든 것이 하나의 애플리케이션에 담겨 있습니다. 긍정적인 결과를 얻으려면 우리는 이 모든 것을 결합해야 합니다.

Python은 MetaTrader 5에서 데이터를 가져옵니다. 이후 라이브러리를 사용하여 처리한 다음 MetaTrader 5로 명령을 다시 보내 거래를 실행합니다. 물론 어려움도 있습니다. 하지만 이러한 응용 프로그램을 함께 사용하면 매우 효율적입니다.

Python에서 MetaTrader 5를 사용하기 위해 개발자가 제공하는 특별 라이브러리를 사용할 수 있습니다. 설치 하면 바로 활성화 됩니다. 이렇게 하면 우리는 호가를 받고, 주문을 보내고, 포지션을 관리할 수 있습니다. 모든 것이 터미널 자체와 동일하며 이제 Python의 기능도 사용항수 있습니다.

이제 어떤 기능과 성능을 사용할 수 있을까요? 지금은 그 수가 꽤 많아졌습니다. 예를 들어 우리는 거래를 자동화하고 과거 데이터에 대해 복잡한 분석을 수행할 수 있습니다. 우리만의 거래 플랫폼을 만들 수도 있습니다. 이는 고급 사용자를 위한 작업이지만 가능한 작업입니다.


환경 설정: 필요한 라이브러리 설치 및 MetaTrader 5 연결

Python으로 워크플로를 시작해 보겠습니다. 아직 없다면 python.org를 방문하세요. 또한 ADD TO PATCH 동의도 설정해야 합니다.

다음 단계는 라이브러리입니다. 몇 개 정도 필요할 것 같습니다. 가장 중요한 것은 MetaTrader 5입니다. 설치에는 특별한 기술이 필요하지 않습니다.

코맨드를 열고 다음을 입력합니다.

pip install MetaTrader5 pandas numpy

Enter 키를 누르고 커피를 마시러 가세요. 아니면 차도 좋습니다. 아니면 여러분이 원하시는 대로 하세요.

모든 것이 준비되었나요? 이제 MetaTrader 5에 연결할 시간입니다.

가장 먼저 해야 할 일은 MetaTrader 5를 설치하는 것입니다. 브로커 사이트에서 다운로드하세요. 터미널의 경로를 꼭 기억하세요. 일반적으로 다음과 같습니다. "C:\ProgramFiles\MetaTrader 5\terminal64.exe".

이제 Python을 열고 다음을 입력하세요:

import MetaTrader5 as mt5

if not mt5.initialize(path="C:/Program Files/MetaTrader 5/terminal64.exe"):
    print("Alas! Failed to connect :(")
    mt5.shutdown()
else:
    print("Hooray! Connection successful!")

모든 것이 시작되면 다음 단계로 넘어가세요.


코드 구조: 주요 함수와 함수의 목적

'imports'부터 시작해 보겠습니다. 여기에는 다음과 같은 imports가 있습니다. MetaTrader5, pandas, datetime, pytz... 다음으로는 함수가 있습니다.

  • 첫 번째 함수는 remove_duplicate_indices입니다. 이를 통해 데이터에 중복이 없는지 확인할 수 있습니다.
  • 다음은 get_mt5_data입니다. MetaTrader 5 함수에 접근하여 지난 24시간 동안의 필요한 데이터를 추출합니다.
  • get_currency_data — 매우 흥미로운 함수입니다. 여러 통화 쌍에 대해 get_mt5_data를 호출합니다. AUDUSD, EURUSD, GBPJPY 등 다양한 통화쌍.
  • 다음은 calculate_synthetic_prices입니다. 이 기능은 정말 대단합니다. 통화 쌍들을 처리하는 동안 수백 개의 합성 가격을 생성합니다.
  • analyze_arbitrage는 실제 가격과 합성 가격을 비교하여 차익거래의 기회를 찾습니다. 모든 결과는 CSV 파일로 저장됩니다.
  • open_test_limit_order — 또 다른 강력한 코드 단위. 차익거래의 기회가 발견되면 이 함수는 테스트 주문을 엽니다. 하지만 동시에 10개 이상의 거래를 열 수는 없습니다.

마지막으로 'main' 함수입니다. 올바른 순서로 함수를 호출하여 전체 프로세스를 관리합니다.

결국 모든 것은 끝없는 반복입니다. 5분 간격으로 전체 루프를 행하지만 작동하는 시간에만 행합니다. 이것이 우리의 구조입니다. 간단하지만 효율적입니다. 


MetaTrader 5에서 데이터 가져오기: get_mt5_data 함수

첫 번째 작업은 터미널에서 데이터를 수신하는 것입니다.

if not mt5.initialize(path=terminal_path):
    print(f"Failed to connect to MetaTrader 5 terminal at {terminal_path}")
    return None
timezone = pytz.timezone("Etc/UTC")
utc_from = datetime.now(timezone) - timedelta(days=1)

우리가 UTC를 사용한다는 점에 유의하세요. 외환 시장에서는 시간대 혼동이 용납되지 않기 때문입니다.

이제 가장 중요한 것은 틱을 얻는 것입니다.

ticks = mt5.copy_ticks_from(symbol, utc_from, count, mt5.COPY_TICKS_ALL)

데이터를 수신했나요? 좋습니다! 이제 우리는 틱을 다뤄야 합니다. 이를 위해 우리는 pandas를 사용합니다:

ticks_frame = pd.DataFrame(ticks)
ticks_frame['time'] = pd.to_datetime(ticks_frame['time'], unit='s')

Voila! 이제 우리는 데이터가 담긴 DataFrame을 갖게 되었습니다. 이미 분석을 위해 준비되었습니다.

하지만 뭔가 잘못된다면? 괜찮아요! 우리 함수에는 다음과 같은 내용도 포함됩니다.

if ticks is None:
    print(f"Failed to fetch data for {symbol}")
    return None

단순히 문제를 보고하고 None을 반환합니다.


여러 통화 쌍 처리: get_currency_data 함수

get_currency_data 함수를 이용해 시스템을 더 자세히 살펴보겠습니다. 코드를 살펴보겠습니다.

def get_currency_data():
    # Define currency pairs and the amount of data
    symbols = ["AUDUSD", "AUDJPY", "CADJPY", "AUDCHF", "AUDNZD", "USDCAD", "USDCHF", "USDJPY", "NZDUSD", "GBPUSD", "EURUSD", "CADCHF", "CHFJPY", "NZDCAD", "NZDCHF", "NZDJPY", "GBPCAD", "GBPCHF", "GBPJPY", "GBPNZD", "EURCAD", "EURCHF", "EURGBP", "EURJPY", "EURNZD"]
    count = 1000  # number of data points for each currency pair
    data = {}
    for symbol in symbols:
        df = get_mt5_data(symbol, count, terminal_path)
        if df is not None:
            data[symbol] = df[['time', 'bid', 'ask']].set_index('time')
    return data

모든 것은 통화 쌍을 정의하는 것부터 시작됩니다. 이 목록에는 AUDUSD, EURUSD, GBPJPY 및 우리에게 잘 알려진 다른 통화쌍이 포함되어 있습니다.

이제 다음 단계로 넘어가겠습니다. 이 함수는 빈 '데이터' 사전을 생성합니다. 나중에 필요한 데이터로 채워질 것입니다.

이제 함수가 작동을 시작합니다. 통화 쌍 목록을 살펴보겠습니다. 각 쌍에 대해 get_mt5_data를 호출합니다. get_mt5_data가 데이터(None이 아닌)를 반환하는 경우, 우리의 함수는 가장 중요한 시간, bid, ask만 취합니다.

그리고 마침내 대단원의 막을 내립니다. 이 함수는 데이터로 채워진 사전을 반환합니다. 

이제 우리는 get_currency_data를 얻습니다. 작고 강력하고 간단하지만 효과적입니다.


2000년 합성 가격 계산: 전략 및 실행

시스템의 기본인 calculate_synthetic_prices 함수를 살펴보겠습니다. 이를 통해 우리는 합성된 데이터를 얻을 수 있습니다.

코드를 살펴보겠습니다.

def calculate_synthetic_prices(data):
    synthetic_prices = {}

    # Remove duplicate indices from all DataFrames in the data dictionary
    for key in data:
        data[key] = remove_duplicate_indices(data[key])

    # Calculate synthetic prices for all pairs using multiple methods
    pairs = [('AUDUSD', 'USDCHF'), ('AUDUSD', 'NZDUSD'), ('AUDUSD', 'USDJPY'),
             ('USDCHF', 'USDCAD'), ('USDCHF', 'NZDCHF'), ('USDCHF', 'CHFJPY'),
             ('USDJPY', 'USDCAD'), ('USDJPY', 'NZDJPY'), ('USDJPY', 'GBPJPY'),
             ('NZDUSD', 'NZDCAD'), ('NZDUSD', 'NZDCHF'), ('NZDUSD', 'NZDJPY'),
             ('GBPUSD', 'GBPCAD'), ('GBPUSD', 'GBPCHF'), ('GBPUSD', 'GBPJPY'),
             ('EURUSD', 'EURCAD'), ('EURUSD', 'EURCHF'), ('EURUSD', 'EURJPY'),
             ('CADCHF', 'CADJPY'), ('CADCHF', 'GBPCAD'), ('CADCHF', 'EURCAD'),
             ('CHFJPY', 'GBPCHF'), ('CHFJPY', 'EURCHF'), ('CHFJPY', 'NZDCHF'),
             ('NZDCAD', 'NZDJPY'), ('NZDCAD', 'GBPNZD'), ('NZDCAD', 'EURNZD'),
             ('NZDCHF', 'NZDJPY'), ('NZDCHF', 'GBPNZD'), ('NZDCHF', 'EURNZD'),
             ('NZDJPY', 'GBPNZD'), ('NZDJPY', 'EURNZD')]

    method_count = 1
    for pair1, pair2 in pairs:
        print(f"Calculating synthetic price for {pair1} and {pair2} using method {method_count}")
        synthetic_prices[f'{pair1}_{method_count}'] = data[pair1]['bid'] / data[pair2]['ask']
        method_count += 1
        print(f"Calculating synthetic price for {pair1} and {pair2} using method {method_count}")
        synthetic_prices[f'{pair1}_{method_count}'] = data[pair1]['bid'] / data[pair2]['bid']
        method_count += 1

    return pd.DataFrame(synthetic_prices)


차익거래 기회 분석: analyze_arbitrage 함수

먼저 빈 사전 synthetic_prices를 만듭니다. 여기에 데이터를 채워 넣을 것입니다. 그런 다음 모든 데이터를 검토하고 중복된 인덱스를 제거해서 향후 오류가 발생하지 않도록 합니다.

다음 단계는 '쌍' 목록입니다. 이는 합성에 사용할 통화 쌍입니다. 그러면 또 다른 과정이 시작됩니다. 우리는 모든 쌍을 통해 루프를 실행합니다. 각 쌍에 대해 우리는 두 가지 방법으로 합성 가격을 계산합니다.

  1. 첫 번째 쌍의 bid를 두 번째 쌍의 ask로 나눕니다.
  2. 첫 번째 쌍의 bid를 두 번째 쌍의 bid로 나눕니다.

우리는 매번 method_count를 늘립니다. 결과적으로 우리는 2000개의 합성 쌍을 얻게 되었습니다!

calculate_synthetic_prices 함수는 이렇게 작동합니다. 단순히 가격을 계산하는 것이 아니라 실제로 새로운 기회를 창출하는 것입니다. 이 기능은 차익거래 기회의 형태로 훌륭한 결과를 제공합니다!


결과 시각화: CSV로 데이터 저장

analyze_arbitrage 함수를 살펴보겠습니다. 이 함수는 단순히 데이터를 분석하는 것이 아니라 숫자의 흐름 속에서 필요한 것을 찾아냅니다. 이제 살펴보겠습니다.

def analyze_arbitrage(data, synthetic_prices, method_count):
    # Calculate spreads for each pair
    spreads = {}
    for pair in data.keys():
        for i in range(1, method_count + 1):
            synthetic_pair = f'{pair}_{i}'
            if synthetic_pair in synthetic_prices.columns:
                print(f"Analyzing arbitrage opportunity for {synthetic_pair}")
                spreads[synthetic_pair] = data[pair]['bid'] - synthetic_prices[synthetic_pair]
    # Identify arbitrage opportunities
    arbitrage_opportunities = pd.DataFrame(spreads) > 0.00008
    print("Arbitrage opportunities:")
    print(arbitrage_opportunities)
    # Save the full table of arbitrage opportunities to a CSV file
    arbitrage_opportunities.to_csv('arbitrage_opportunities.csv')
    return arbitrage_opportunities

먼저 우리 함수는 빈 '스프레드' 사전을 만듭니다. 여기에 데이터를 채워 넣을 것입니다.

다음 단계로 넘어가겠습니다. 이 함수는 모든 통화 쌍과 합성 통화 쌍에서 실행됩니다. 각 쌍에 대해 실제 bid 가격과 합성 가격의 차이인 스프레드를 계산합니다.

spreads[synthetic_pair] = data[pair]['bid'] - synthetic_prices[synthetic_pair]

이 문자열은 매우 중요한 역할을 합니다. 실제 가격과 합성 가격의 차이를 찾아냅니다. 만약 이 차이가 양수라면 우리는 차익거래의 기회를 얻게 됩니다.

더욱 심각한 결과를 얻으려면 0.00008이라는 숫자를 사용합니다.

arbitrage_opportunities = pd.DataFrame(spreads) > 0.00008

이 문자열은 8 포인트 미만의 모든 가능성을 분류합니다. 이렇게 하면 우리는 수익일 확률이 더 높은 기회를 얻을 수 있을 것입니다.

다음 단계는 다음과 같습니다.

arbitrage_opportunities.to_csv('arbitrage_opportunities.csv')

이제 모든 데이터가 CSV 파일에 저장됩니다. 이제 우리는 이를 연구하고 분석하고 차트를 그리고 - 생산적인 작업을 할 수 있습니다. 이 모든 것은 함수 analyze_arbitrage 덕분에 가능합니다. 이 함수는 단순히 분석하는 데 그치지 않고 차익 거래의 기회를 찾아내고 저장해 둡니다.


테스트 주문 개시: open_test_limit_order 함수

다음으로 open_test_limit_order 함수를 살펴보겠습니다. 그러면 주문이 열릴 겁니다.

살펴보겠습니다.

def open_test_limit_order(symbol, order_type, price, volume, take_profit, stop_loss, terminal_path):
    if not mt5.initialize(path=terminal_path):
        print(f"Failed to connect to MetaTrader 5 terminal at {terminal_path}")
        return None
    symbol_info = mt5.symbol_info(symbol)
    positions_total = mt5.positions_total()
    if symbol_info is None:
        print(f"Instrument not found: {symbol}")
        return None
    if positions_total >= MAX_OPEN_TRADES:
        print("MAX POSITIONS TOTAL!")
        return None
    # Check if symbol_info is None before accessing its attributes
    if symbol_info is not None:
        request = {
            "action": mt5.TRADE_ACTION_DEAL,
            "symbol": symbol,
            "volume": volume,
            "type": order_type,
            "price": price,
            "deviation": 30,
            "magic": 123456,
            "comment": "Stochastic Stupi Sustem",
            "type_time": mt5.ORDER_TIME_GTC,
            "type_filling": mt5.ORDER_FILLING_IOC,
            "tp": price + take_profit * symbol_info.point if order_type == mt5.ORDER_TYPE_BUY else price - take_profit * symbol_info.point,
            "sl": price - stop_loss * symbol_info.point if order_type == mt5.ORDER_TYPE_BUY else price + stop_loss * symbol_info.point,
        }
        result = mt5.order_send(request)
        if result is not None and result.retcode == mt5.TRADE_RETCODE_DONE:
            print(f"Test limit order placed for {symbol}")
            return result.order
        else:
            print(f"Error: Test limit order not placed for {symbol}, retcode={result.retcode if result is not None else 'None'}")
            return None
    else:
        print(f"Error: Symbol info not found for {symbol}")
        return None

우리의 함수가 가장 먼저 하는 일은 MetaTrader 5 터미널에 연결을 시도하는 것입니다. 그런 다음 우리가 거래하려는 상품이 존재하는지 확인합니다.

코드:

if positions_total >= MAX_OPEN_TRADES:
    print("MAX POSITIONS TOTAL!")
    return None

이러한 점검을 통해 우리가 너무 많은 포지션을 보유하지 않도록 합니다.

이제 다음 단계는 주문을 열기 위한 요청을 생성하는 것입니다. 여기에는 상당히 많은 매개변수가 있습니다. 주문 유형, 거래량, 가격, 편차, 매직 넘버, 코멘트... 모든 것이 잘 진행되면 함수는 우리에게 준비되었다는 것을 알려줍니다. 그렇지 않으면 메시지가 나타납니다.

open_test_limit_order 함수는 이렇게 작동합니다. 이것이 시장과 우리의 관계입니다. 어떤 면에서는 브로커의 기능을 수행합니다.


일시적인 거래 제한: 특정 시간 동안 작업

이제 거래 시간에 대해 이야기해 보겠습니다. 

if current_time >= datetime.strptime("23:30", "%H:%M").time() or current_time <= datetime.strptime("05:00", "%H:%M").time():
    print("Current time is between 23:30 and 05:00. Skipping execution.")
    time.sleep(300)  # Wait for 5 minutes before checking again
    continue

여기서 무슨 일이 일어나고 있는 걸까요? 우리의 시스템은 시간을 체크합니다. 시계가 오후 11시 30분에서 오전 5시 사이의 시간을 표시하면 해당 시간이 거래 시간이 아니라는 것을 확인하고 5분 동안 대기 모드로 전환됩니다. 그러면 활성화되어 시간을 다시 확인하고 여전히 이르면 다시 대기 모드로 들어갑니다.

왜 이것이 필요할까요? 여기에는 이유가 있습니다. 첫째 유동성입니다. 밤에는 보통 유동성이 줄어듭니다. 둘째 스프레드입니다. 밤이 되면 스프레드가 넓어집니다. 셋째 뉴스. 가장 중요한 내용은 대개 거래 시간 중에 나옵니다.


런타임 루프 및 오류 처리

'main' 함수를 살펴보겠습니다. 이 함수는 선장과 비슷하지만 스티어링 휠 대신 키보드가 있습니다. 이 함수가 무엇을 할까요? 모든 것은 간단합니다.

  1. 데이터 수집
  2. 합성 가격 계산 
  3. 차익거래 기회를 찾기 
  4. 주문 개시

약간의 오류 처리도 있습니다. 

def main():
    data = get_currency_data()
    synthetic_prices = calculate_synthetic_prices(data)
    method_count = 2000  # Define the method_count variable here
    arbitrage_opportunities = analyze_arbitrage(data, synthetic_prices, method_count)

    # Trade based on arbitrage opportunities
    for symbol in arbitrage_opportunities.columns:
        if arbitrage_opportunities[symbol].any():
            direction = "BUY" if arbitrage_opportunities[symbol].iloc[0] else "SELL"
            symbol = symbol.split('_')[0]  # Remove the index from the symbol
            symbol_info = mt5.symbol_info_tick(symbol)
            if symbol_info is not None:
                price = symbol_info.bid if direction == "BUY" else symbol_info.ask
                take_profit = 450
                stop_loss = 200
                order = open_test_limit_order(symbol, mt5.ORDER_TYPE_BUY if direction == "BUY" else mt5.ORDER_TYPE_SELL, price, 0.50, take_profit, stop_loss, terminal_path)
            else:
                print(f"Error: Symbol info tick not found for {symbol}")


시스템 확장성: 새로운 통화 쌍 및 메서드 추가

새로운 통화쌍을 추가하시겠습니까? 이 목록에 포함하기만 하면 됩니다.

symbols = ["EURUSD", "GBPUSD", "USDJPY", ... , "YOURPAIR"]

이제 시스템은 새로운 쌍에 대해 알고 있습니다. . 새로운 계산 메서드는 어떤가요? 

def calculate_synthetic_prices(data):
    # ... existing code ...
    
    # Add a new method
    synthetic_prices[f'{pair1}_{method_count}'] = data[pair1]['ask'] / data[pair2]['bid']
    method_count += 1


차익러래 시스템의 테스트 및 백테스팅

백테스팅에 대해 이야기해 보겠습니다. 이는 모든 거래 시스템에 있어서 정말 중요한 사항입니다. 우리의 차익거래 시스템도 예외는 아닙니다.

우리는 무엇을 했나요? 우리는 과거 데이터를 통해 전략을 실행했습니다. 왜 그랬을까요? 얼마나 효율적인지 이해하기 위해서 우리의 코드는 get_historical_data로 시작합니다. 이 함수는 MetaTrader 5에서 이전 데이터를 검색합니다. 이 데이터가 없다면 우리는 생산적으로 일할 수 없습니다.

그 다음에는 calculate_synthetic_prices가 나옵니다. 여기서 우리는 합성 환율을 계산합니다. 이는 우리의 차익거래 전략의 핵심 부분입니다. Analyze_arbitrage는 우리의 기회 감지기로 실제 가격과 합성 가격을 비교하고 차이점을 찾습니다. 그 결과로 우리는 잠재적인 수익을 찾을 수 있습니다. simulate_trade는 거의 거래 과정입니다. 하지만 이는 테스트 모드에서 발생합니다. 이는 매우 중요한 과정입니다. 시뮬레이션에서 실수하는 것이 실제 돈을 잃는 것보다는 낫기 때문입니다.

마지막으로 backtest_arbitrage_system은 모든 데이터를 종합하여 과거 데이터를 통해 전략을 실행합니다. 날마다 그리고 거래마다.

import MetaTrader5 as mt5
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
import pytz

# Path to MetaTrader 5 terminal
terminal_path = "C:/Program Files/ForexBroker - MetaTrader 5/Arima/terminal64.exe"

def remove_duplicate_indices(df):
    """Removes duplicate indices, keeping only the first row with a unique index."""
    return df[~df.index.duplicated(keep='first')]

def get_historical_data(start_date, end_date, terminal_path):
    if not mt5.initialize(path=terminal_path):
        print(f"Failed to connect to MetaTrader 5 terminal at {terminal_path}")
        return None

    symbols = ["AUDUSD", "AUDJPY", "CADJPY", "AUDCHF", "AUDNZD", "USDCAD", "USDCHF", "USDJPY", "NZDUSD", "GBPUSD", "EURUSD", "CADCHF", "CHFJPY", "NZDCAD", "NZDCHF", "NZDJPY", "GBPCAD", "GBPCHF", "GBPJPY", "GBPNZD", "EURCAD", "EURCHF", "EURGBP", "EURJPY", "EURNZD"]
    
    historical_data = {}
    for symbol in symbols:
        timeframe = mt5.TIMEFRAME_M1
        rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date)
        if rates is not None and len(rates) > 0:
            df = pd.DataFrame(rates)
            df['time'] = pd.to_datetime(df['time'], unit='s')
            df.set_index('time', inplace=True)
            df = df[['open', 'high', 'low', 'close']]
            df['bid'] = df['close']  # Simplification: use 'close' as 'bid'
            df['ask'] = df['close'] + 0.000001  # Simplification: add spread
            historical_data[symbol] = df

    mt5.shutdown()
    return historical_data

def calculate_synthetic_prices(data):
    synthetic_prices = {}
    pairs = [('AUDUSD', 'USDCHF'), ('AUDUSD', 'NZDUSD'), ('AUDUSD', 'USDJPY'),
             ('USDCHF', 'USDCAD'), ('USDCHF', 'NZDCHF'), ('USDCHF', 'CHFJPY'),
             ('USDJPY', 'USDCAD'), ('USDJPY', 'NZDJPY'), ('USDJPY', 'GBPJPY'),
             ('NZDUSD', 'NZDCAD'), ('NZDUSD', 'NZDCHF'), ('NZDUSD', 'NZDJPY'),
             ('GBPUSD', 'GBPCAD'), ('GBPUSD', 'GBPCHF'), ('GBPUSD', 'GBPJPY'),
             ('EURUSD', 'EURCAD'), ('EURUSD', 'EURCHF'), ('EURUSD', 'EURJPY'),
             ('CADCHF', 'CADJPY'), ('CADCHF', 'GBPCAD'), ('CADCHF', 'EURCAD'),
             ('CHFJPY', 'GBPCHF'), ('CHFJPY', 'EURCHF'), ('CHFJPY', 'NZDCHF'),
             ('NZDCAD', 'NZDJPY'), ('NZDCAD', 'GBPNZD'), ('NZDCAD', 'EURNZD'),
             ('NZDCHF', 'NZDJPY'), ('NZDCHF', 'GBPNZD'), ('NZDCHF', 'EURNZD'),
             ('NZDJPY', 'GBPNZD'), ('NZDJPY', 'EURNZD')]

    for pair1, pair2 in pairs:
        if pair1 in data and pair2 in data:
            synthetic_prices[f'{pair1}_{pair2}_1'] = data[pair1]['bid'] / data[pair2]['ask']
            synthetic_prices[f'{pair1}_{pair2}_2'] = data[pair1]['bid'] / data[pair2]['bid']

    return pd.DataFrame(synthetic_prices)

def analyze_arbitrage(data, synthetic_prices):
    spreads = {}
    for pair in data.keys():
        for synth_pair in synthetic_prices.columns:
            if pair in synth_pair:
                spreads[synth_pair] = data[pair]['bid'] - synthetic_prices[synth_pair]

    arbitrage_opportunities = pd.DataFrame(spreads) > 0.00008
    return arbitrage_opportunities

def simulate_trade(data, direction, entry_price, take_profit, stop_loss):
    for i, row in data.iterrows():
        current_price = row['bid'] if direction == "BUY" else row['ask']
        
        if direction == "BUY":
            if current_price >= entry_price + take_profit:
                return {'profit': take_profit * 800, 'duration': i}
            elif current_price <= entry_price - stop_loss:
                return {'profit': -stop_loss * 400, 'duration': i}
        else:  # SELL
            if current_price <= entry_price - take_profit:
                return {'profit': take_profit * 800, 'duration': i}
            elif current_price >= entry_price + stop_loss:
                return {'profit': -stop_loss * 400, 'duration': i}
    
    # If the loop completes without hitting TP or SL, close at the last price
    last_price = data['bid'].iloc[-1] if direction == "BUY" else data['ask'].iloc[-1]
    profit = (last_price - entry_price) * 100000 if direction == "BUY" else (entry_price - last_price) * 100000
    return {'profit': profit, 'duration': len(data)}

def backtest_arbitrage_system(historical_data, start_date, end_date):
    equity_curve = [10000]  # Starting with $10,000
    trades = []
    dates = pd.date_range(start=start_date, end=end_date, freq='D')

    for current_date in dates:
        print(f"Backtesting for date: {current_date.date()}")
        
        # Get data for the current day
        data = {symbol: df[df.index.date == current_date.date()] for symbol, df in historical_data.items()}
        
        # Skip if no data for the current day
        if all(df.empty for df in data.values()):
            continue

        synthetic_prices = calculate_synthetic_prices(data)
        arbitrage_opportunities = analyze_arbitrage(data, synthetic_prices)

        # Simulate trades based on arbitrage opportunities
        for symbol in arbitrage_opportunities.columns:
            if arbitrage_opportunities[symbol].any():
                direction = "BUY" if arbitrage_opportunities[symbol].iloc[0] else "SELL"
                base_symbol = symbol.split('_')[0]
                if base_symbol in data and not data[base_symbol].empty:
                    price = data[base_symbol]['bid'].iloc[-1] if direction == "BUY" else data[base_symbol]['ask'].iloc[-1]
                    take_profit = 800 * 0.00001  # Convert to price
                    stop_loss = 400 * 0.00001  # Convert to price
                    
                    # Simulate trade
                    trade_result = simulate_trade(data[base_symbol], direction, price, take_profit, stop_loss)
                    trades.append(trade_result)
                    
                    # Update equity curve
                    equity_curve.append(equity_curve[-1] + trade_result['profit'])

    return equity_curve, trades

def main():
    start_date = datetime(2024, 1, 1, tzinfo=pytz.UTC)
    end_date = datetime(2024, 8, 31, tzinfo=pytz.UTC)  # Backtest for January-August 2024
    
    print("Fetching historical data...")
    historical_data = get_historical_data(start_date, end_date, terminal_path)
    
    if historical_data is None:
        print("Failed to fetch historical data. Exiting.")
        return

    print("Starting backtest...")
    equity_curve, trades = backtest_arbitrage_system(historical_data, start_date, end_date)

    total_profit = sum(trade['profit'] for trade in trades)
    win_rate = sum(1 for trade in trades if trade['profit'] > 0) / len(trades) if trades else 0

    print(f"Backtest completed. Results:")
    print(f"Total Profit: ${total_profit:.2f}")
    print(f"Win Rate: {win_rate:.2%}")
    print(f"Final Equity: ${equity_curve[-1]:.2f}")

    # Plot equity curve
    plt.figure(figsize=(15, 10))
    plt.plot(equity_curve)
    plt.title('Equity Curve: Backtest Results')
    plt.xlabel('Trade Number')
    plt.ylabel('Account Balance ($)')
    plt.savefig('equity_curve.png')
    plt.close()

    print("Equity curve saved as 'equity_curve.png'.")

if __name__ == "__main__":
    main()

왜 이것이 중요한가요? 백테스팅은 우리의 시스템이 얼마나 효율적인지를 보여주기 때문입니다. 수익성이 있는지, 아니면 보증금이 고갈되는지.... 드로다운이란 무엇인가요? 거래에서 이길 확률은 얼마입니까? 우리는 이 모든 것을 백테스트를 통해 배웠습니다.

물론 과거의 결과가 미래의 결과를 보장하는 것은 아닙니다. 시장은 변화하고 있습니다. 하지만 백테스트 없이는 어떤 결과도 얻을 수 없습니다. 결과를 알고 있다는 것은, 대략 무엇을 기대해야 할지를 알고 있다는 것입니다. 또 다른 중요한 점 - 백테스팅이 시스템을 최적화하는 데 도움이 된다는 것입니다. 우리는 매개변수를 변경하고 결과를 계속해서 살펴봅니다. 이렇게 우리는 단계적으로 시스템을 개선해 나갑니다.

시스템 백테스트 결과는 다음과 같습니다.

다음은 MetaTrader 5에서 시스템을 테스트한 결과입니다.

다음은 해당 시스템의 MQL5 EA 코드입니다.

//+------------------------------------------------------------------+
//|                                                 TrissBotDemo.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
// Input parameters
input int MAX_OPEN_TRADES = 10;
input double VOLUME = 0.50;
input int TAKE_PROFIT = 450;
input int STOP_LOSS = 200;
input double MIN_SPREAD = 0.00008;

// Global variables
string symbols[] = {"AUDUSD", "AUDJPY", "CADJPY", "AUDCHF", "AUDNZD", "USDCAD", "USDCHF", "USDJPY", "NZDUSD", "GBPUSD", "EURUSD", "CADCHF", "CHFJPY", "NZDCAD", "NZDCHF", "NZDJPY", "GBPCAD", "GBPCHF", "GBPJPY", "GBPNZD", "EURCAD", "EURCHF", "EURGBP", "EURJPY", "EURNZD"};
int symbolsTotal;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    symbolsTotal = ArraySize(symbols);
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Cleanup code here
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    if(!IsTradeAllowed()) return;
    
    datetime currentTime = TimeGMT();
    if(currentTime >= StringToTime("23:30:00") || currentTime <= StringToTime("05:00:00"))
    {
        Print("Current time is between 23:30 and 05:00. Skipping execution.");
        return;
    }
    
    AnalyzeAndTrade();
}

//+------------------------------------------------------------------+
//| Analyze arbitrage opportunities and trade                        |
//+------------------------------------------------------------------+
void AnalyzeAndTrade()
{
    double synthetic_prices[];
    ArrayResize(synthetic_prices, symbolsTotal);
    
    for(int i = 0; i < symbolsTotal; i++)
    {
        synthetic_prices[i] = CalculateSyntheticPrice(symbols[i]);
        double currentPrice = SymbolInfoDouble(symbols[i], SYMBOL_BID);
        
        if(MathAbs(currentPrice - synthetic_prices[i]) > MIN_SPREAD)
        {
            if(currentPrice > synthetic_prices[i])
            {
                OpenOrder(symbols[i], ORDER_TYPE_SELL);
            }
            else
            {
                OpenOrder(symbols[i], ORDER_TYPE_BUY);
            }
        }
        
    }
}

//+------------------------------------------------------------------+
//| Calculate synthetic price for a symbol                           |
//+------------------------------------------------------------------+
double CalculateSyntheticPrice(string symbol)
{
    // This is a simplified version. You need to implement the logic
    // to calculate synthetic prices based on your specific method
    return SymbolInfoDouble(symbol, SYMBOL_ASK);
}

//+------------------------------------------------------------------+
//| Open a new order                                                 |
//+------------------------------------------------------------------+
void OpenOrder(string symbol, ENUM_ORDER_TYPE orderType)
{
    if(PositionsTotal() >= MAX_OPEN_TRADES)
    {
        Print("MAX POSITIONS TOTAL!");
        return;
    }
    
    double price = (orderType == ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID);
    double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
    
    double tp = (orderType == ORDER_TYPE_BUY) ? price + TAKE_PROFIT * point : price - TAKE_PROFIT * point;
    double sl = (orderType == ORDER_TYPE_BUY) ? price - STOP_LOSS * point : price + STOP_LOSS * point;
    
    MqlTradeRequest request = {};
    MqlTradeResult result = {};
    
    request.action = TRADE_ACTION_DEAL;
    request.symbol = symbol;
    request.volume = VOLUME;
    request.type = orderType;
    request.price = price;
    request.deviation = 30;
    request.magic = 123456;
    request.comment = "ArbitrageAdvisor";
    request.type_time = ORDER_TIME_GTC;
    request.type_filling = ORDER_FILLING_IOC;
    request.tp = tp;
    request.sl = sl;
    
    if(!OrderSend(request, result))
    {
        Print("OrderSend error ", GetLastError());
        return;
    }
    
    if(result.retcode == TRADE_RETCODE_DONE)
    {
        Print("Order placed successfully");
    }
    else
    {
        Print("Order failed with retcode ", result.retcode);
    }
}

//+------------------------------------------------------------------+
//| Check if trading is allowed                                      |
//+------------------------------------------------------------------+
bool IsTradeAllowed()
{
    if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
    {
        Print("Trade is not allowed in the terminal");
        return false;
    }
    
    if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
    {
        Print("Trade is not allowed in the Expert Advisor");
        return false;
    }
    
    return true;
}


시스템의 가능한 개선 사항 및 합법성, 또는 리밋 주문으로 유동성 공급자를 공격하지 않는 방법

우리의 시스템에는 잠재적인 어려움이 있습니다. 브로커와 유동성 공급자는 종종 이런 시스템을 못마땅하게 여깁니다. 왜 그럴까요? 왜냐하면 우리는 본질적으로 시장에서 필요한 유동성을 빼앗고 있기 때문입니다. 그들은 이를 위해 특별한 용어를 만들어냈습니다 - 독성의 주문 흐름 

이건 진짜 문제예요. 우리는 우리의 마켓 주문을 통해 말 그대로 시스템의 유동성을 빨아들입니다. 니는 모든 사람에게 필요합니다. 대형 기업과 소규모 거래자 모두에게요. 물론 이는 결과를 초래합니다.

이런 상황에서 어떻게 해야 할까요? 타협안이 있습니다 - 리밋 주문입니다. 

하지만 이것으로 모든 문제가 해결되는 것은 아닙니다: 독성의 주문 흐름이라는 라벨이 붙는 이유는 시장에서 현재 유동성을 흡수했기 때문이 아니라 그러한 주문 흐름을 처리하는 데 드는 높은 부하 때문입니다. 저는 아직 이 문제를 해결하지 못했습니다. 예를 들어 막대한 양의 차익 거래를 처리하는 데 100달러를 지출하고 그로부터 50달러의 수수료를 받는다면 이는 수익성이 없습니다. 그러므로 여기서 핵심은 높은 매출과 많은 랏, 그리고 빠른 수익 속도일 것입니다. 그러면 브로커도 리베이트를 지불할 준비가 되어 있을 수도 있습니다.

이제 코드로 넘어가겠습니다. 어떻게 개선할 수 있을까요? 첫째 우리는 리밋 주문을 처리하는 함수를 추가할 수 있습니다. 여기에는 많은 작업도 필요합니다 - 실행되지 않은 주문을 기다리고 취소하는 논리를 철저히 생각해야 합니다.

머신 러닝은 시스템을 개선하는 데 흥미로운 아이디어가 될 수 있습니다. 저는 우리의 시스템을 훈련시켜 어떤 차익거래의 기회가 가장 성공할 가능성이 높은지 예측할 수 있게 하는 것이 가능할 것이라고 제안합니다. 


결론

요약해 보겠습니다. 우리는 차익거래의 기회를 찾는 시스템을 만들었습니다. 기억하세요 시스템은 여러분의 모든 재무적 문제를 해결하지는 못합니다. 

우리는 백테스팅을 정리했습니다. 이 기능은 시간 기반 데이터를 활용하며 더 나아가 과거에는 시스템이 어떻게 작동했을지 살펴볼 수 있다는 장점이 있습니다. 하지만 기억하세요. 과거의 결과가 미래의 결과를 보장하는 것은 아닙니다. 시장은 끊임없이 변화하는 복잡한 메커니즘입니다.

하지만 가장 중요한 게 뭔지 아세요? 코드가 아닙니다. 알고리즘이 아닙니다. 바로 여러분.. 배우고, 실험하고, 실수하고, 다시 시도하려는 의지. 이건 정말 값을 매길 수 없을 만큼 귀중한 것입니다.

그러니 거기서 멈추지 마세요. 이 시스템은 여러분이 알고리즘 트레이딩의 세계로 나아가는 여정의 시작일 뿐입니다. 이를 새로운 아이디어와 전략의 출발점으로 삼으세요. 우리의 삶과 마찬가지로 거래에서도 가장 중요한 것은 균형입니다. 위험과 신중함, 탐욕과 합리성, 복잡성과 단순성 사이의 균형.

이 흥미진진한 여정에 대해 행운을 빕니다. 그리고 여러분의 알고리즘이 항상 시장보다 한 발 앞서 나아가기를 바랍니다!

MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/15964

최근 코멘트 | 토론으로 가기 (7)
pivomoe
pivomoe | 24 10월 2024 에서 00:31

무슨 내용인지 설명해 주세요:

А теперь следующий шаг — список pairs. Это наши валютные пары, которые мы будем использовать для синтеза. Дальше начинается еще один процесс. Мы запускаем цикл по всем парам. Для каждой пары мы рассчитываем синтетическую цену двумя способами:

Делим bid первой пары на ask второй.
Делим bid первой пары на bid второй.
И каждый раз мы увеличиваем наш method_count. В итоге у нас получается не 1000, не 1500, а целых 2000 синтетических цен!

다음은 쌍입니다:

pairs = [('AUDUSD', 'USDCHF'), ('AUDUSD', 'NZDUSD'), ('AUDUSD', 'USDJPY'),
             ('USDCHF', 'USDCAD'), ('USDCHF', 'NZDCHF'), ('USDCHF', 'CHFJPY'),
             ('USDJPY', 'USDCAD'), ('USDJPY', 'NZDJPY'), ('USDJPY', 'GBPJPY'),
             ('NZDUSD', 'NZDCAD'), ('NZDUSD', 'NZDCHF'), ('NZDUSD', 'NZDJPY'),
             ('GBPUSD', 'GBPCAD'), ('GBPUSD', 'GBPCHF'), ('GBPUSD', 'GBPJPY'),
             ('EURUSD', 'EURCAD'), ('EURUSD', 'EURCHF'), ('EURUSD', 'EURJPY'),
             ('CADCHF', 'CADJPY'), ('CADCHF', 'GBPCAD'), ('CADCHF', 'EURCAD'),
             ('CHFJPY', 'GBPCHF'), ('CHFJPY', 'EURCHF'), ('CHFJPY', 'NZDCHF'),
             ('NZDCAD', 'NZDJPY'), ('NZDCAD', 'GBPNZD'), ('NZDCAD', 'EURNZD'),
             ('NZDCHF', 'NZDJPY'), ('NZDCHF', 'GBPNZD'), ('NZDCHF', 'EURNZD'),
             ('NZDJPY', 'GBPNZD'), ('NZDJPY', 'EURNZD')]

첫 번째 쌍의 입찰가는 얼마인가요? 첫 번째 쌍입니다:

('AUDUSD', 'USDCHF')
Andrey Khatimlianskii
Andrey Khatimlianskii | 28 10월 2024 에서 16:09
pivomoe #:

첫 번째 쌍의 입찰가는 얼마입니까? 첫 번째 쌍입니다:

AUDUSD도 한 쌍입니다. AUD에서 USD로.

Roman Shiredchenko
Roman Shiredchenko | 28 10월 2024 에서 20:12
pivomoe #:

무슨 내용인지 설명해 주세요:

쌍은 다음과 같습니다:

첫 번째 쌍의 입찰가는 얼마인가요? 첫 번째 쌍입니다:

이것이 합성이 구축되는 방식입니다. 차이가 아니라 분할을 통해. 그리고 단순하지는 않지만... 읽기.....
leonerd
leonerd | 21 11월 2024 에서 10:59
ticks = mt5.copy_ticks_from(symbol, utc_from, count, mt5.COPY_TICKS_ALL)

모두 설치되었습니다. 이것이 틱으로 표시되는 내용입니다:

array([b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'',

...

b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'',

b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'',

b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b''],

dtype='|V0')


그리고 여기서 우리는 이미 제 시간에 출구를 얻었습니다:

ticks_frame['time'] = pd.to_datetime(ticks_frame['time'], unit='s')
leonerd
leonerd | 22 11월 2024 에서 09:11

예제 https://www.mql5.com/ko/docs/python_metatrader5/mt5copyticksfrom_py 의 코드도 작동하지 않습니다.

>>>  timezone = pytz.timezone("Etc/UTC")
>>>  utc_from = datetime(2020, 1, 10, tzinfo=timezone)
>>>  ticks = mt5.copy_ticks_from("EURUSD", utc_from, 100000, mt5.COPY_TICKS_ALL)
>>>
>>> print("수신된 틱:",len(ticks))
Получено тиков: 100000
>>> print("결과물인 틱을 그대로 가져가자.")
Выведем полученные тики как есть
>>>  count = 0
>>> for tick in ticks:
...     count+=1
...     print(tick)
...     if count >= 100:
...         break
...
b''
b''
b''
b''

어쨌든 파이썬은 어떤가요? 어떻게 준비하나요? 불분명합니다...

새로운 기능: MQL5의 커스텀 인디케이터 새로운 기능: MQL5의 커스텀 인디케이터
MetaTrader5와 MQL5의 새로운 기능 전체를 나열하지는 않겠습니다. 종류도 많은 데다가, 별도의 설명이 필요한 기능들도 있거든요. 객체 지향 프로그래밍을 이용한 코드 작성법 또한 다음에 알아보도록 하겠습니다. 다른 기능들과 함께 설명하기에는 조금 어려운 이야기일 수 있으니까요. 이 글에서는 인디케이터와 인디케이터의 구조, 드로잉 타입과 프로그래밍 디테일을 MQL4와 비교해 볼게요. 초보자 분들께 많은 도움이 되면 좋겠고 기존에 사용하시던 개발자 분들도 뭔가 새로운 걸 얻어 가실 수 있길 바랍니다.
MQL5의 테이블 모델에 기반한 테이블 및 헤더 클래스: MVC 개념 적용하기 MQL5의 테이블 모델에 기반한 테이블 및 헤더 클래스: MVC 개념 적용하기
이 글의 두 번째 파트에서는 MVC(모델-뷰-컨트롤러) 아키텍처 패러다임을 사용하여 MQL5에서 테이블 모델을 구현하는 방법에 대해 알아봅니다. 이 문서에서는 이전에 만든 테이블 모델을 기반으로 테이블 클래스와 테이블 헤더를 개발하는 방법에 대해 알아봅니다. 개발된 클래스는 다음 글에서 설명할 뷰 및 컨트롤러 컴포넌트를 추가 구현하는 기반이 될 것입니다.
새 MetaTrader 와 MQL5를 소개해드립니다 새 MetaTrader 와 MQL5를 소개해드립니다
본 문서는 MetaTrader5의 간략 리뷰입니다. 짧은 시간 내에 시스템의 모든 세부 사항을 안내해드리기는 어렵습니다 - 테스트는 2009.09.09에 시작되었습니다. 이는 상징적인 일자로, 전 이것이 행운의 숫자가 될거라 믿어 의심치않습니다. 제가 새 MetaTrader 5 터미널과 MQL5 베타버전을 받은지 며칠이 지났습니다. 아직 모든 기능을 사용해본 것은 아니지만, 벌써부터 감명깊네요.
윌리엄 간 메서드(3부): 점성술은 맞는 건가요? 윌리엄 간 메서드(3부): 점성술은 맞는 건가요?
행성과 별의 위치가 금융 시장에 영향을 미칠까요? 통계와 빅데이터로 무장하고 별과 주식 차트가 교차하는 세계로 흥미진진한 여행을 떠나보세요.