Zaman, fiyat ve hacme göre 3D çubuklar oluşturma
Giriş
Bu projeye başlayalı altı ay oldu. Bu fikrin ortaya çıkmasının üzerinden yarım yıl geçti; bana aptalca gelmişti, bu yüzden pek üzerinde durmadım, sadece tanıdığım yatırımcılarla tartıştım.
Her şey basit bir soruyla başladı - yatırımcılar neden 2D grafiklere bakarak 3D bir piyasayı analiz etmeye çalışmakta ısrar ediyorlar? Fiyat hareketinin değerlendirilmesi, teknik analiz, dalga teorisi - tüm bunlar piyasanın bir düzleme yansıtılmasıyla yapılıyor. Peki ya fiyat, hacim ve zamanın gerçek yapısını görmeye çalışırsak ne olur?
Algoritmik sistemler üzerine yaptığım çalışmalarda, geleneksel göstergelerin fiyat ve hacim arasındaki kritik ilişkileri gözden kaçırdığı gerçeğiyle sürekli olarak karşılaştım.
3D çubuklar fikri hemen ortaya çıkmadı. İlk olarak, piyasa derinliğinin 3D görselleştirilmesiyle ilgili denemeler yaptım. Ardından hacim-fiyat kümelerinin ilk taslakları belirdi. Zaman bileşenini eklediğimde ve ilk 3D çubuğu oluşturduğumda, bunun piyasayı görmenin temelde yeni bir yolu olduğunu açıkça fark ettim.
Bugün sizlerle bu çalışmanın sonuçlarını paylaşmak istiyorum. Size Python ve MetaTrader 5'in gerçek zamanlı olarak hacim çubukları oluşturmanıza nasıl olanak sağladığını göstereceğim. Hesaplamaların arkasındaki matematikten ve bu bilgilerin pratik alım-satımda nasıl kullanılacağından bahsedeceğim.
3D çubukta farklı olan nedir?
Piyasaya iki boyutlu grafiklerin merceğinden baktığımız sürece, en önemli şeyi, yani gerçek yapısını gözden kaçırırız. Geleneksel teknik analiz fiyat-zaman, hacim-zaman projeksiyonları ile çalışır, ancak bu bileşenlerin etkileşiminin tam resmini asla göstermez.
3D analiz, piyasayı bir bütün olarak görmemizi sağlaması bakımından temelde farklıdır. Bir hacim çubuğu oluşturduğumuzda, kelimenin tam anlamıyla, her bir boyutun kritik bilgiler taşıdığı piyasa durumunun bir "anlık görüntüsünü" oluşturmuş oluruz:
- çubuğun yüksekliği fiyat hareketinin genliğini gösterir
- genişliği zaman ölçeğini yansıtır
- derinliği hacim dağılımını görselleştirir
Bu neden önemli? Bir grafik üzerinde iki özdeş fiyat hareketi hayal edin. İki boyutlu olarak aynı görünürler. Ancak hacim bileşenini eklediğimizde, resim dramatik bir şekilde değişir - bir hareket büyük hacimle desteklenerek derin ve istikrarlı bir çubuk oluştururken, diğeri gerçek işlemler bakımından minimum desteğe sahip yüzeysel bir dalgalanma olabilir.
3D çubukları kullanan entegre bir yaklaşım, teknik analizin klasik bir sorunu olan sinyal gecikmesini çözer. Çubuğun hacimsel yapısı ilk tiklerden itibaren oluşmaya başlar ve güçlü bir hareketin ortaya çıkışını normal bir grafikte görünmeden çok önce fark etmemizi sağlar. Özünde, geçmiş kalıplara değil, mevcut işlemlerin gerçek dinamiklerine dayanan bir tahmine dayalı analiz aracı elde ediyoruz.
Çok değişkenli veri analizi güzel bir görselleştirmeden daha fazlasıdır; piyasa mikro yapısını anlamanın temelde yeni bir yoludur. Her 3D çubuk şu bilgileri içerir:
- hacmin fiyat aralığı içindeki dağılımı
- pozisyon birikim hızı
- alıcılar ve satıcılar arasındaki dengesizlikler
- mikro düzeyde volatilite
- hareket momentumu
Tüm bu bileşenler tek bir mekanizma olarak çalışarak fiyat hareketinin gerçek doğasını görmenizi sağlar. Klasik teknik analizin sadece bir mum veya çubuk gördüğü yerde, 3D analiz arz ve talep etkileşiminin karmaşık yapısını gösterir.
Ana ölçütlerin hesaplanması için denklemler. 7D çubukların oluşturulmasının temel ilkeleri. Farklı boyutları tek bir sistemde birleştirme mantığı
3D çubukların matematiksel modeli, gerçek piyasa mikro yapısının analizinden ortaya çıkmıştır. Sistemdeki her bir çubuk üç boyutlu bir şekil olarak temsil edilebilir:
class Bar3D: def __init__(self): self.price_range = None # Price range self.time_period = None # Time interval self.volume_profile = {} # Volume profile by prices self.direction = None # Movement direction self.momentum = None # Impulse self.volatility = None # Volatility self.spread = None # Average spread
Kilit nokta, çubuk içindeki hacimsel profilin hesaplanmasıdır. Klasik çubuklardan farklı olarak, hacmin fiyat seviyelerine göre dağılımını analiz ediyoruz.
def calculate_volume_profile(self, ticks_data): volume_by_price = defaultdict(float) for tick in ticks_data: price_level = round(tick.price, 5) volume_by_price[price_level] += tick.volume # Normalize the profile total_volume = sum(volume_by_price.values()) for price in volume_by_price: volume_by_price[price] /= total_volume return volume_by_price
Momentum, fiyat ve hacim değişim oranının bir kombinasyonu olarak hesaplanır:
def calculate_momentum(self): price_velocity = (self.close - self.open) / self.time_period volume_intensity = self.total_volume / self.time_period self.momentum = price_velocity * volume_intensity * self.direction
Çubuk içi volatilitenin analizine özellikle dikkat edilir. Hareketin mikro yapısını dikkate alan değiştirilmiş bir ATR denklemi kullanıyoruz:
def calculate_volatility(self, tick_data): tick_changes = np.diff([tick.price for tick in tick_data]) weighted_std = np.std(tick_changes * [tick.volume for tick in tick_data[1:]]) time_factor = np.sqrt(self.time_period) self.volatility = weighted_std * time_factor
Klasik çubuklardan temel farkı, tüm ölçütlerin gerçek zamanlı olarak hesaplanması ve çubuk yapısının oluşumunu görmemize olanak sağlamasıdır:
def update_bar(self, new_tick): self.update_price_range(new_tick.price) self.update_volume_profile(new_tick) self.recalculate_momentum() self.update_volatility(new_tick) # Recalculate the volumetric center of gravity self.volume_poc = self.calculate_poc()
Tüm ölçümler, belirli bir enstrüman için ayarlanmış ağırlık faktörleri sistemi aracılığıyla birleştirilir:
def calculate_bar_strength(self): return (self.momentum_weight * self.normalized_momentum + self.volatility_weight * self.normalized_volatility + self.volume_weight * self.normalized_volume_concentration + self.spread_weight * self.normalized_spread_factor)
Gerçek alım-satımda bu matematiksel model, piyasanın şu yönlerini görmemizi sağlar:
- hacim birikimindeki dengesizlikler
- fiyat oluşum hızındaki anormallikler
- konsolidasyon ve kırılma bölgeleri
- hacim özellikleri aracılığıyla bir trendin gerçek gücü
Her 3D çubuk sadece grafikteki bir nokta değil, aynı zamanda zamanın belirli bir anındaki piyasanın durumunun tam teşekküllü bir göstergesidir.
3D çubuklar oluşturmaya yönelik algoritmanın detaylı bir analizi. MetaTrader 5 ile çalışmanın özellikleri. Veri işlemeyle ilgili ayrıntılar
Ana algoritmanın hata ayıklamasını tamamladıktan sonra, nihayet en ilginç kısma geldim - çok boyutlu çubukların gerçek zamanlı olarak uygulanması. Kabul ediyorum, ilk başta göz korkutucu bir görev gibi görünüyordu. MetaTrader 5, harici komut dosyalarına pek dost değildir ve dokümantasyon bazen uygun bir açıklama sağlamakta yetersiz kalır. Ama sonunda bunun üstesinden nasıl geldiğimi size anlatayım.
Verileri depolamak için temel bir yapı ile başladım. Birkaç deneme ve düzeltmeden sonra, aşağıdaki sınıf ortaya çıktı:
class Bar7D: def __init__(self): self.time = None self.open = None self.high = None self.low = None self.close = None self.tick_volume = 0 self.volume_profile = {} self.direction = 0 self.trend_count = 0 self.volatility = 0 self.momentum = 0
En zor kısım, blok büyüklüğünün nasıl doğru bir şekilde hesaplanacağını bulmaktı. Birçok denemeden sonra bu denklemde karar kıldım:
def calculate_brick_size(symbol_info, multiplier=45): spread = symbol_info.spread point = symbol_info.point min_price_brick = spread * multiplier * point # Adaptive adjustment for volatility atr = calculate_atr(symbol_info.name) if atr > min_price_brick * 2: min_price_brick = atr / 2 return min_price_brick
Hacimlerle ilgili de çok sorun yaşadım. İlk başta, sabit büyüklükte bir volume_brick kullanmak istedim, ancak bunun işe yaramadığını hemen fark ettim. Çözüm, uyarlanabilir bir algoritma şeklinde geldi:
def adaptive_volume_threshold(tick_volume, history_volumes): median_volume = np.median(history_volumes) std_volume = np.std(history_volumes) if tick_volume > median_volume + 2 * std_volume: return median_volume + std_volume return max(tick_volume, median_volume / 2)
Ancak istatistiksel ölçütlerin hesaplanmasında biraz aşırıya kaçtığımı düşünüyorum:
def calculate_stats(df): df['ma_5'] = df['close'].rolling(5).mean() df['ma_20'] = df['close'].rolling(20).mean() df['volume_ma_5'] = df['tick_volume'].rolling(5).mean() df['price_volatility'] = df['price_change'].rolling(10).std() df['volume_volatility'] = df['tick_volume'].rolling(10).std() df['trend_strength'] = df['trend_count'] * df['direction'] # This is probably too much df['zscore_price'] = stats.zscore(df['close'], nan_policy='omit') df['zscore_volume'] = stats.zscore(df['tick_volume'], nan_policy='omit') return df
Komik ama en zor kısmı kodu yazmak değil, gerçek koşullarda hata ayıklamaktı.
İşte 3-9 aralığında normalleştirme içeren fonksiyonun nihai sonucu. Neden 3-9? Hem Gann hem de Tesla bu sayılarda gizli bir tür sihir olduğunu öne sürmüşlerdir. Ayrıca, iyi bilinen bir platformda, bu sayılara dayanarak başarılı bir geri dönüş komut dosyası oluşturduğu söylenen bir yatırımcıyı şahsen gördüm. Ancak komplo teorilerine ve gizemciliğe girmeyelim. Bunun yerine şunu deneyelim:
def create_true_3d_renko(symbol, timeframe, min_spread_multiplier=45, volume_brick=500, lookback=20000): """ Creates 3D Renko bars with extended analytics """ rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, lookback) if rates is None: print(f"Error getting data for {symbol}") return None, None df = pd.DataFrame(rates) df['time'] = pd.to_datetime(df['time'], unit='s') if df.isnull().any().any(): print("Missing values detected, cleaning...") df = df.dropna() if len(df) == 0: print("No data for analysis after cleaning") return None, None symbol_info = mt5.symbol_info(symbol) if symbol_info is None: print(f"Failed to get symbol info for {symbol}") return None, None try: min_price_brick = symbol_info.spread * min_spread_multiplier * symbol_info.point if min_price_brick <= 0: print("Invalid block size") return None, None except AttributeError as e: print(f"Error getting symbol parameters: {e}") return None, None # Convert time to numeric and scale everything scaler = MinMaxScaler(feature_range=(3, 9)) # Convert datetime to numeric (seconds from start) df['time_numeric'] = (df['time'] - df['time'].min()).dt.total_seconds() # Scale all numeric data together columns_to_scale = ['time_numeric', 'open', 'high', 'low', 'close', 'tick_volume'] df[columns_to_scale] = scaler.fit_transform(df[columns_to_scale]) renko_blocks = [] current_price = float(df.iloc[0]['close']) current_tick_volume = 0 current_time = df.iloc[0]['time'] current_time_numeric = float(df.iloc[0]['time_numeric']) current_spread = float(symbol_info.spread) current_type = 0 prev_direction = 0 trend_count = 0 try: for idx, row in df.iterrows(): if pd.isna(row['tick_volume']) or pd.isna(row['close']): continue current_tick_volume += float(row['tick_volume']) volume_bricks = int(current_tick_volume / volume_brick) price_diff = float(row['close']) - current_price if pd.isna(price_diff) or pd.isna(min_price_brick): continue price_bricks = int(price_diff / min_price_brick) if volume_bricks > 0 or abs(price_bricks) > 0: direction = np.sign(price_bricks) if price_bricks != 0 else 1 if direction == prev_direction: trend_count += 1 else: trend_count = 1 renko_block = { 'time': current_time, 'time_numeric': float(row['time_numeric']), 'open': float(row['open']), 'close': float(row['close']), 'high': float(row['high']), 'low': float(row['low']), 'tick_volume': float(row['tick_volume']), 'direction': float(direction), 'spread': float(current_spread), 'type': float(current_type), 'trend_count': trend_count, 'price_change': price_diff, 'volume_intensity': float(row['tick_volume']) / volume_brick, 'price_velocity': price_diff / (volume_bricks if volume_bricks > 0 else 1) } if volume_bricks > 0: current_tick_volume = current_tick_volume % volume_brick if price_bricks != 0: current_price += min_price_brick * price_bricks prev_direction = direction renko_blocks.append(renko_block) except Exception as e: print(f"Error processing data: {e}") if len(renko_blocks) == 0: return None, None if len(renko_blocks) == 0: print("Failed to create any blocks") return None, None result_df = pd.DataFrame(renko_blocks) # Scale derived metrics to same range derived_metrics = ['price_change', 'volume_intensity', 'price_velocity', 'spread'] result_df[derived_metrics] = scaler.fit_transform(result_df[derived_metrics]) # Add analytical metrics using scaled data 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'] # Scale moving averages and volatility 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]) # Add statistical metrics and scale them 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İşte tek bir ölçekte elde ettiğimiz çubuk serisi böyle görünüyor. Pek durağan değil, öyle değil mi?

