Русский Español Português
preview
Analyzing binary code of prices on the exchange (Part I): A new look at technical analysis

Analyzing binary code of prices on the exchange (Part I): A new look at technical analysis

MetaTrader 5Indicators |
253 1
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Introduction

Do you remember how in "The Matrix" Neo saw the world as a green binary code? But what if you look at exchange charts that way? In my daily trading, I have often wondered: is it possible to "hear" what the market is trying to tell us if we imagine its movements as a kind of code or language?

The idea came unexpectedly, while analyzing the Bitcoin chart. Watching surges and slumps of the price I noticed that certain combinations of movements are repeated, like letters in words. This made me think: what if we encode these movements in binary format and try to "read" the resulting message?

Does it sound insane? Maybe. But remember: isn't technical analysis based on the search for patterns? Aren't candlesticks  the same coding system for price movements? After all, the market itself  is a huge stream of information, encoded digitally.

In this article, I want to share my experiment on turning price movements into meaningful binary code sequences. We will look at how one can convert various aspects of market behavior —  from simple price movements to complex patterns — into binary format, and try to find patterns in this code that will help us better understand the language of the market.

Yes, this is an unconventional approach. But isn't that what trading is all about — looking for new ways to see what others are missing?

Let us dive down the rabbit hole together and see where it leads us...


The concept of representing price movements as a binary code

My approach is based on a simple but intriguing idea: any price movement can be represented as a sequence of zeros and ones. Think about it: when we look at the chart, what do we see? Growth and decline, strong and weak movements, volumes above or below the average. In fact, we are already unknowingly converting this information into a binary format in our head!

Here is a simple example. Let us say we see three candles in a row: a rising one, a falling one, and one rising again. In the most primitive representation, this can be encoded as "101". But what if we add more parameters? For example, should we take into account the strength of the movement, the trading volume, or the position relative to the moving average?

In my experiments, I have found that even basic coding based on the "rise-fall" principle can produce interesting results. However, the real magic begins when we start taking into account multiple timeframes. Imagine: the same sequence of "101" on an hourly chart may look completely different on a daily or weekly timeframe. This creates a kind of data matrix where each price movement is described by a unique binary sequence.

Particularly interesting results are obtained when analyzing cryptocurrencies. Bitcoin's volatility creates clear, well-defined patterns in binary representation. Sometimes it seems that these sequences are similar to Morse code — the same rhythmic dance of dots and dashes, only instead of them — the rise and fall of prices.

Of course, skeptics may say that this is just a complicated version of classical technical analysis. But isn't that the essence of progress — to look at familiar things from a new angle? After all, candlesticks once seemed exotic as well, but today no trader can do without them.

In the following sections, we will take a detailed look at the various ways to encode price movements and how this information can help us in real trading. In the meantime, I suggest you take a look at your favorite chart yourself and try to see a sequence of zeros and ones in it. I am sure this will change your understanding of technical analysis...


Basic principles of conversion

Do you remember first computer games? The ones where the graphics consisted of pixels and the sound was 8-bit? So, our approach to market analysis is somewhat similar to creating a retro game: we take the complex world of exchange quotes and turn it into a simple binary code.

Let me tell you how this idea was born. One day, while developing another indicator, I accidentally displayed only zeros and ones on the chart instead of the usual lines. And you know what? Interesting patterns began to appear in this series of numbers. It was as if the market was trying to say something in its binary language.

For those who like to dig into the code, here is how it works in Python:

def encode_price_movement(self, prices):
    normalized = ((prices - np.min(prices)) / 
                 (np.max(prices) - np.min(prices)) * 255).astype(np.uint8)
    data = bytes(normalized)
    entropy = hashlib.sha256(data).digest()
    mnemonic = self.mnemo.to_mnemonic(entropy)
    return mnemonic

In the direction of movement (rise/fall)

Do you know what's the most fun thing about trading? The fact that we spend hours looking at charts, trying to guess the next price movement, but in fact, it all comes down to the simplest choice: up or down? One or zero?

While working on this project, I spent hours experimenting with Bitcoin. Why with it? Because crypto is a pure code. No quarterly reports, dividends, or other fundamentals are available there. Just pure crowd psychology, encoded in numbers.

