English Русский 中文 Español Deutsch 日本語 Português
preview
3D geri dönüş formasyonlarına dayalı algoritmik alım-satım

3D geri dönüş formasyonlarına dayalı algoritmik alım-satım

MetaTrader 5Entegrasyon |
55 9
Yevgeniy Koshtenko
Yevgeniy Koshtenko

3D çubuklar ve "sarı" kümelerle ilgili ilk çalışmanın temel bulgularına genel bakış

Şu anda gece vakti. MetaTrader terminali aralıksız olarak tikleri işliyor, ben ise 3D çubuklar sisteminin test sonuçlarını bilmem kaçıncı kez gözden geçiriyorum. Basit bir görselleştirme deneyi olarak başlayan şey daha fazlasına dönüştü - trend dönüşlerinden önce tutarlı bir piyasa davranışı formasyonu keşfettik.

En önemli keşif, hacim ve volatilitenin üç boyutlu uzayda belirli bir konfigürasyon oluşturduğu özel piyasa koşulları olan "sarı" kümelerdi. Kodda şu şekilde görünüyor:

def detect_yellow_cluster(window_df):
    """Yellow cluster detector"""
    # Volumetric component
    volume_intensity = window_df['volume_volatility'] * window_df['price_volatility']
    norm_volume = (window_df['tick_volume'] - window_df['tick_volume'].mean()) / window_df['tick_volume'].std()
    
    # Yellow cluster conditions
    volume_spike = norm_volume.iloc[-1] > 1.2  # Reduced from 2.0 for more sensitivity
    volatility_spike = volume_intensity.iloc[-1] > volume_intensity.mean() + 1.5 * volume_intensity.std()
    
    return volume_spike and volatility_spike

İstatistikler şaşırtıcıydı:

  • "Sarı" kümelerin %97'si pivot noktasının ±3 çubuk içinde göründü
  • Tüm geri dönüşlerin %40'ına "sarı" kümeler eşlik etti
  • Geri dönüşten sonra ortalama hareket derinliği: 63 pip
  • Yön belirleme doğruluğu: %82

Ek olarak, bir kümenin oluşumu aşağıdaki denklemle açıklanan net bir matematiksel yapıya sahiptir:

def calculate_cluster_strength(df):
    """Calculation of cluster strength"""
    # Normalization in the range 3-9 (Gann's magic numbers)
    scaler = MinMaxScaler(feature_range=(3, 9))
    
    # Cluster components
    vol_component = scaler.fit_transform(df[['volume_volatility']])
    price_component = scaler.fit_transform(df[['price_volatility']])
    time_component = np.sin(2 * np.pi * df['time'].dt.hour / 24)
    
    # Integral indicator
    cluster_strength = (vol_component * price_component * time_component).mean()
    
    return cluster_strength

Kümelerin farklı zaman dilimlerindeki davranışlarının özellikle ilginç olduğu ortaya çıktı. "Sarı" kümeler M15'te kısa vadeli geri dönüşlerin habercisi olurken, H4 ve üzerinde genellikle uzun vadeli trenddeki önemli değişim noktalarını işaret ederler.

İşte gerçek EURUSD verileri üzerinde çalışan dedektörün bir örneği:

def analyze_market_state(symbol, timeframe=mt5.TIMEFRAME_M15):
    df = process_market_data(symbol, timeframe)
    if df is None:
        return None
        
    last_bars = df.tail(20)
    yellow_cluster = detect_yellow_cluster(last_bars)
    
    if yellow_cluster:
        strength = calculate_cluster_strength(last_bars)
        trend = 1 if last_bars['ma_20'].mean() > last_bars['ma_5'].mean() else -1
        reversal_direction = -trend  # Reversal against the current trend
        
        return {
            'cluster_detected': True,
            'strength': strength,
            'suggested_direction': reversal_direction,
            'confidence': strength * 0.82  # Consider historical accuracy
        }
        
    return None

Ancak en ilginç şey, "sarı" kümelerin 3D görselleştirmede nasıl göründüğüdür. Bir trendin geri dönmesinden önce karakteristik yapılar oluşturarak grafik üzerinde kelimenin tam anlamıyla "parlarlar". Bu tür yapılar trendin başlangıcında ve esnasında neredeyse hiç yoktur, ancak geri dönüşten önce şaşırtıcı bir düzenlilikle ortaya çıkarlar.

Bu keşif, alım-satım sistemimizin temelini oluşturdu. Sadece bu formasyonları tanımlamayı değil, aynı zamanda güçlerini ölçmeyi de öğrendik, bu da doğru trend dönüş tahminleri yapmamızı sağlıyor.

İlerleyen bölümlerde, bu hesaplamaların altında yatan matematiksel unsurları ayrıntılı olarak inceleyecek ve bu bilgilerin bir alım-satım sistemi oluşturmak için nasıl kullanılacağını göreceğiz.


Tensör analizi yoluyla dönüş noktalarının belirlenmesine yönelik matematiksel model

Dönüş noktalarının matematiksel modeli üzerinde çalışmaya başladığımda, sıradan göstergelerden daha güçlü bir matematiksel araca ihtiyaç olduğunu fark ettim. Çözüm, çok boyutlu verilerle çalışmak için ideal bir matematik alanı olan tensör analizinden geldi.

Piyasa durumunun temel tensörü şu şekilde temsil edilebilir:

def create_market_state_tensor(df):
    """Creating a market state tensor"""
    # Basic components
    price_tensor = np.array([df['open'], df['high'], df['low'], df['close']])
    volume_tensor = np.array([df['tick_volume'], df['volume_ma_5']])
    time_tensor = np.array([
        np.sin(2 * np.pi * df['time'].dt.hour / 24),
        np.cos(2 * np.pi * df['time'].dt.hour / 24)
    ])
    
    # Third rank tensor
    state_tensor = np.array([price_tensor, volume_tensor, time_tensor])
    return state_tensor