Doğal olarak böyle bir seriyle tatmin olmadım, çünkü amacım az çok durağan bir seri oluşturmaktı - durağan, zaman-hacim-fiyat serisi. Devamında şunu yaptım:
Volatilite ölçümünün uygulanması
create_stationary_4d_features fonksiyonunu uygularken, temelde farklı bir yol izledim. Verileri basitçe 3-9 aralığına ölçeklediğimiz orijinal 3D çubukların aksine, burada gerçekten durağan seriler oluşturmaya odaklandım.
Fonksiyonun ana fikri, durağan özellikler aracılığıyla piyasanın dört boyutlu bir temsilini oluşturmaktır. Basitçe ölçeklendirmek yerine, durağanlığı sağlamak için her boyut özel bir şekilde dönüştürülür:
- Zaman boyutu: Burada trigonometrik dönüşümü uygulayarak saatleri sinüs ve kosinüs dalgalarına dönüştürdüm. sin(2π * saat/24) ve cos(2π * saat/24) denklemleri döngüsel özellikler yaratarak günlük mevsimsellik sorununu tamamen ortadan kaldırmaktadır.
- Fiyat ölçümü: Mutlak fiyat değerleri yerine, bunların göreceli değişimleri kullanılır. Kodda bu, tipik fiyatın (yüksek + düşük + kapanış)/3 hesaplanması ve ardından getirilerin ve ivmelerinin hesaplanmasıyla uygulanır. Bu yaklaşım, fiyat seviyesinden bağımsız olarak seriyi durağan hale getirmektedir.
- Hacimsel ölçüm: Burada ilginç bir nokta var - sadece hacimlerdeki değişimleri değil, bunların göreceli artışlarını da dikkate alıyoruz. Bu önemlidir çünkü hacimler genellikle çok dengesiz dağılır. Kodda bu, pct_change() ve diff() fonksiyonlarının art arda uygulanması yoluyla gerçekleştirilir.
- Volatilitenin ölçülmesi: Burada iki aşamalı bir dönüşüm uyguladım - önce getirilerin standart sapması yoluyla devam eden volatiliteyi hesaplıyoruz ve ardından bu volatilitedeki göreceli değişimleri elde ediyoruz. Aslında, "volatilitenin volatilitesini" elde ediyoruz.
Her veri bloğu 20 periyotluk bir kayan pencerede oluşturulur. Bu rastgele bir sayı değildir - verilerin yerel yapısını korumak ve hesaplamaların istatistiksel anlamlılığını sağlamak arasında bir denge kurmak amacıyla seçilmiştir.
Hesaplanan tüm özellikler sonuçta 3-9 aralığına ölçeklendirilir, ancak bu halihazırda durağan serilere uygulanan ikincil bir dönüşümdür. Bu, veri ön işleme için temelde farklı bir yaklaşım kullanırken 3D çubukların orijinal uygulanışıyla uyumluluğu sürdürmemizi sağlar.
Özellikle önemli bir nokta, orijinal fonksiyondaki tüm temel ölçütleri korumaktır - hareketli ortalamalar, volatilite, z-skorları. Bu, yeni uygulanışın orijinal fonksiyonun yerine doğrudan kullanılmasına ve daha yüksek kalitede durağan veriler elde edilmesine olanak tanır.
Sonuç olarak, istatistiksel anlamda durağan olmakla kalmayıp aynı zamanda piyasa yapısıyla ilgili tüm önemli bilgileri koruyan bir özellikler kümesi elde ediyoruz. Bu yaklaşım, verileri makine öğrenimi ve istatistiksel analiz yöntemlerini uygulamak için çok daha uygun hale getirirken, orijinal alım-satım bağlamıyla olan bağlantısını da sürdürür.
Fonksiyon aşağıda yer almaktadır:
def create_true_3d_renko(symbol, timeframe, min_spread_multiplier=45, volume_brick=500, lookback=20000): """ Creates 4D stationary features with same interface as 3D Renko """ rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, lookback) if rates is None: print(f"Error getting data for {symbol}") return None, None df = pd.DataFrame(rates) df['time'] = pd.to_datetime(df['time'], unit='s') if df.isnull().any().any(): print("Missing values detected, cleaning...") df = df.dropna() if len(df) == 0: print("No data for analysis after cleaning") return None, None symbol_info = mt5.symbol_info(symbol) if symbol_info is None: print(f"Failed to get symbol info for {symbol}") return None, None try: min_price_brick = symbol_info.spread * min_spread_multiplier * symbol_info.point if min_price_brick <= 0: print("Invalid block size") return None, None except AttributeError as e: print(f"Error getting symbol parameters: {e}") return None, None scaler = MinMaxScaler(feature_range=(3, 9)) df_blocks = [] try: # 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) except Exception as e: print(f"Error processing data: {e}") if len(df_blocks) == 0: return None, None if len(df_blocks) == 0: print("Failed to create any blocks") return None, None result_df = pd.DataFrame(df_blocks) # Scale all 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 same analytical metrics as in original function 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'] # Scale moving averages and volatility 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]) # Add statistical metrics and scale them 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
2D olarak şöyle görünüyor:

Şimdi, plotly kullanarak 3D fiyatlar için etkileşimli bir 3D model oluşturmayı deneyelim. Yan tarafında normal iki boyutlu bir grafikte görünmelidir. İşte kod:
import plotly.graph_objects as go from plotly.subplots import make_subplots def create_interactive_3d(df, symbol, save_dir): """ Creates interactive 3D visualization with smoothed data and original price chart """ try: save_dir = Path(save_dir) # Smooth all series with MA(100) df_smooth = df.copy() smooth_columns = ['close', 'tick_volume', 'price_volatility', 'volume_volatility'] for col in smooth_columns: df_smooth[f'{col}_smooth'] = df_smooth[col].rolling(window=100, min_periods=1).mean() # Create subplots: 3D view and original chart side by side fig = make_subplots( rows=1, cols=2, specs=[[{'type': 'scene'}, {'type': 'xy'}]], subplot_titles=(f'{symbol} 3D View (MA100)', f'{symbol} Original Price'), horizontal_spacing=0.05 ) # Add 3D scatter plot fig.add_trace( go.Scatter3d( x=np.arange(len(df_smooth)), y=df_smooth['tick_volume_smooth'], z=df_smooth['close_smooth'], mode='markers', marker=dict( size=5, color=df_smooth['price_volatility_smooth'], colorscale='Viridis', opacity=0.8, showscale=True, colorbar=dict(x=0.45) ), hovertemplate= "Time: %{x}<br>" + "Volume: %{y:.2f}<br>" + "Price: %{z:.5f}<br>" + "Volatility: %{marker.color:.5f}", name='3D View' ), row=1, col=1 ) # Add original price chart fig.add_trace( go.Candlestick( x=np.arange(len(df)), open=df['open'], high=df['high'], low=df['low'], close=df['close'], name='OHLC' ), row=1, col=2 ) # Add smoothed price line fig.add_trace( go.Scatter( x=np.arange(len(df_smooth)), y=df_smooth['close_smooth'], line=dict(color='blue', width=1), name='MA100' ), row=1, col=2 ) # Update 3D layout fig.update_scenes( xaxis_title='Time', yaxis_title='Volume', zaxis_title='Price', camera=dict( up=dict(x=0, y=0, z=1), center=dict(x=0, y=0, z=0), eye=dict(x=1.5, y=1.5, z=1.5) ) ) # Update 2D layout fig.update_xaxes(title_text="Time", row=1, col=2) fig.update_yaxes(title_text="Price", row=1, col=2) # Update overall layout fig.update_layout( width=1500, # Double width to accommodate both plots height=750, showlegend=True, title_text=f"{symbol} Combined Analysis" ) # Save interactive HTML fig.write_html(save_dir / f'{symbol}_combined_view.html') # Create additional plots with smoothed data (unchanged) fig2 = make_subplots(rows=2, cols=2, subplot_titles=('Smoothed Price', 'Smoothed Volume', 'Smoothed Price Volatility', 'Smoothed Volume Volatility')) fig2.add_trace( go.Scatter(x=np.arange(len(df_smooth)), y=df_smooth['close_smooth'], name='Price MA100'), row=1, col=1 ) fig2.add_trace( go.Scatter(x=np.arange(len(df_smooth)), y=df_smooth['tick_volume_smooth'], name='Volume MA100'), row=1, col=2 ) fig2.add_trace( go.Scatter(x=np.arange(len(df_smooth)), y=df_smooth['price_volatility_smooth'], name='Price Vol MA100'), row=2, col=1 ) fig2.add_trace( go.Scatter(x=np.arange(len(df_smooth)), y=df_smooth['volume_volatility_smooth'], name='Volume Vol MA100'), row=2, col=2 ) fig2.update_layout( height=750, width=750, showlegend=True, title_text=f"{symbol} Smoothed Data Analysis" ) fig2.write_html(save_dir / f'{symbol}_smoothed_analysis.html') print(f"Interactive visualizations saved in {save_dir}") except Exception as e: print(f"Error creating interactive visualization: {e}") raise
Yeni fiyat aralığımız şu şekildedir:
Genel olarak, çok ilginç görünüyor. Zamana göre fiyat gruplamasında belirli sekanslar ve hacme göre fiyat gruplamasında aykırı değerler görüyoruz. Yani, piyasa huzursuz olduğunda, büyük hacimler meydana geldiğinde, volatilite hızla arttığında, istatistiklerin ötesine geçen tehlikeli bir patlamayla karşı karşıya olduğumuz hissi oluşur (ve önde gelen yatırımcıların deneyimleriyle doğrudan onaylanır) - kötü şöhretli kuyruk riskleri. Dolayısıyla burada, bu tür koordinatlarda "normalliğin sınırlarının ötesine" böyle bir fiyat çıkışını hemen tespit edebiliriz. Sadece bunun için bile çok değişkenli fiyat grafikleri fikrine minnettarım!
Lütfen dikkat edin:

Hastanın muayenesi (3D grafikler)
Sonra, görselleştirmeyi öneriyorum. Ama palmiye ağacının altındaki parlak geleceğimizi değil, 3D fiyat grafiklerini. Durumları dört kümeye ayıralım: yükseliş trendi, düşüş trendi, yükseliş trendinden düşüş trendine dönüş ve düşüş trendinden yükseliş trendine dönüş. Bunu yapmak için kodu biraz değiştirmemiz gerekecek: artık çubuk indekslerine ihtiyacımız yok, belirli tarihlerdeki verileri yükleyeceğiz. Aslında, bunu yapmak için sadece mt5.copy_rates_range'e başvurmamız gerekiyor.
def create_true_3d_renko(symbol, timeframe, start_date, end_date, min_spread_multiplier=45, volume_brick=500): """ Creates 4D stationary features with same interface as 3D Renko """ rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date) if rates is None: print(f"Error getting data for {symbol}") return None, None df = pd.DataFrame(rates) df['time'] = pd.to_datetime(df['time'], unit='s') if df.isnull().any().any(): print("Missing values detected, cleaning...") df = df.dropna() if len(df) == 0: print("No data for analysis after cleaning") return None, None symbol_info = mt5.symbol_info(symbol) if symbol_info is None: print(f"Failed to get symbol info for {symbol}") return None, None try: min_price_brick = symbol_info.spread * min_spread_multiplier * symbol_info.point if min_price_brick <= 0: print("Invalid block size") return None, None except AttributeError as e: print(f"Error getting symbol parameters: {e}") return None, None scaler = MinMaxScaler(feature_range=(3, 9)) df_blocks = [] try: # 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) except Exception as e: print(f"Error processing data: {e}") if len(df_blocks) == 0: return None, None if len(df_blocks) == 0: print("Failed to create any blocks") return None, None result_df = pd.DataFrame(df_blocks) # Scale all 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 same analytical metrics as in original function 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'] # Scale moving averages and volatility 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]) # Add statistical metrics and scale them 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
İşte değiştirilmiş kodumuz:
def main(): try: # Initialize MT5 if not mt5.initialize(): print("MetaTrader5 initialization error") return # Analysis parameters symbols = ["EURUSD", "GBPUSD"] timeframes = { "M15": mt5.TIMEFRAME_M15 } # 7D analysis parameters params = { "min_spread_multiplier": 45, "volume_brick": 500 } # Date range for data fetching start_date = datetime(2017, 1, 1) end_date = datetime(2018, 2, 1) # Analysis for each symbol and timeframe for symbol in symbols: print(f"\nAnalyzing symbol {symbol}") # Create symbol directory symbol_dir = Path('charts') / symbol symbol_dir.mkdir(parents=True, exist_ok=True) # Get symbol info symbol_info = mt5.symbol_info(symbol) if symbol_info is None: print(f"Failed to get symbol info for {symbol}") continue print(f"Spread: {symbol_info.spread} points") print(f"Tick: {symbol_info.point}") # Analysis for each timeframe for tf_name, tf in timeframes.items(): print(f"\nAnalyzing timeframe {tf_name}") # Create timeframe directory tf_dir = symbol_dir / tf_name tf_dir.mkdir(exist_ok=True) # Get and analyze data print("Getting data...") df, brick_size = create_true_3d_renko( symbol=symbol, timeframe=tf, start_date=start_date, end_date=end_date, min_spread_multiplier=params["min_spread_multiplier"], volume_brick=params["volume_brick"] ) if df is not None and brick_size is not None: print(f"Created {len(df)} 7D bars") print(f"Block size: {brick_size}") # Basic statistics print("\nBasic statistics:") print(f"Average volume: {df['tick_volume'].mean():.2f}") print(f"Average trend length: {df['trend_count'].mean():.2f}") print(f"Max uptrend length: {df[df['direction'] > 0]['trend_count'].max()}") print(f"Max downtrend length: {df[df['direction'] < 0]['trend_count'].max()}") # Create visualizations print("\nCreating visualizations...") create_visualizations(df, symbol, tf_dir) # Save data csv_file = tf_dir / f"{symbol}_{tf_name}_7d_data.csv" df.to_csv(csv_file) print(f"Data saved to {csv_file}") # Results analysis trend_ratio = len(df[df['direction'] > 0]) / len(df[df['direction'] < 0]) print(f"\nUp/Down bars ratio: {trend_ratio:.2f}") volume_corr = df['tick_volume'].corr(df['price_change'].abs()) print(f"Volume-Price change correlation: {volume_corr:.2f}") # Print warnings if anomalies detected if df['price_volatility'].max() > df['price_volatility'].mean() * 3: print("\nWARNING: High volatility periods detected!") if df['volume_volatility'].max() > df['volume_volatility'].mean() * 3: print("WARNING: Abnormal volume spikes detected!") else: print(f"Failed to create 3D bars for {symbol} on {tf_name}") print("\nAnalysis completed successfully!") except Exception as e: print(f"An error occurred: {e}") import traceback print(traceback.format_exc()) finally: mt5.shutdown()
İlk veri bölümünü ele alalım - EURUSD, 1 Ocak 2017'den 1 Şubat 2018'e kadar. Aslında, çok güçlü bir yükseliş trendi. 3D çubuklarda nasıl göründüğünü görmeye hazır mısınız?