This is what basic analysis looks like in Python:

def analyze_words_frequency(self, prices):
    price_diff = np.diff(prices)
    bullish_mnemonics = []
    bearish_mnemonics = []
    
    for i in range(len(price_diff)):
        window = prices[max(0, i-4):i+1]
        if len(window) < 5:
            continue
        mnemonic = self.encode_price_movement(window)
        if price_diff[i] > 0:
            bullish_mnemonics.extend(mnemonic.split())
        else:
            bearish_mnemonics.extend(mnemonic.split())

And now the most interesting thing is how it works in real trading. Imagine: you look at the chart, and instead of ordinary candles, you see the sequence "1011101". Does it look like nonsense? But it does not! This sequence can tell you more than a dozen technical indicators.

It is funny, but when I showed this code to my fellow traders, they first twirled their finger at my temple. "Why complicate it?" they said. And then one of them noticed an interesting thing: some binary sequences appear before strong movements more often than they should according to probability theory.

Of course, this does not mean that we have invented a machine for printing money. But you must admit  that there is something fascinating about the idea that the market communicates with us through binary code. Like in that “The Matrix" movie, remember?

Relative to the moving average

Working with MetaTrader 5 through Python, I discovered a whole new perspective on moving averages. You know, it turned out funny — I just wanted to connect the terminal to Python, but in the end I found something more significant.

import MetaTrader5 as mt5
import numpy as np

def encode_ma_crossings(symbol, timeframe, ma_period=20):
    # Connect to the terminal
    if not mt5.initialize():
        print("MT5 initialization error")
        return None
        
    # Get data
    rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, 1000)
    prices = np.array([rate[4] for rate in rates])  # Use Close prices
    
    # Calculate SMA and program crossover
    ma = np.convolve(prices, np.ones(ma_period)/ma_period, mode='valid')
    crossings = prices[ma_period-1:] > ma
    
    mt5.shutdown()
    return np.where(crossings, 1, 0)

The most interesting thing started when I noticed a strange pattern: on Bitcoin, the "101" sequences near MA often preceded strong movements. Especially during periods of high volatility. It is as if the market is "trying on" before jumping.

By the force of movement (momentum)

"The market is either sleeping or running" is a phrase I often repeat to my fellow traders. Now imagine: we can encode these states into simple zeros and ones. Beautiful isn’t it?

def encode_momentum(symbol, timeframe, threshold=0.02):
    if not mt5.initialize():
        return None
        
    # Load data from MT5
    rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, 1000)
    prices = np.array([rate[4] for rate in rates])
    
    # Calculate price changes
    price_changes = np.diff(prices) / prices[:-1]
    
    # Program strong movements
    strong_moves = np.abs(price_changes) > threshold
    momentum = np.zeros_like(price_changes)
    momentum[strong_moves] = np.sign(price_changes[strong_moves])
    
    mt5.shutdown()
    return momentum

I remember my first experiment with this code. I launched it on the hourly chart of ETH/USD, and you know what? It turned out that after the "000" sequence (a period of low volatility), the probability of a strong movement increases by 23%. This is not exactly the golden rule of trading, but you must admit  that there is something to think about.

By the way, when I added volumes to the analysis, the picture became even more interesting. But more on that in the following sections. In the meantime, I suggest you play with these patterns yourself. Maybe you'll find something that I missed.

By trading volume


But with the volumes, it turned out to be a funny story. Initially, I didn't attach much importance to them — just another encoding parameter. But when I started analyzing patterns on BTC/USD, I realized that volumes sometimes tell more than the price itself.

import MetaTrader5 as mt5
import numpy as np
from datetime import datetime

def encode_volume_patterns(symbol, timeframe):
    if not mt5.initialize():
        return None
    
    # Take volume data
    rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, 1000)
    volumes = np.array([rate[5] for rate in rates])
    
    # Calculate average volume for the last 20 bars
    avg_volume = np.convolve(volumes, np.ones(20)/20, mode='valid')
    
    # Code: 1 for volume above average and 0 - for below
    volume_code = volumes[19:] > avg_volume
    
    mt5.shutdown()
    return np.where(volume_code, 1, 0)