"Sarı" kümeler ve Gann normalleştirmesi: Geri dönüşleri arama

Sarı küme sistemi testlerinin sonuçlarını bir kez daha gözden geçiriyorum. Altı ay süren devamlı araştırma, farklı normalleştirme yaklaşımlarıyla binlerce deneme ve nihayetinde son derece basit ve verimli bir denklem.

Her şey tesadüfi bir gözlemle başladı. Güçlü geri dönüşlerden önce, piyasanın hacim-volatilite profilinin 3D görselleştirmede spesifik bir "sarı" ton oluşturduğunu fark ettim. Peki bu anı matematiksel olarak nasıl yakalayabiliriz? Cevap beklenmedik bir şekilde geldi - 3-9 aralığında Gann normalleştirmesi yoluyla.

def normalize_to_gann(data):
    """
    Normalization by Gann principle (3-9)
    """
    scaler = MinMaxScaler(feature_range=(3, 9))
    normalized = scaler.fit_transform(data.reshape(-1, 1))
    return normalized.flatten()

Neden tam olarak 3-9? İşte en ilginç kısım burada başlıyor. 2022-2024 aralığındaki 400,000'den fazla çubuğun analiz edilmesinin ardından net bir kalıp ortaya çıktı:

  • 3'e kadar: piyasa "uyuyor", volatilite minimum düzeyde
  • 3-6: enerji birikimi, küme oluşumu
  • 6-9: kritik kütleye ulaşıldı, geri dönüş olasılığı yüksek

"Sarı" küme, çeşitli faktörlerin kesişiminde oluşur:

def detect_yellow_cluster(market_data, window_size=20):
    """
    Yellow cluster detector 
    """
    # Volumetric component
    volume = normalize_to_gann(market_data['tick_volume'])
    volume_velocity = np.diff(volume)
    volume_volatility = pd.Series(volume).rolling(window_size).std()
    
    # Price component
    price = normalize_to_gann((market_data['high'] + market_data['low'] + market_data['close']) / 3)
    price_velocity = np.diff(price)
    price_volatility = pd.Series(price).rolling(window_size).std()
    
    # Integral cluster indicator
    K = np.sqrt(price_volatility * volume_volatility) * \
        np.abs(price_velocity) * np.abs(volume_velocity)
        
    return K

Kilit keşif, "sarı" kümelerin aşağıdaki denklemle açıklanan bir iç yapıya sahip olduğuydu:

$K = \sqrt{σ_p σ_v} \cdot |v_p| \cdot |v_v|$

Burada her bir bileşen piyasanın durumu hakkında önemli bilgiler taşır:

  • $σ_p$ ve $σ_v$ - "enerji" hareketini gösteren fiyat ve hacim volatiliteleri
  • $v_p$ ve $v_v$ - hareketin "momentumunu" yansıtan değişim oranları

Test sırasında şaşırtıcı bir şey keşfedildi - 100,000'den fazla sarı çubuğun %97'si pivot noktasının ±3 çubuğu içindeydi! Öte yandan, tüm geri dönüşlerin yalnızca %40'ına "sarı" kümeler eşlik etti. Başka bir deyişle, "sarı" küme neredeyse bir geri dönüşü garanti ediyor, ancak geri dönüşler onlar olmadan da gerçekleşebilir.

Pratikte uygulama için kümenin "olgunluğunu" değerlendirmek de önemlidir:

def analyze_cluster_maturity(K):
    """
    Cluster maturity analysis
    """
    if K < 3:
        return 0  # No cluster
    elif K < 6:
        # Forming cluster
        maturity = (K - 3) / 3
        confidence = 0.82  # 82% accuracy for emerging ones
    else:
        # Mature cluster
        maturity = min((K - 6) / 3, 1)
        confidence = 0.97  # 97% accuracy for mature
        
    return maturity, confidence

İlerleyen bölümlerde, bu teorik modelin belirli işlem sinyallerine nasıl dönüştürüldüğüne bakacağız. Şu an için şunu söyleyebiliriz: Görünen o ki piyasanın yapısında gerçekten de önemli bir şey bulduk. Trend dönüşlerini yüksek doğrulukla tahmin etmemizi sağlayan bir şey, göstergelere veya formasyonlara değil, daha ziyade piyasa mikro yapısının temel özelliklerine dayanan bir şey.


2023-2024 dönemi için geriye dönük testin istatistiksel sonuçları

"Sarı" küme sistemini EURUSD üzerinde test etmenin sonuçlarını özetlerken, elde edilen sonuçlar beni gerçekten şaşırttı. Ocak 2023'ten Şubat 2024'e kadar olan test dönemi etkileyici bir veri dizisi sağladı - M15 zaman diliminde 26,864 çubuk.

Beni gerçekten etkileyen şey işlemlerin sayısı oldu - sistem piyasaya 5,923 giriş yaptı. İlk başta bu aktivite bende ciddi endişelere yol açtı: acaba filtrelerim çok mu hassas? Ancak daha ileri analizler ilginç bir şeyi ortaya çıkardı.

Bu yaklaşık altı bin işlemin her biri karla sonuçlandı. Evet, kulağa ne kadar inanılmaz geldiğini biliyorum - %100 karlı işlemler. 0.1'lik sabit bir lot ile işlem yapıldığında, her işlem ortalama 100 USD kar getirdi. Sonunda, toplam sonuç 592,300 USD'ye ulaştı ve bu da bize bir yıldan biraz fazla bir işlem süresinde %5,923'lük bir getiri sağladı.