İşte başka bir görselleştirme:

Yükseliş trendinin başlangıcına dikkat edin:

Ve sonuna:

Şimdi de düşüş trendine bakalım. 1 Şubat 2018'den 20 Mart 2020'ye kadar:

Düşüş trendinin başlangıcı:

Ve sonu:

Dolayısıyla, 3D gösterimdeki her iki trendin de (hem düşüş hem de yükseliş) 3D nokta yoğunluğunun altında bir nokta alanı olarak başladığını görüyoruz. Her iki durumda da trendin sonu parlak sarı bir renk şeması ile işaretlendi.
Bu olguyu ve EURUSD fiyatlarının yükseliş ve düşüş trendlerindeki davranışını açıklamak için aşağıdaki evrensel denklem kullanılabilir:
P(t) = P_0 + \int_{t_0}^{t} A \cdot e^{k(t-u)} \cdot V(u) \, du + N(t)
Tanımlamalar:
- P(t) - belirli bir zamandaki döviz fiyatı.
- P_0 - belirli bir zamandaki ilk fiyat.
- A - fiyat değişimlerinin ölçeğini karakterize eden trend genliği.
- k - değişim oranını belirleyen katsayı (k > 0 yükseliş trendi; k < 0 düşüş trendi anlamına gelir).
- V(u) - piyasa aktivitesini etkileyen ve fiyat değişimlerinin önemini artırabilen belirli bir zamandaki işlem hacmi.
- N(t) - öngörülemeyen piyasa dalgalanmalarını yansıtan rastgele gürültü.
Metin açıklaması
Bu denklem, bir dövizin fiyatının bir dizi faktöre bağlı olarak zaman içinde nasıl değiştiğini açıklar. İlk fiyat başlangıç noktasıdır, bundan sonra integral, trend genliğinin ve değişim oranının etkisini hesaba katarak, büyüklüğe bağlı olarak fiyatı üstel artışa veya düşüşe maruz bırakır. Fonksiyonun temsil ettiği işlem hacmi, piyasa aktivitesinin de fiyat değişimlerini etkilediğini gösteren başka bir boyut katar.
Bu model, farklı trendler altındaki fiyat hareketlerinin görselleştirilmesine ve zaman ekseni, fiyat ve hacmin piyasa aktivitesinin zengin bir resmini oluşturduğu 3D uzayda görüntülenmesine olanak tanır. Bu grafikteki renk şemasının parlaklığı trendin gücünü yansıtabilir; daha parlak renkler daha yüksek türev fiyat ve işlem hacmi değerlerine karşılık gelir ve piyasadaki güçlü hacim hareketlerine işaret eder.
Geri dönüşü görüntüleme
İşte 14 Kasım'dan 28 Kasım'a kadar olan dönem. Bu zaman aralığının yaklaşık olarak ortasında fiyatlarda bir geri dönüş yaşayacağız. Bu 3D koordinatlarda nasıl görünüyor? İşte burada:

Geri dönüş ve normalleştirilmiş fiyat koordinatında artış anında tanıdık olan sarı rengi görüyoruz. Şimdi, 13 Eylül 2024'ten aynı yılın 10 Ekim'ine kadar olan dönemde, trendin geri döndüğü başka bir fiyat bölümüne bakalım:

Aynı resmi tekrar görüyoruz, sadece sarı renk ve birikimi artık altta. İlginç görünüyor.
19 Ağustos 2024 - 30 Ağustos 2024, bu tarih aralığının ortasında trendin geri döndüğü görülebilir. Koordinatlarımıza bakalım.

Yine, tamamen aynı resim. Şimdi 17 Temmuz 2024'ten 8 Ağustos 2024'e kadar olan dönemi ele alalım. Model yakında bir geri dönüş işaretleri gösterecek mi?

Son dönem 21 Nisan - 10 Ağustos 2023 tarihleri arasındadır. Yükseliş trendi burada sona erdi.

Tanıdık sarı rengi tekrar görüyoruz.
Sarı kümeler
3D çubukları geliştirirken çok ilginç bir özellikle karşılaştım - sarı renkli hacim-volatilite kümeler. Grafikteki davranışları beni büyüledi! Bir ton geçmiş veriyi inceledikten sonra (tam olarak 2022-2024 için 400,000'den fazla çubuk), şaşırtıcı bir şey fark ettim.
İlk başta gözlerime inanamadım - yaklaşık 100 bin sarı çubuğun neredeyse tamamı (%97!) fiyat dönüşlerine yakındı. Üstelik bu, artı veya eksi üç çubukluk bir aralıkta çalıştı. İlginç bir şekilde, geri dönüşlerin sadece %40'ı (toplamda yaklaşık 169 bin adet vardı) sarı çubuklar içeriyordu. Sarı çubukların geri dönüşleri neredeyse garanti ettiği ortaya çıktı, ancak geri dönüşler onlar olmadan da gerçekleşebilir.

Trendleri daha derinlemesine incelediğimde net bir kalıp fark ettim. Trendin başlangıcında ve esnasında neredeyse hiç sarı çubuk yoktur, sadece yoğun bir grup halinde normal 3D çubuklar vardır. Ancak geri dönüşten önce, sarı kümeler grafikte parlar.
Bu durum özellikle uzun trendlerde açıkça görülmektedir. Örneğin, EURUSD'nin 2017'nin başından Şubat 2018'e kadar olan artışını ve ardından Mart 2020'ye kadar olan düşüşünü ele alalım. Her iki durumda da, bu sarı kümeler geri dönüşten önce ortaya çıktı ve 3D yerleşimleri kelimenin tam anlamıyla fiyatın nereye gideceğini gösterdi!
Bunu kısa dönemler üzerinde de test ettim - 2024'te 2-3 haftalık birkaç bölüm seçtim. Saat gibi işledi! Geri dönüşlerden önce her seferinde, uyarı gibi sarı çubuklar belirdi: "Hey, dostum, trend geri dönmek üzere!"
Bu sadece bir gösterge değildir. Bence piyasa yapısının kendisinde gerçekten önemli bir şey bulduk - hacimlerin dağılma şekli ve bir trend değişikliğinden önce volatilitenin değişmesi. Artık 3D grafikte sarı kümeler gördüğümde, geri dönüşe hazırlanma zamanının geldiğini biliyorum!
Sonuç
3D çubukları keşfimizi sonlandırırken, bu araştırmanın piyasa mikro yapısına ilişkin anlayışımı ne kadar derinden değiştirdiğini belirtmeden edemeyeceğim. Görselleştirmede bir deney olarak başlayan şey, piyasayı görmenin ve anlamanın temelde yeni bir yoluna dönüştü.
Bu proje üzerinde çalışırken, fiyatların geleneksel iki boyutlu temsilinin bizi ne kadar kısıtladığını defalarca fark ettim. Üç boyutlu analize geçiş, fiyat, hacim ve zaman arasındaki ilişkileri anlamak için tamamen yeni ufuklar açtı. Özellikle önemli piyasa olaylarından önce gelen kalıpların üç boyutlu uzayda ne kadar net göründüğü beni çok etkiledi.
En önemli keşif, potansiyel trend dönüşlerini erken tespit etme becerisiydi. 3D gösterimde hacimlerin karakteristik birikimi ve renk şemasındaki değişiklik, yaklaşan trend değişikliklerinin şaşırtıcı derecede güvenilir göstergeleri olduğunu kanıtlandı. Bu sadece teorik bir gözlem değildir - bunu birçok geçmiş örnekle teyit ettik.
Geliştirdiğimiz matematiksel model, piyasa dinamiklerini sadece görselleştirmemize değil, aynı zamanda niceliksel olarak değerlendirmemize de olanak sağlar. Modern görselleştirme teknolojileri ve yazılım araçlarının entegrasyonu, bu yöntemin gerçek alım-satımda uygulanmasını mümkün kıldı. Bu araçları her gün kullanıyorum ve piyasa analizine yaklaşımımda büyük bir fark yarattılar.
Ancak, henüz yolculuğun başında olduğumuza inanıyorum. Bu proje, çok değişkenli piyasa mikro yapı analizi dünyasının kapısını açtı ve bu yönde yapılacak daha fazla araştırmanın çok daha ilginç keşifler sağlayacağından eminim. Belki de bir sonraki adım, 3D formasyonları otomatik olarak tanımak için makine öğreniminin entegrasyonu veya çok değişkenli analize dayalı yeni alım-satım stratejilerinin geliştirilmesi olacaktır.
Nihayetinde, bu araştırmanın gerçek değeri güzel grafiklerde veya karmaşık denklemlerde değil, sağladığı yeni piyasa içgörülerindedir. Bir araştırmacı olarak, teknik analizin geleceğinin piyasa verilerini analiz etmeye yönelik çok değişkenli bir yaklaşımda yattığına kuvvetle inanıyorum.
MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/16555
Uyarı: Bu materyallerin tüm hakları MetaQuotes Ltd'ye aittir. Bu materyallerin tamamen veya kısmen kopyalanması veya yeniden yazdırılması yasaktır.
Bu makale sitenin bir kullanıcısı tarafından yazılmıştır ve kendi kişisel görüşlerini yansıtmaktadır. MetaQuotes Ltd, sunulan bilgilerin doğruluğundan veya açıklanan çözümlerin, stratejilerin veya tavsiyelerin kullanımından kaynaklanan herhangi bir sonuçtan sorumlu değildir.
Yeni Raylara Adım Atın: MQL5'te Özel Göstergeler
Borsada doğrusal olmayan regresyon modelleri
İşte Karışınızda Yeni MetaTrader 5 ve MQL5
Forex veri analizinde ilişkilendirme kurallarını kullanma
- Ücretsiz alım-satım uygulamaları
- İşlem kopyalama için 8.000'den fazla sinyal
- Finansal piyasaları keşfetmek için ekonomik haberler
Web sitesi politikasını ve kullanım şartlarını kabul edersiniz
Akla hemen şu soru geliyor - neden? Düz bir grafik doğru analiz için yeterli değil mi? İşte bu noktada normal lise geometrisi işe yarar.
Herhangi bir algoritma esasen uzamsal boyutları araştırır. Algoritmalar yaratarak, çok boyutlu arama yoluyla temel kombinatoryal patlama sorununu çözmeye çalışıyoruz. Bu bizim sonsuz olasılıklar denizinde gezinme yöntemimizdir.
(Çeviri mükemmel değilse özür dilerim)
Herhangi bir algoritma esasen uzamsal boyutları araştırır. Algoritmalar yaratarak, çok boyutlu arama yoluyla temel kombinatoryal patlama sorununu çözmeye çalışıyoruz. Bu bizim sonsuz olasılıklar denizinde gezinme yöntemimizdir.
(Çeviri mükemmel değilse özür dileriz)
Anlaşıldı. Trend tahminini basit okul geometrik formülleriyle çözemezsek, insanlar turbo süperşarjlı, akıllı telefon kontrollü, gülen yüzlü ve diğer cicili bicili bir Lysaped icat etmeye başlar! Ama tekerlekleri yok ve olması da beklenmiyor. Ve tekerlekler olmadan, tek şasiyle uzağa gidemezsiniz.
Anlıyorum. Trend tahminini basit okul geometrik formülleriyle çözmek imkansızsa, insanlar turbo süperşarjlı, akıllı telefon kontrollü, gülen yüzlü ve diğer cicili bicili bir lisaped icat etmeye başlar! Ama tekerlekleri yok ve olması da beklenmiyor. Ve tekerlekler olmadan, tek bir çerçeveyle uzağa gidemezsiniz.