Do you know what turned out to be the most interesting? The “111” sequence by volumes (three high volume bars in a row) often appears before serious movements. It is like the "whales" are warming up before a race. This is especially clear on the 4-hour chart.


Timeframes and their impact on coding

I remember sitting over the code one night, trying to figure out why the same patterns work differently on different timeframes. And then it dawned on me — what if looking at the same moment in time on several timeframes at once?

def multi_timeframe_code(symbol, timeframes=[mt5.TIMEFRAME_M15, mt5.TIMEFRAME_H1, mt5.TIMEFRAME_H4]):
    if not mt5.initialize():
        return None
    
    combined_code = []
    
    for tf in timeframes:
        rates = mt5.copy_rates_from_pos(symbol, tf, 0, 100)
        prices = np.array([rate[4] for rate in rates])
        
        # Code direction on each timeframe
        price_direction = np.diff(prices) > 0
        combined_code.append(np.where(price_direction, 1, 0))
    
    mt5.shutdown()
    
    # Combine codes from different timeframes
    return np.array(combined_code)

The results amazed me. It turned out that some combinations appear only at certain periods during 24 hours. For example, on the EUR/USD pair, the "101” sequence on the 15-minute chart has a completely different value during the Asian session than during the European one.

There was even such a case: I was trading using this system on ETHUSDT, and I noticed a strange thing — "before each serious movement, the "110” sequence would appear on the hourly chart, and on the 4-hour chart at the same time - "101" would appear. Coincidence? Possibly. But after the fifth such incident, I was no longer sure about it.

By the way, it is funny to observe how some of the "ideal" setups on the lower timeframes break down against the harsh reality of the higher ones. Now, before entering the market, I always check binary patterns on at least three timeframes. Double check? Maybe. But in trading, it is better to overdo it than underdo it.


Looking for and studying binary coarse and naive patterns with zero entropy.

Let me tell you how the idea was born to look for patterns in binary market patterns. It all started with a simple observation — any price movement can be represented as a chain of zeros and ones. But the deeper I dug, the more interesting it became.

import MetaTrader5 as mt5
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import base58
from sklearn.preprocessing import StandardScaler
from collections import Counter

def initialize_mt5():
    if not mt5.initialize():
        print("Initialize() failed")
        mt5.shutdown()
        return False
    return True

def get_eurusd_data(start_date, end_date, timeframe):
    rates = mt5.copy_rates_range("EURUSD", timeframe, start_date, end_date)
    df = pd.DataFrame(rates)
    df['time'] = pd.to_datetime(df['time'], unit='s')
    return df