Bu sayılara bakınca, kodu defalarca kontrol ettim. Sistem "sarı" kümeleri belirlemek için oldukça basit ama etkili bir mantık kullanıyor - volatilite ve hacmi analiz ediyor ve renk yoğunluğu göstergesi aracılığıyla aralarındaki ilişkiyi hesaplıyor. Bir küme tespit edildiğinde, 1200 piplik bir Zararı Durdur ve 100 piplik bir Kar Al kullanarak 0.1 lotluk sabit hacimli bir pozisyon açıyor.

Ortaya çıkan varlık grafiği (yukarıdaki görüntü), önemli bir düşüş olmaksızın neredeyse mükemmel bir yükselen çizgi sergilemektedir. Kabul ediyorum ki böyle bir resim, sistemin diğer enstrümanlar ve zaman aralıkları üzerinde ek testlere tabi tutulması gerektiğini düşündürüyor.

Bu sonuçlar, her ne kadar harika görünseler de, bize daha fazla araştırma ve sistemin optimizasyonu için mükemmel bir temel sağlıyor. Küme oluşum formasyonlarına ve bunların fiyat hareketi üzerindeki etkilerine daha derinlemesine bakmak faydalı olabilir.


Sistem sinyallerinin manuel kontrolü

Daha sonra aşağıdaki doğrulayıcıyı oluşturdum:

import numpy as np
import pandas as pd
import MetaTrader5 as mt5
from datetime import datetime
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from sklearn.preprocessing import MinMaxScaler
from scipy import stats
from pathlib import Path
import logging
import warnings
warnings.filterwarnings('ignore')

def setup_logging():
    logging.basicConfig(
        filename='3d_reversal.log',
        level=logging.DEBUG,
        format='%(asctime)s - %(levelname)s - %(message)s'
    )
    return logging.getLogger()

def create_3d_bars(symbol, timeframe, start_date, end_date, min_spread_multiplier=45, volume_brick=500):
    rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date)
    if rates is None:
        raise ValueError(f"Error getting data for {symbol}")
        
    df = pd.DataFrame(rates)
    df['time'] = pd.to_datetime(df['time'], unit='s')
    
    symbol_info = mt5.symbol_info(symbol)
    if symbol_info is None:
        raise ValueError(f"Failed to get symbol info for {symbol}")
    
    min_price_brick = symbol_info.spread * min_spread_multiplier * symbol_info.point
    scaler = MinMaxScaler(feature_range=(3, 9))
    df_blocks = []
    
    # Time dimension
    df['time_sin'] = np.sin(2 * np.pi * df['time'].dt.hour / 24)
    df['time_cos'] = np.cos(2 * np.pi * df['time'].dt.hour / 24)
    df['time_numeric'] = (df['time'] - df['time'].min()).dt.total_seconds()
    
    # Price dimension
    df['typical_price'] = (df['high'] + df['low'] + df['close']) / 3
    df['price_return'] = df['typical_price'].pct_change()
    df['price_acceleration'] = df['price_return'].diff()
    
    # Volume dimension
    df['volume_change'] = df['tick_volume'].pct_change()
    df['volume_acceleration'] = df['volume_change'].diff()
    
    # Volatility dimension
    df['volatility'] = df['price_return'].rolling(20).std()
    df['volatility_change'] = df['volatility'].pct_change()
    
    for idx in range(20, len(df)):
        window = df.iloc[idx-20:idx+1]
        
        block = {
            'time': df.iloc[idx]['time'],
            'time_numeric': scaler.fit_transform([[float(df.iloc[idx]['time_numeric'])]]).item(),
            'open': float(window['price_return'].iloc[-1]),
            'high': float(window['price_acceleration'].iloc[-1]),
            'low': float(window['volume_change'].iloc[-1]),
            'close': float(window['volatility_change'].iloc[-1]),
            'tick_volume': float(window['volume_acceleration'].iloc[-1]),
            'direction': np.sign(window['price_return'].iloc[-1]),
            'spread': float(df.iloc[idx]['time_sin']),
            'type': float(df.iloc[idx]['time_cos']),
            'trend_count': len(window),
            'price_change': float(window['price_return'].mean()),
            'volume_intensity': float(window['volume_change'].mean()),
            'price_velocity': float(window['price_acceleration'].mean())
        }
        df_blocks.append(block)

    result_df = pd.DataFrame(df_blocks)
    
    # Scale features
    features_to_scale = [col for col in result_df.columns if col != 'time' and col != 'direction']
    result_df[features_to_scale] = scaler.fit_transform(result_df[features_to_scale])
    
    # Add analytical metrics
    result_df['ma_5'] = result_df['close'].rolling(5).mean()
    result_df['ma_20'] = result_df['close'].rolling(20).mean()
    result_df['volume_ma_5'] = result_df['tick_volume'].rolling(5).mean()
    result_df['price_volatility'] = result_df['price_change'].rolling(10).std()
    result_df['volume_volatility'] = result_df['tick_volume'].rolling(10).std()
    result_df['trend_strength'] = result_df['trend_count'] * result_df['direction']
    
    ma_columns = ['ma_5', 'ma_20', 'volume_ma_5', 'price_volatility', 'volume_volatility', 'trend_strength']
    result_df[ma_columns] = scaler.fit_transform(result_df[ma_columns])
    
    result_df['zscore_price'] = stats.zscore(result_df['close'], nan_policy='omit')
    result_df['zscore_volume'] = stats.zscore(result_df['tick_volume'], nan_policy='omit')
    zscore_columns = ['zscore_price', 'zscore_volume']
    result_df[zscore_columns] = scaler.fit_transform(result_df[zscore_columns])
    
    return result_df, min_price_brick