class PriceDecoder:
    def __init__(self, df):
        self.df = df
        self.binary_patterns = []
    
    # Method 1: Encoding based on the movement direction
    def direction_encoding(self, window=10):
        binary = (self.df['close'] > self.df['close'].shift(1)).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 2: Encoding based on relation to MA
    def ma_encoding(self, ma_period=20, window=10):
        ma = self.df['close'].rolling(ma_period).mean()
        binary = (self.df['close'] > ma).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 3: Encoding according to movement strength
    def momentum_encoding(self, threshold=0.0001, window=10):
        returns = self.df['close'].pct_change()
        binary = (returns.abs() > threshold).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method4: Volume encoding
    def volume_encoding(self, window=10):
        avg_volume = self.df['tick_volume'].rolling(window).mean()
        binary = (self.df['tick_volume'] > avg_volume).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 5: Fractal encoding
    def fractal_encoding(self, window=10):
        highs = self.df['high'].rolling(5, center=True).max()
        lows = self.df['low'].rolling(5, center=True).min()
        binary = ((self.df['high'] == highs) | (self.df['low'] == lows)).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 6: Volatility encoding
    def volatility_encoding(self, window=10):
        volatility = self.df['high'] - self.df['low']
        avg_volatility = volatility.rolling(20).mean()
        binary = (volatility > avg_volatility).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 7: Candlestick pattern encoding
    def candle_pattern_encoding(self, window=10):
        body = abs(self.df['close'] - self.df['open'])
        shadow = self.df['high'] - self.df['low']
        binary = (body > shadow/2).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 8: Entropy Encoding
    def entropy_encoding(self, window=10):
        returns = self.df['close'].pct_change()
        entropy = returns.rolling(window).apply(lambda x: np.sum(-x[x!=0]*np.log2(abs(x[x!=0]))))
        binary = (entropy > entropy.mean()).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 9: Convergence/divergence encoding
    def convergence_encoding(self, window=10):
        ma_fast = self.df['close'].rolling(5).mean()
        ma_slow = self.df['close'].rolling(20).mean()
        binary = (ma_fast > ma_slow).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 10: Price level encoding
    def price_level_encoding(self, window=10):
        pivot = (self.df['high'] + self.df['low'] + self.df['close']) / 3
        binary = (self.df['close'] > pivot).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 11: RSI momentum encoding
    def rsi_momentum_encoding(self, window=10, rsi_period=14):
        delta = self.df['close'].diff()
        gain = (delta.where(delta > 0, 0)).rolling(window=rsi_period).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=rsi_period).mean()
        rs = gain / loss
        rsi = 100 - (100 / (1 + rs))
        binary = (rsi > 50).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 12: Cluster encoding
    def cluster_encoding(self, window=10):
        prices = self.df['close'].values.reshape(-1, 1)
        scaler = StandardScaler()
        prices_scaled = scaler.fit_transform(prices)
        binary = (prices_scaled > 0).astype(int).flatten()
        pattern = ''.join(map(str, binary[-window:]))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 13: Extremum encoding
    def extremum_encoding(self, window=10):
        highs = self.df['high'].rolling(window).max()
        lows = self.df['low'].rolling(window).min()
        mid = (highs + lows) / 2
        binary = (self.df['close'] > mid).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 14: Trend encoding
    def trend_encoding(self, window=10):
        slope = pd.Series(np.nan, index=self.df.index)
        for i in range(window, len(self.df)):
            y = self.df['close'].iloc[i-window:i].values
            x = np.arange(window)
            slope[i] = np.polyfit(x, y, 1)[0]
        binary = (slope > 0).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    # Method 15: Hybrid encoding
    def hybrid_encoding(self, window=10):
        direction = (self.df['close'] > self.df['close'].shift(1)).astype(int)
        volume = (self.df['tick_volume'] > self.df['tick_volume'].rolling(window).mean()).astype(int)
        volatility = ((self.df['high'] - self.df['low']) > 
                     (self.df['high'] - self.df['low']).rolling(window).mean()).astype(int)
        binary = (direction & volume & volatility).astype(int)
        pattern = ''.join(binary.astype(str).tail(window))
        return pattern, self.analyze_pattern(pattern)
    
    def analyze_pattern(self, pattern, min_seq_len=2, max_seq_len=8):
        # Convert to base58 for compactness
        base58_pattern = base58.b58encode(pattern.encode()).decode()
        
        # Advanced repeatability analysis with different depths
        repeating_sequences = {}
        for seq_len in range(min_seq_len, max_seq_len + 1):
            sequences_at_depth = []
            for i in range(len(pattern) - seq_len + 1):
                sequence = pattern[i:i+seq_len]
                count = pattern.count(sequence)
                if count > 1:
                    sequences_at_depth.append({
                        'sequence': sequence,
                        'count': count,
                        'positions': [j for j in range(len(pattern)) if pattern[j:j+seq_len] == sequence]
                    })
            if sequences_at_depth:
                repeating_sequences[seq_len] = sorted(sequences_at_depth, 
                                                    key=lambda x: (x['count'], len(x['sequence'])), 
                                                    reverse=True)
        
        # Entropy analysis for different windows
        entropy_analysis = {}
        for window_size in range(2, min(9, len(pattern) + 1)):
            windows = [pattern[i:i+window_size] for i in range(len(pattern)-window_size+1)]
            window_entropy = {}
            for window in set(windows):
                count = windows.count(window)
                prob = count / len(windows)
                local_entropy = -prob * np.log2(prob) if prob > 0 else 0
                window_entropy[window] = {
                    'count': count,
                    'probability': prob,
                    'entropy': local_entropy
                }
            entropy_analysis[window_size] = window_entropy
        
        # Search for stable patterns (with zero entropy)
        stable_patterns = []
        for window_size, patterns in entropy_analysis.items():
            zero_entropy_patterns = {
                pattern: data for pattern, data in patterns.items() 
                if abs(data['entropy']) < 0.01 and data['count'] > 1
            }
            if zero_entropy_patterns:
                stable_patterns.append({
                    'window_size': window_size,
                    'patterns': zero_entropy_patterns
                })
        
        # Basic statistics
        ones_count = pattern.count('1')
        zeros_count = pattern.count('0')
        overall_entropy = 0
        if len(pattern) > 0:
            probabilities = [pattern.count(char)/len(pattern) for char in set(pattern)]
            overall_entropy = -sum(p * np.log2(p) for p in probabilities if p > 0)
        
        return {
            'base58': base58_pattern,
            'repeating_sequences': repeating_sequences,
            'stable_patterns': stable_patterns,
            'entropy_analysis': entropy_analysis,
            'ones_ratio': ones_count / len(pattern),
            'zeros_ratio': zeros_count / len(pattern),
            'overall_entropy': overall_entropy
        }

def main():
    if not initialize_mt5():
        return
    
    # Get data for the last month
    end_date = datetime.now()
    start_date = end_date - timedelta(days=30)
    df = get_eurusd_data(start_date, end_date, mt5.TIMEFRAME_H1)
    
    decoder = PriceDecoder(df)
    
    # Use all encoding methods
    methods = [
        ('Direction', decoder.direction_encoding),
        ('MA', decoder.ma_encoding),
        ('Momentum', decoder.momentum_encoding),
        ('Volume', decoder.volume_encoding),
        ('Fractal', decoder.fractal_encoding),
        ('Volatility', decoder.volatility_encoding),
        ('Candle Pattern', decoder.candle_pattern_encoding),
        ('Entropy', decoder.entropy_encoding),
        ('Convergence', decoder.convergence_encoding),
        ('Price Level', decoder.price_level_encoding),
        ('RSI Momentum', decoder.rsi_momentum_encoding),
        ('Cluster', decoder.cluster_encoding),
        ('Extremum', decoder.extremum_encoding),
        ('Trend', decoder.trend_encoding),
        ('Hybrid', decoder.hybrid_encoding)
    ]
    
    print("\nPrice Pattern Analysis Results:")
    print("-" * 50)
    
    for method_name, method in methods:
        pattern, analysis = method()
        print(f"\n{method_name} Encoding:")
        print(f"Pattern: {pattern}")
        print(f"Base58: {analysis['base58']}")
        print("\nStable Patterns with Zero Entropy:")
        for stable_group in analysis['stable_patterns']:
            print(f"\nWindow Size {stable_group['window_size']}:")
            for pattern, data in stable_group['patterns'].items():
                print(f"  {pattern}: appears {data['count']} times")
        
        print("\nRepeating Sequences by Length:")
        for length, sequences in analysis['repeating_sequences'].items():
            print(f"\nLength {length}:")
            for seq in sequences[:3]:  # show top 3 for each length
                print(f"  {seq['sequence']}: appears {seq['count']} times at positions {seq['positions']}")
        
        print(f"\nBasic Statistics:")
        print(f"Ones ratio: {analysis['ones_ratio']:.2f}")
        print(f"Zeros ratio: {analysis['zeros_ratio']:.2f}")
        print(f"Overall entropy: {analysis['overall_entropy']:.2f}")
    
    mt5.shutdown()

if __name__ == "__main__":
    main()

Imagine that you are looking at the chart not as a trader, but as a decryptor. Each bar is a letter in the strange message of the market. What you need is just to understand which alphabet it uses.