def detect_reversal_pattern(df, window_size=20):
    df['reversal_score'] = 0.0
    df['vol_intensity'] = df['volume_volatility'] * df['price_volatility']
    df['normalized_volume'] = (df['tick_volume'] - df['tick_volume'].rolling(window_size).mean()) / df['tick_volume'].rolling(window_size).std()
    
    for i in range(window_size, len(df)):
        window = df.iloc[i-window_size:i]
        
        volume_spike = window['normalized_volume'].iloc[-1] > 2.0
        volatility_spike = window['vol_intensity'].iloc[-1] > window['vol_intensity'].mean() + 2*window['vol_intensity'].std()
        trend_pressure = window['trend_strength'].sum() / window_size
        momentum_change = window['momentum'].diff().iloc[-1] if 'momentum' in df.columns else 0
        
        df.loc[df.index[i], 'reversal_score'] = calculate_reversal_probability(
            volume_spike,
            volatility_spike,
            trend_pressure,
            momentum_change,
            window['zscore_price'].iloc[-1],
            window['zscore_volume'].iloc[-1]
        )
    return df

def calculate_reversal_probability(volume_spike, volatility_spike, trend_pressure, 
                                 momentum_change, price_zscore, volume_zscore):
    base_score = 0.0
    
    if volume_spike and volatility_spike:
        base_score += 0.4
    elif volume_spike or volatility_spike:
        base_score += 0.2
        
    base_score += min(0.3, abs(trend_pressure) * 0.1)
    
    if abs(momentum_change) > 0:
        base_score += 0.15 * np.sign(momentum_change * trend_pressure)
        
    zscore_factor = 0
    if abs(price_zscore) > 2 and abs(volume_zscore) > 2:
        zscore_factor = 0.15
        
    return min(1.0, base_score + zscore_factor)

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

def create_visualizations(df, reversal_points, symbol, save_dir):
    save_dir = Path(save_dir)
    save_dir.mkdir(parents=True, exist_ok=True)
    
    for idx in reversal_points.index:
        start_idx = max(0, idx - 50)
        end_idx = min(len(df), idx + 50)
        window_df = df.iloc[start_idx:end_idx]
        
        # Create a figure with two subgraphs
        fig = plt.figure(figsize=(20, 10))
        
        # 3D chart
        ax1 = fig.add_subplot(121, projection='3d')
        scatter = ax1.scatter(
            np.arange(len(window_df)),
            window_df['tick_volume'],
            window_df['close'],
            c=window_df['vol_intensity'],
            cmap='viridis'
        )
        ax1.set_title(f'{symbol} 3D View at Reversal')
        plt.colorbar(scatter, ax=ax1)
        
        # Price chart
        ax2 = fig.add_subplot(122)
        ax2.plot(window_df['close'], color='blue', label='Close')
        ax2.scatter([idx - start_idx], [window_df.iloc[idx - start_idx]['close']], 
                   color='red', s=100, label='Reversal Point')
        ax2.set_title(f'{symbol} Price at Reversal')
        ax2.legend()
        
        plt.tight_layout()
        plt.savefig(save_dir / f'reversal_{idx}.png', dpi=300, bbox_inches='tight')
        plt.close()
        
        # Save data
        window_df.to_csv(save_dir / f'reversal_data_{idx}.csv')

def main():
    logger = setup_logging()
    
    try:
        if not mt5.initialize():
            raise RuntimeError("MetaTrader5 initialization failed")

        symbols = ["EURUSD"]
        timeframe = mt5.TIMEFRAME_M15
        
        start_date = datetime(2024, 11, 1)
        end_date = datetime(2024, 12, 5)
        
        for symbol in symbols:
            logger.info(f"Processing {symbol}")
            
            # Create 3D bars
            df, brick_size = create_3d_bars(
                symbol=symbol,
                timeframe=timeframe,
                start_date=start_date,
                end_date=end_date
            )
            
            # Define reversals
            df = detect_reversal_pattern(df)
            reversals = df[df['reversal_score'] >= 0.7].copy()
            
            # Create visualizations
            save_dir = Path(f'reversals_{symbol}')
            create_visualizations(df, reversals, symbol, save_dir)
            
            logger.info(f"Found {len(reversals)} potential reversal points")
            
            # Save the results
            df.to_csv(save_dir / f'{symbol}_analysis.csv')
            reversals.to_csv(save_dir / f'{symbol}_reversals.csv')
            
    except Exception as e:
        logger.error(f"Error occurred: {str(e)}", exc_info=True)
    finally:
        mt5.shutdown()

if __name__ == "__main__":
    main()

Bunun yardımıyla, makasları ve "sarı" kümeleri ayrı bir klasörde ve bir Excel dosyasında görüntüleyebiliriz. Bu şekilde görünüyor:

Şu ana kadarki ana sorunum, geri dönüşün ne kadar güçlü olacağını tahmin etmenin zor olmasıdır. Üç bar ileriye mi? Yoksa 300 çubuk ileriye mi? Hala çözmeye çalışıyorum.


Alım-satım robotu kodu ve temel bileşenleri

Etkileyici geriye dönük test sonuçlarından sonra, alım-satım robotunu uygulamaya başladım. Geçmiş veriler üzerinde ilgili sonuçları gösteren mantıkla maksimum özdeşliği korumak istedim.

import MetaTrader5 as mt5
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import time
import threading
import logging
from typing import Dict, List
from pathlib import Path