I have created 15 different coding methods, each with its own logic:

  • direction_encoding — is the simplest method. Is the price higher than the previous one? One. Lower? Zero. Like Morse code for beginners.
  • ma_encoding — more interesting yet. Monitoring the intersection of the price with the moving average. When I was testing on EURUSD, I noticed a funny thing — some combinations appeared more often during the European session.
  • momentum_encoding — here we are looking at the force of movement. You know what is amazing? Three consecutive zeros (a period of weak momentum) are often followed by a sharp spike in activity.
  • volume_encoding — volume analysis in binary format. I remember once noticing a strange pattern on Bitcoin – "10101" which in terms of volume almost always preceded a strong movement.
  • fractal_encoding — fractal patterns in binary form. It is like looking for Bill Williams fractals, only in a world of zeros and ones.
  • volatility_encoding — encoding volatility. It is funny, but some volatility patterns turned out to be so stable that their entropy tended to zero.
  • candle_pattern_encoding — classic candle patterns in binary representation. The Doji in this system looks like "101".
  • entropy_encoding — here we measure the information entropy of price movements. When entropy drops close to zero - expect surprises.
  • convergence_encoding — convergence-divergence of moving averages in binary format. Classics of technical analysis in a new format.
  • price_level_encoding — encoding the ratio of the price to the key levels. Particularly interesting patterns appear near round numbers.
  • rsi_momentum_encoding — RSI in binary form. It turned out that some combinations of "101" on the RSI have almost zero entropy.
  • cluster_encoding — cluster price analysis in binary representation. It is like looking for order accumulation zones, only in the code.
  • extremum_encoding — local maxima and minima in the form of zeros and ones. Sometimes very characteristic sequences appear before important reversals.
  • trend_encoding — the direction of the trend in binary form. The method works fine on higher timeframes.
  • hybrid_encoding — a combination of all approaches. As a super indicator, only in binary format.

The most interesting thing started when I added the analyze_pattern. This method does not just look for repeating sequences — it measures their entropy, converts them to base58 for compactness, and identifies patterns with zero entropy.

Do you know what struck me the most? Some patterns appear with such regularity that it can no longer be an accident. For example, "11001” sequence in hybrid_encoding often appears before strong movements on the EURUSD.

Do you remember chaos theory and the butterfly effect? So, sometimes in this chaos of price movements, islands of order appear — patterns with almost zero entropy. It is like the market is sharing its plans for a second...

In the following sections, we will talk about how to use these patterns in real trading. In the meantime, I suggest that we experiment with the code. Maybe you will find your own unique patterns in the binary code of the market?


Analyzing naive binary patterns in prices

Analyzing the results of experiments with binary coding of the market, I discovered several unexpected patterns. Let us look at the most interesting finds.

First of all, pay attention to the momentum pattern "1010111111". Surprisingly, with an entropy of 0.72, this pattern appeared 43 times in our timeframe! The average profitability constituted -0.01%, and the win rate was 44.19%. It would seem that the result was not impressive, but the maximum profit reached +0.34% with a maximum drawdown of -0.42%. This indicates the potential of the pattern with proper risk management.

In terms of volume, we see the characteristic sequence "0111001111" with a 70/30 ratio of ones to zeros. This pattern appeared 13 times in a month, showing a win rate of 46.15%. Interestingly, despite the negative average return (-0.06%), the maximum profit reached +0.28%.

The real discovery was the Convergence (Convergence/Divergence) patterns. The sequence "0000000000" with zero entropy appeared an incredible 1652 times! At the same time, the win rate was 53.27%, and the maximum profit reached +0.68%. Given the number of signals, this is a statistically significant result.

Among all the methods, the best statistics were shown by:

  • Fractal pattern "0110000100": 63.64% win rate on 11 trades, average profitability +0.14%
  • Volatility pattern "0010001011": win rate of 100% on 2 trades, average profitability +0.21%
  • Candle pattern "1010111110": win rate of 100% on 3 trades, average profitability +0.04%

As for the hybrid encoding ("0010000011"), it showed 12 signals with a win rate of 25% and an average return of -0.04%. However, the maximum profit reached +0.33%, which indicates the potential of the method with proper signal filtering.

RSI Momentum pattern ("0010000001") showed particularly interesting results. With only three appearances in a month, all of them turned out to be unprofitable, which may indicate a strong signal to open positions in the opposite direction.

Based on the statistics obtained, the optimal strategy may look like this:

  1. Basic signal: fractal coding pattern (63.64% of successful trades)
  2. Confirmation: convergence (high frequency of occurrence and positive win rate)
  3. Filter: volatility and candlestick patterns (100% win rate)

In the following sections, we will take a deeper look at the practical application of these patterns in real trading. In the meantime, I can say that binary coding really allows you to see the market from a new angle and find non-obvious patterns in its behavior.


Attempts to decrypt binary code on the exchange using neural network

At some point, while dealing with binary patterns, I wondered if I could teach a computer to read this peculiar language of the market. That is how the idea was born to use CatBoost to predict future price movements.

The beauty of CatBoost is that it does a great job with categorical data. And our binary patterns, in fact, are categories — sequences of zeros and ones, each of which says something about the state of the market.

The BinaryPatternPredictor class became the heart of the system. Its main task is to transform binary patterns into features that the neural network can understand. To do this, I used the sliding window technique: we take a piece of history (the lookback period) and try to predict whether there will be more ones or zeros in the next window.

The most interesting thing started when I worked under prepare_features method. Imagine: each pattern becomes a set of features. There is not only the sequence of zeros and ones, but also additional metrics — how many ones are in the window, what is the trend in the latest values, how it correlates with other coding methods.

I remember my first experiment with momentum method. In the code, it looks simple:

returns = self.decoder.df['close'].pct_change()
base_binary = (returns.abs() > 0.0001).astype(int)

But there is a whole philosophy behind these lines — we turn the force of price movement into a simple sequence of zeros and ones.

But the hybrid method turned out to be particularly interesting:

direction = (self.decoder.df['close'] > self.decoder.df['close'].shift(1)).astype(int)
volume = (self.decoder.df['tick_volume'] > self.decoder.df['tick_volume'].rolling(self.lookback).mean()).astype(int)
volatility = ((self.decoder.df['high'] - self.decoder.df['low']) > 
              (self.decoder.df['high'] - self.decoder.df['low']).rolling(self.lookback).mean()).astype(int)
base_binary = (direction & volume & volatility)

Here we combine three different views of the market: the direction of movement, volume and volatility. We get something like the "three whales" of technical analysis, only in binary form.

In the train method, I intentionally did not shuffle the data (shuffle=False). Why so? Because in real trading, it is important to us how the model works on sequential data, because the market does not randomly scatter its patterns, but forms them one by one.

When it came to prediction in predict_next_pattern, I added a model confidence assessment:

prediction = self.model.predict_proba(last_window)[0]
return {
    'probability_more_ones': prediction[1],
    'probability_more_zeros': prediction[0],
    'prediction': 'More ones' if prediction[1] > 0.5 else 'More zeros',
    'confidence': max(prediction)
}

This proved to be extremely useful — the higher the model confidence, the more often the forecast turned out to be correct.

In the main() main function, I did an analysis using several encoding methods at once. In practice, it turned out that the discrepancy in the predictions of different methods often provides more information than a unanimous prediction. As in that proverb — one head is good, but five coding methods are better!

I was especially pleased with how the model learned to find non-obvious patterns. For example, the combination of weak volatility with rising volume often predicted a predominance of units in the next window. But this is, in fact, a classic situation of accumulating positions before a strong move!


Neural network results

Do you know what really surprised me about the results of this experiment? Each coding method has shown its own unique character, just like different indicators in classical technical analysis.

Let us start with momentum — it is simply fantastic! An accuracy of 95% and an F1-score of 0.92 indicate that the model reads momentum movements of the market almost accurately. It is especially interesting that the prediction shows a 95% probability that ones will prevail in the next pattern. In fact, this is a signal of continued strong movement.

Volume analysis did not disappoint either. With an accuracy of 83% and balanced metrics (precision and recall are also about 0.83), it perfectly captures the activity of major players. When I saw prediction with 94% confidence in predominance of the ones, I immediately recalled the classic rule — "volume confirms the trend."

But the convergence method provided a surprise. With the same accuracy of 83%, it predicts the prevalence of zeros with 78% confidence. This directly contradicts the momentum and volume signals! Such a divergence often indicates an approaching trend reversal.

The hybrid method turned out to be a real discovery. Accuracy of 91% and precision of 0.92 show that a combination of different approaches yields more consistent results. Interestingly, it also predicts a predominance of zeros (75% probability), confirming the convergence signal.