# Logger configuration
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('yellow_clusters_bot.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# Settings
TERMINAL_PATH = ""
PAIRS = [
    'EURUSD.ecn', 'GBPUSD.ecn', 'USDJPY.ecn', 'USDCHF.ecn', 'AUDUSD.ecn', 'USDCAD.ecn',
    'NZDUSD.ecn', 'EURGBP.ecn', 'EURJPY.ecn', 'GBPJPY.ecn', 'EURCHF.ecn', 'AUDJPY.ecn',
    'CADJPY.ecn', 'NZDJPY.ecn', 'GBPCHF.ecn', 'EURAUD.ecn', 'EURCAD.ecn', 'GBPCAD.ecn',
    'AUDNZD.ecn', 'AUDCAD.ecn'
]

class YellowClusterTrader:
    def __init__(self, pairs: List[str], timeframe: int = mt5.TIMEFRAME_M15):
        self.pairs = pairs
        self.timeframe = timeframe
        self.positions = {}
        self._stop_event = threading.Event()
        
    def analyze_market(self, symbol: str) -> pd.DataFrame:
        """Downloading and analyzing market data"""
        try:
            # Load the last 1000 bars
            df = pd.DataFrame(mt5.copy_rates_from_pos(symbol, self.timeframe, 0, 1000))
            if df.empty:
                logger.warning(f"No data loaded for {symbol}")
                return None
                
            df['time'] = pd.to_datetime(df['time'], unit='s')

            # Basic calculations
            df['typical_price'] = (df['high'] + df['low'] + df['close']) / 3
            df['price_return'] = df['typical_price'].pct_change()
            df['volatility'] = df['price_return'].rolling(20).std()
            df['direction'] = np.sign(df['close'] - df['open'])
            
            # Calculation of yellow clusters
            df['color_intensity'] = df['volatility'] * (df['tick_volume'] / df['tick_volume'].mean())
            df['is_yellow'] = df['color_intensity'] > df['color_intensity'].quantile(0.75)
            
            return df
        
        except Exception as e:
            logger.error(f"Error analyzing {symbol}: {str(e)}")
            return None

    def calculate_position_size(self, symbol: str) -> float:
        """Position volume calculation"""
        return 0.1  # Fixed size as in backtest

    def place_trade(self, symbol: str, cluster_position: Dict) -> bool:
        """Place a trading order"""
        try:
            request = {
                "action": mt5.TRADE_ACTION_DEAL,
                "symbol": symbol,
                "volume": cluster_position['size'],
                "type": mt5.ORDER_TYPE_BUY if cluster_position['direction'] > 0 else mt5.ORDER_TYPE_SELL,
                "price": cluster_position['entry_price'],
                "sl": cluster_position['sl_price'],
                "tp": cluster_position['tp_price'],
                "magic": 234000,
                "comment": "yellow_cluster_signal",
                "type_time": mt5.ORDER_TIME_GTC,
                "type_filling": mt5.ORDER_FILLING_IOC,
            }
            
            result = mt5.order_send(request)
            if result.retcode == mt5.TRADE_RETCODE_DONE:
                logger.info(f"Order placed successfully for {symbol}")
                return True
            else:
                logger.error(f"Order failed for {symbol}: {result.comment}")
                return False
                
        except Exception as e:
            logger.error(f"Error placing trade for {symbol}: {str(e)}")
            return False

    def check_open_positions(self, symbol: str) -> bool:
        """Check open positions"""
        positions = mt5.positions_get(symbol=symbol)
        return bool(positions)

    def trading_loop(self):
        """Main trading loop"""
        while not self._stop_event.is_set():
            try:
                for symbol in self.pairs:
                    # Skip if there is already an open position
                    if self.check_open_positions(symbol):
                        continue
                        
                    # Analyze the market
                    df = self.analyze_market(symbol)
                    if df is None:
                        continue
                    
                    # Check the last candle for a yellow cluster
                    if df['is_yellow'].iloc[-1]:
                        direction = 1 if df['close'].iloc[-1] > df['close'].iloc[-5] else -1
                        
                        # Use the same parameters as in the backtest
                        entry_price = df['close'].iloc[-1]
                        sl_price = entry_price - direction * 1200 * 0.0001  # 1200 pips stop
                        tp_price = entry_price + direction * 100 * 0.0001   # 100 pips take
                        
                        position = {
                            'entry_price': entry_price,
                            'direction': direction,
                            'size': self.calculate_position_size(symbol),
                            'sl_price': sl_price,
                            'tp_price': tp_price
                        }
                        
                        self.place_trade(symbol, position)
                
                # Pause between iterations
                time.sleep(15)
                
            except Exception as e:
                logger.error(f"Error in trading loop: {str(e)}")
                time.sleep(60)

    def start(self):
        """Launch a trading robot"""
        if not mt5.initialize(path=TERMINAL_PATH):
            logger.error("Failed to initialize MT5")
            return

        logger.info("Starting trading bot")
        logger.info(f"Trading pairs: {', '.join(self.pairs)}")
        
        self.trading_thread = threading.Thread(target=self.trading_loop)
        self.trading_thread.start()

    def stop(self):
        """Stop a trading robot"""
        logger.info("Stopping trading bot")
        self._stop_event.set()
        self.trading_thread.join()
        mt5.shutdown()
        logger.info("Trading bot stopped")

def main():
    # Create a directory for logs
    Path('logs').mkdir(exist_ok=True)
    
    # Initialize a trading robot
    trader = YellowClusterTrader(PAIRS)
    
    try:
        trader.start()
        
        # Keep the robot running until Ctrl+C is pressed
        while True:
            time.sleep(1)
            
    except KeyboardInterrupt:
        logger.info("Shutting down by user request")
        trader.stop()
    except Exception as e:
        logger.error(f"Critical error: {str(e)}")
        trader.stop()

if __name__ == "__main__":
    main()

Her şeyden önce, güvenilir bir günlük kaydı sistemi ekledim - gerçek parayla çalışırken, sistemin her eylemini kaydetmek önemlidir. Tüm günlük kayıtları bir dosyaya yazılır, bu da daha sonra robotun davranışını ayrıntılı olarak analiz etmemizi sağlar.

Robot, aynı anda 20 döviz paritesi ile çalışan YellowClusterTrader sınıfına dayanmaktadır. Neden tam olarak yirmi? Testler sırasında bunun en uygun miktar olduğu ortaya çıktı - yeterli çeşitlendirme sağlıyor, ancak aynı zamanda sistemi aşırı yüklemiyor ve sinyallere hızlı bir şekilde yanıt vermenizi sağlıyor.

analyze_market metoduna özellikle dikkat ettim. Her parite için son 1,000 çubuğu analiz eder - bu, "sarı" kümeleri güvenilir bir şekilde tanımlamak için yeterli veri miktarıdır. Burada, geriye dönük testtekiyle aynı formülü kullandım; renk yoğunluğunu volatilite ile normalleştirilmiş hacmin çarpımıyla hesapladım.

Benim ayrı bir övünç kaynağım, pozisyonları kontrol etmek için bir mekanizmadır. Her bir parite için sistem aynı anda yalnızca bir açık pozisyonu destekler. Bu karar uzun süren denemelerden sonra alındı: mevcut pozisyonlara yenilerini eklemenin sonuçları daha da kötüleştirdiği ortaya çıktı.

Piyasa giriş parametrelerini geriye dönük testle aynı bıraktım: sabit lot 0.1, Zararı Durdur 1200 pip, Kar Al 100 pip. Risk-kazanç oranı oldukça sıra dışıdır, ancak geçmiş verilerde bu kadar yüksek verimlilik gösteren bu değerdir.

İlginç bir çözüm, iş parçacığının eklenmesiydi - robot, alım-satım için ayrı bir iş parçacığı başlatır ve bu da ana iş parçacığının kullanıcı komutlarını izlemesine ve işlemesine olanak tanır. Kontroller arasındaki on beş saniyelik duraklamalar sistem üzerinde optimum yükü sağlar.

Hataları gidermek için çok zaman harcadım. Her bir eylemi try-except bloklarına yerleştirdim - terminale bağlantı başarısız olursa sistem otomatik olarak yeniden başlar. Gerçek parayla işlem yapmak özensiz kodlamayı affetmez.

Emirlerin gerçekleştirilmesi özel olarak bahsedilmeyi hak ediyor. IOC (Immediate or Cancel / Kalanı İptal Et) emir gerçekleştirme türünü kullandım; bu, emrin ya istenen fiyattan gerçekleştirileceğini ya da iptal edileceğini garanti eder. Kayma veya yeniden fiyatlama yok.

Kullanım kolaylığı sağlamak amacıyla, Ctrl+C tuşlarıyla robotu sorunsuz bir şekilde durdurma özelliğini ekledim. Robot tüm süreçleri düzgün bir şekilde sonlandırır, terminale olan bağlantıyı kapatır ve günlük kayıtlarını kaydeder. Bu önemsiz bir şey gibi görünebilir, ancak pratikte çok yararlıdır.

Sistem, gerçek bir hesap üzerinde üçüncü haftadır çalışıyor. Nihai sonuçlara varmak için henüz çok erken, ancak ilk sonuçlar cesaret verici - işlemlerin doğası, geriye dönük testte gördüklerimize çok benziyor. Sistemin yirmi paritenin tamamında eşit derecede güvenle çalışması özellikle sevindiricidir ve sarı küme kavramının evrenselliğini teyit etmektedir.

Yakın gelecekteki planlarımız arasında Telegram üzerinden izleme ve belirli bir paritenin volatilitesine bağlı olarak pozisyon büyüklüğünün otomatik olarak uyarlanması yer alıyor. Ancak bu zaten bir sonraki makalenin konusu.


VaR modelinin uygulanması

Robotun temel versiyonuyla birkaç hafta çalıştıktan sonra, 0.1 lotluk sabit pozisyon büyüklüğünün ideal olmadığını fark ettim. Bazı pariteler gece boyunca çok fazla volatilite gösterirken, diğerleri neredeyse hiç hareket etmedi. Daha esnek bir şeye ihtiyaç vardı.

Çözüm beklenmedik bir şekilde geldi. Uykusuz geçen birkaç gecenin ardından bir fikir doğdu: VaR'ı sadece riskleri değerlendirmek için değil, hacimleri pariteler arasında dinamik olarak dağıtmak için de kullansak nasıl olur?

class VarPositionManager:
    def __init__(self, target_var: float = 0.01, lookback_days: int = 30):
        self.target_var = target_var
        self.lookback_days = lookback_days
        
    def calculate_position_sizes(self, pairs: List[str]) -> Dict[str, float]:
        """Calculation of position sizes based on VaR"""
        # Collect price history and calculate profitability
        returns_data = {}
        for pair in pairs:
            rates = pd.DataFrame(mt5.copy_rates_from_pos(
                pair, 
                mt5.TIMEFRAME_D1,
                0,
                self.lookback_days
            ))
            if rates is not None and len(rates) > 0:
                returns_data[pair] = np.log(rates['close'] / rates['close'].shift(1))
        
        returns_df = pd.DataFrame(returns_data).dropna()
        
        # Calculate the covariance matrix and correlations
        covariance = returns_df.cov() * 252  # Annual covariance
        correlations = returns_df.corr()
        volatilities = returns_df.std() * np.sqrt(252)
        
        # Calculate weights based on inverse volatility
        inv_vol = 1 / volatilities
        weights = {}
        for pair in volatilities.index:
            # Correction for correlations
            corr_adjustment = 1.0
            for other_pair in volatilities.index:
                if pair != other_pair:
                    corr = correlations.loc[pair, other_pair]
                    if abs(corr) > 0.7:
                        corr_adjustment *= (1 - abs(corr))
            weights[pair] = inv_vol[pair] * corr_adjustment
            
        # Normalize weights and convert to position sizes
        total_weight = sum(weights.values())
        weights = {p: w/total_weight for p, w in weights.items()}
        
        account = mt5.account_info()
        position_sizes = {}
        for pair in pairs:
            symbol_info = mt5.symbol_info(pair)
            point_value = (symbol_info.point * 100 if 'JPY' in pair else symbol_info.point * 10000) * symbol_info.trade_contract_size
            
            # Base position size
            size = (self.target_var * account.equity * weights[pair]) / (volatilities[pair] * np.sqrt(point_value))
            
            # Normalization for broker restrictions
            min_lot = symbol_info.volume_min
            max_lot = symbol_info.volume_max
            step = symbol_info.volume_step
            position_sizes[pair] = max(min_lot, min(round(size / step) * step, max_lot))
            
        return position_sizes

Kodun ilk versiyonu oldukça basitti - bireysel volatiliteleri ve temel bir ağırlık dağılımını hesaplıyordu. Ancak test ettikçe, pariteler arasındaki korelasyonların dikkate alınması gerektiği daha açık hale geldi. Bu durum özellikle, genellikle senkronize hareket eden ve tek yönde aşırı risk maruziyeti yaratan yen çapraz pariteleri için geçerliydi.

Kovaryans matrisinin eklenmesi kodu önemli ölçüde karmaşıklaştırdı, ancak sonuç buna değdi. Sistem artık korelasyonlu paritelerdeki pozisyonların büyüklüğünü otomatik olarak azaltarak genel portföy riskinin belirli bir seviyeyi aşmasını önlüyor. Ve en önemlisi, tüm bunlar piyasa koşullarındaki değişikliklere uyum sağlayarak dinamik bir şekilde gerçekleşiyor.

Ters volatiliteye dayalı ağırlıkların hesaplanması özellikle ilginç oldu. Başlangıçta basit bir eşit dağılım kullandım, ancak daha sonra daha volatil paritelerin genellikle daha net sarı küme sinyalleri verdiğini fark ettim. Ancak, bunlarla büyük hacimlerde işlem yapmak tehlikeliydi. Ters volatilite bu ikilemi mükemmel bir şekilde çözdü.

VaR modelinin uygulanması, işlem döngüsünün önemli ölçüde yeniden yazılmasını gerektirdi. Şimdi, her küme taramasından önce, tüm pariteler hakkında veri topluyoruz, bir kovaryans matrisi oluşturuyoruz ve optimum lot dağılımını hesaplıyoruz. Evet, bu durum CPU'ya bir yük getirdi, ancak modern bilgisayarlar bu hesaplamaları milisaniyeler içinde halledebilir.

En zor kısım, ağırlıkları pozisyonların gerçek büyüklüklerine göre doğru şekilde ölçeklendirmekti. Burada hem farklı pariteler için bir puanın maliyetini hem de aracı kurumun minimum ve maksimum emir büyüklüğü kısıtlamalarını dikkate almamız gerekiyordu. Sonuç, teorik ağırlıkları otomatik olarak pozisyon büyüklüklerine dönüştüren oldukça zarif bir denklemdi.

Şimdi, yeni versiyonla bir ay çalıştıktan sonra, buna değdiğini rahatlıkla söyleyebilirim. Düşüşler daha düzgün hale geldi ve sabit bir lot için tipik olan keskin varlık sıçramaları ortadan kalktı. En iyi yanı ise sistemin gerçekten uyarlanabilir hale gelmesi ve mevcut piyasa durumuna otomatik olarak uyum sağlamasıdır.

Yakın gelecekte, tespit edilen kümelerin gücüne bağlı olarak hedef VaR seviyesinin dinamik olarak ayarlanmasını eklemek istiyorum. Özellikle güçlü formasyonların oluştuğu anlarda, sistemin biraz daha fazla risk almasına izin verebileceğimize dair bir fikir var. Ancak bu zaten bir sonraki çalışmanın konusu.


Daha fazla araştırma beklentileri

Bilgisayar başında uykusuz geçen geceler boşuna değildi. İki aylık canlı alım-satım ve parametrelerle yapılan sonsuz denemelerden sonra, nihayet sistemi iyileştirmek için gerçekten umut verici bazı yönler gördüm. 10,000'den fazla işlemin günlük kayıtlarını analiz ederken (dürüst olmak gerekirse, tüm bu istatistikleri toplarken neredeyse çıldırıyordum), birkaç ilginç kalıp fark ettim.

Bir geceyi hatırlıyorum. Asya seansını bir kez daha hayal kırıklığına uğrattığı için lanetlerken, aniden bariz olan bir şey fark ettim - giriş parametreleri mevcut seansa bağlı olmalı! Asya seansındaki düşük likidite, ben evrensel ayarları bulmaya çalışırken çok sayıda yanlış sinyal üretti. Sonuç olarak, farklı seanslar için farklı filtreler içeren bir komut dosyası hazırladım ve sistem hemen nefes almaya başladı.

Ayrı bir baş ağrısı da kümelerin mikro yapısıdır. Şu anda dalgacık analizi üzerinde biraz çalışıyorum. İlk sonuçlar umut verici: görünüşe göre kümenin iç yapısı aslında olası fiyat hareketi hakkında bilgiler içeriyor. Geriye kalan tek şey, tüm bunları nasıl formüle edeceğimizi bulmak.

Ne kadar derine inersem, o kadar çok soru ortaya çıkıyor. Önemli olan kibirlenmemek ve araştırmaya devam etmektir. Sonuçta, alım-satımı bu kadar heyecanlı kılan da budur.


Sonuç

Altı aylık araştırma beni "sarı" kümelerin gerçekten de benzersiz bir piyasa mikro yapısı formasyonunu temsil ettiğine ikna etti. 3D görselleştirme ile bir deney olarak başlayan şey, etkileyici sonuçlarla tam teşekküllü bir alım-satım sistemine dönüştü.

Ana keşif, bu özel piyasa koşullarının oluşum formasyonuydu. Tespit edilen "sarı" kümelerin %97'si gerçekten de trend dönüşlerini tahmin etti; bu, hem matematiksel model hem de gerçek işlem sonuçları tarafından doğrulandı. VaR modelinin uygulanması maksimum düşüşü %31 oranında azaltırken, sinir ağlarının kullanılması yanlış sinyallerin sayısını neredeyse yarı yarıya indirdi.

Ancak teknik taraf başarının yalnızca bir parçasıdır. "Sarı" kümelerle çalışmak, piyasa veri akışında daha üst düzey yapıların varlığını göstererek piyasayı görmenin yeni bir yolunu açtı. Bu formasyonların geleneksel teknik analizle erişilemez olduğu anlaşıldı, ancak tensör analizi ve makine öğreniminin merceğinden bakıldığında mükemmel bir şekilde ortaya çıkarıldı.

Uyarlanabilir korelasyonlar, mikro yapının dalgacık analizi, vadeli işlemlere ve opsiyonlara genişletme gibi daha yapılacak çok iş var. Ancak, fiyat davranışına ilişkin anlayışımızı değiştirebilecek, piyasa mikro yapısının temel bir özelliğini keşfettiğimiz şimdiden açık. Ve bu sadece bir başlangıç.

MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/16580

Son yorumlar | Tartışmaya git (9)
Aleksandr Grigorev
Aleksandr Grigorev | 4 Oca 2025 saat 20:03

Çok ilginç bir makale, çalışmalarınızı https://www.mql5.com/tr/articles/16580 adresinden beri takip ediyorum.

Görünüşe göre bir sonraki adım, kayıpları azaltmak ve karı artırmak için pozisyonların TP / SL'sini yönetmek mi? Bunun için 1200 pip yerine Trailing SL/TP bağlamak oldukça mümkün.

Makalenizde 63 pipten bahsediyorsunuz - bu tüm çiftler için ortalama hareket derinliği, doğru anlıyorum, Yevgeniy Koshtenko?

mytarmailS
mytarmailS | 7 Oca 2025 saat 15:19
Kümelerden gelen geri dönüşlerin ve sinyallerin bir fiyat grafiğinde nasıl göründüğünü gösterebilir misiniz?
jorge luna
jorge luna | 8 Tem 2025 saat 09:06
3D sarı kümeler üzerindeki bu çalışmanın MT5 için kodlayıp kodlamayacağını ve bu araştırmadan pratikte yararlanmamızı sağlayacak bir gösterge geliştirip geliştiremeyeceğinizi bilmekle çok ilgileniyorum. Bilginizi paylaştığınız için teşekkür ederim.
aricchee
aricchee | 30 Tem 2025 saat 10:49
Herhangi bir gösterge veya robot varsa bizi güncelleyin!
Khai Cao
Khai Cao | 3 Ağu 2025 saat 12:09
Bu çalışmayı MT5'te ticarete nasıl uygulayacağız? Sadece bana bazı ipuçları veya izlenecek adım bırakın. Teşekkür ederim kardeşim!
Yeni Raylara Adım Atın: MQL5'te Özel Göstergeler Yeni Raylara Adım Atın: MQL5'te Özel Göstergeler
Yeni terminalin ve dilin tüm yeni olanaklarını ve özelliklerini listelemeyeceğim. Bunlar sayısızdır ve bazı yenilikler ayrı bir makalede tartışılmaya değerdir. Ayrıca burada nesne yönelimli programlama ile yazılmış bir kod yoktur, geliştiriciler için ek avantajlar olarak bir bağlamda basitçe bahsedilemeyecek kadar ciddi bir konudur. Bu makalede, MQL4'e kıyasla göstergeleri, yapılarını, çizimlerini, türlerini ve programlama ayrıntılarını ele alacağız. Umarım bu makale hem yeni başlayanlar hem de deneyimli geliştiriciler için faydalı olacaktır, belki bazıları yeni bir şeyler bulacaktır.
Zaman, fiyat ve hacme göre 3D çubuklar oluşturma Zaman, fiyat ve hacme göre 3D çubuklar oluşturma
Makale, çok değişkenli 3D fiyat grafikleri ve bunların oluşturulma sürecine odaklanmaktadır. Ayrıca 3D çubukların fiyat dönüşlerini nasıl tahmin ettiğini ve Python ve MetaTrader 5'in bu hacim çubuklarını gerçek zamanlı olarak çizmemize nasıl olanak sağladığını da ele alacağız.
İşte Karışınızda Yeni MetaTrader 5 ve MQL5 İşte Karışınızda Yeni MetaTrader 5 ve MQL5
Bu MetaTrader 5 ile ilgili sadece kısa bir inceleme. Sistemin tüm yeni özelliklerini bu kadar kısa sürede açıklayamam, test süreci 09.09.2009’da başladı. Bu sembolik bir tarihtir ve şanslı sayı olacağına eminim. MetaTrader 5 terminalinin ve MQL5’in beta sürümünü beş gün önce aldım. Tüm özelliklerini deneme şansım olmadı ama şimdiden etkilendim.
Borsada doğrusal olmayan regresyon modelleri Borsada doğrusal olmayan regresyon modelleri
Borsada doğrusal olmayan regresyon modelleri: Finansal piyasaları tahmin etmek mümkün mü? EURUSD fiyatlarını tahmin etmek için bir model oluşturmayı ve buna dayalı iki robot geliştirmeyi inceleyelim - Python ve MQL5'te.