I remember testing this model on real EURUSD data. One day, all the methods demonstrated high accuracy, but they differed in their predictions — just like now. A few hours later, a sharp trend reversal took place. Coincidence? Maybe. But since then, I have been closely following such “discrepancies" between methods.

Overall, these results suggest an interesting thought: perhaps the market is really talking to us through binary patterns. However, we do not always know how to read them correctly. And it seems that the neural network copes with this better than the human being.

By the way, an important point is that all these metrics are obtained on an hourly timeframe. On other time intervals, the picture may be completely different. Especially when applying the 3D bars concept... But this is a topic for a separate research...


Conclusion

At the beginning of this article, we asked ourselves: is it possible to hear the voice of the market through binary code? After months of experiments, thousands of lines of code, and countless hours of analysis, I can say that yes, the market really speaks to us in its own special language. And now we have learned to understand it a little.

Do you know what struck me the most in this research? How the simple conversion of price movements into sequences of zeros and ones opens up a completely new perspective on technical analysis. You do not look at charts like you used to, but now you see a part of a big digital message in every movement.

The results of the neural network are particularly impressive. 95% accuracy on momentum patterns, 83% on volume analysis, 91% on a hybrid approach - these are not just numbers. This is a confirmation that there is a hidden order in the apparent chaos of market movements that can be discovered if you know where to search.

Of course, it would be naive to think that we have found the philosopher's stone of trading. The market is too complex and dynamic to be fully described by even the most sophisticated algorithm. But we have definitely discovered a new tool for understanding it.

For me, this project has become something more than just an experiment with code. He reminded that in trading there is always a place for innovation, for a fresh look at familiar things. And maybe the next breakthrough in market analysis will come not from complex mathematical models, but from the ability to hear what the market is trying to tell us in its binary language.

And what's next? It seems to me that we are only at the beginning of the journey. Ahead are experiments with other coding methods, research on relationships between different timeframes, and integration with classical analysis methods. But the main thing is to keep listening to the market. Because it is always ready to share its secrets with those who know how to listen and whose heart is open to innovation. And innovation  is the foundation for development. 

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/16741

Attached files |
Price_Binary_v_1.py (27.43 KB)
Last comments | Go to discussion (1)
Maxim Dmitrievsky
Maxim Dmitrievsky | 14 Jan 2025 at 17:16
Seems like binarisation and quantisation of features, in the MO thread on the forum, didn't end up in anything :)
Artificial Tribe Algorithm (ATA) Artificial Tribe Algorithm (ATA)
The article provides a detailed discussion of the key components and innovations of the ATA optimization algorithm, which is an evolutionary method with a unique dual behavior system that adapts depending on the situation. ATA combines individual and social learning while using crossover for explorations and migration to find solutions when stuck in local optima.
Self Optimizing Expert Advisors in MQL5 (Part 13): A Gentle Introduction To Control Theory Using Matrix Factorization Self Optimizing Expert Advisors in MQL5 (Part 13): A Gentle Introduction To Control Theory Using Matrix Factorization
Financial markets are unpredictable, and trading strategies that look profitable in the past often collapse in real market conditions. This happens because most strategies are fixed once deployed and cannot adapt or learn from their mistakes. By borrowing ideas from control theory, we can use feedback controllers to observe how our strategies interact with markets and adjust their behavior toward profitability. Our results show that adding a feedback controller to a simple moving average strategy improved profits, reduced risk, and increased efficiency, proving that this approach has strong potential for trading applications.
Neural Networks in Trading: A Multi-Agent Self-Adaptive Model (MASA) Neural Networks in Trading: A Multi-Agent Self-Adaptive Model (MASA)
I invite you to get acquainted with the Multi-Agent Self-Adaptive (MASA) framework, which combines reinforcement learning and adaptive strategies, providing a harmonious balance between profitability and risk management in turbulent market conditions.
Introduction to MQL5 (Part 20): Introduction to Harmonic Patterns Introduction to MQL5 (Part 20): Introduction to Harmonic Patterns
In this article, we explore the fundamentals of harmonic patterns, their structures, and how they are applied in trading. You’ll learn about Fibonacci retracements, extensions, and how to implement harmonic pattern detection in MQL5, setting the foundation for building advanced trading tools and Expert Advisors.