Giới thiệu tổng quan

Trong lĩnh vực tài chính phái sinh tiền mã hóa, việc phân tích dữ liệu期权链 (chuỗi quyền chọn) và资金费率 (tỷ lệ tài trợ) là yếu tố then chốt để xây dựng chiến lược giao dịch hiệu quả. Bài viết này sẽ hướng dẫn chi tiết cách sử dụng Tardis CSV — bộ dữ liệu lịch sử chất lượng cao cho phép nghiên cứu sâu về cấu trúc thị trường phái sinh. Đối với các nhà phân tích muốn ứng dụng AI vào quy trình xử lý và diễn giải dữ liệu này, HolySheep AI cung cấp nền tảng với độ trễ dưới 50ms và chi phí chỉ từ $0.42/MTok với DeepSeek V3.2, giúp tiết kiệm đến 85% so với các giải pháp truyền thống.

Tardis CSV là gì và tại sao nó quan trọng

1.1 Khái niệm cơ bản

Tardis là nền tảng thu thập và lưu trữ dữ liệu lịch sử từ nhiều sàn giao dịch tiền mã hóa. Khác với các API thời gian thực, Tardis CSV cho phép bạn tải về các bộ dữ liệu hoàn chỉnh phục vụ nghiên cứu backtesting và phân tích retrospective.

Ưu điểm của Tardis CSV: Độ phủ đa sàn (Binance, Bybit, OKX, Deribit), định dạng chuẩn hóa, khả năng xử lý hàng triệu dòng dữ liệu, và tài liệu API chi tiết.

1.2 Phạm vi dữ liệu hỗ trợ

Tardis cung cấp nhiều loại dữ liệu phái sinh quan trọng:

Cài đặt và cấu hình môi trường

2.1 Yêu cầu hệ thống

Trước khi bắt đầu, đảm bảo môi trường của bạn đã cài đặt các thư viện cần thiết:
# Cài đặt các thư viện cần thiết
pip install pandas numpy matplotlib seaborn requests python-dotenv

Thư viện chuyên dụng cho phân tích tài chính

pip install scipy statsmodels

Cài đặt SDK Tardis (nếu sử dụng API trực tiếp)

pip install tardis-client

Kiểm tra phiên bản

python --version # Python 3.9+ được khuyến nghị pip list | grep -E "pandas|tardis"

2.2 Cấu trúc thư mục dự án

Tổ chức cấu trúc thư mục giúp quản lý dữ liệu hiệu quả:
# Cấu trúc thư mục đề xuất
project/
├── config/
│   └── settings.py          # Cấu hình API keys
├── data/
│   ├── raw/                 # Dữ liệu thô từ Tardis
│   ├── processed/           # Dữ liệu đã xử lý
│   └── analysis/            # Kết quả phân tích
├── notebooks/               # Jupyter notebooks
├── scripts/
│   ├── fetch_tardis.py      # Script tải dữ liệu
│   ├── analyze_options.py   # Phân tích quyền chọn
│   └── analyze_funding.py   # Phân tích funding rate
├── utils/
│   ├── data_loader.py       # Hàm load dữ liệu
│   └── calculations.py      # Công thức tính toán
└── requirements.txt

Tạo cấu trúc tự động

mkdir -p project/{config,data/{raw,processed,analysis},notebooks,scripts,utils} touch project/config/.env

Tải và xử lý dữ liệu Tardis CSV

3.1 Đăng ký và lấy API Key từ Tardis

Để sử dụng Tardis, bạn cần đăng ký tài khoản và lấy API key. Tardis cung cấp gói dùng thử với giới hạn data points nhất định.
# File: scripts/fetch_tardis.py
import os
import pandas as pd
from dotenv import load_dotenv
from datetime import datetime, timedelta
import requests
import time

load_dotenv('config/.env')

class TardisDataFetcher:
    """
    Class kết nối và tải dữ liệu từ Tardis
    Hỗ trợ: Options Chain, Funding Rates, OHLCV
    """
    
    BASE_URL = "https://api.tardis.dev/v1"
    
    def __init__(self, api_key: str = None):
        self.api_key = api_key or os.getenv('TARDIS_API_KEY')
        if not self.api_key:
            raise ValueError("TARDIS_API_KEY không được tìm thấy trong biến môi trường")
    
    def fetch_options_chain(self, exchange: str, symbol: str, 
                            start_date: str, end_date: str) -> pd.DataFrame:
        """
        Tải dữ liệu chuỗi quyền chọn (Options Chain)
        
        Args:
            exchange: Sàn giao dịch (binance, bybit, deribit, okex)
            symbol: Cặp tiền (BTC, ETH)
            start_date: Ngày bắt đầu (YYYY-MM-DD)
            end_date: Ngày kết thúc (YYYY-MM-DD)
        
        Returns:
            DataFrame chứa dữ liệu quyền chọn
        """
        url = f"{self.BASE_URL}/export/derivatives/options"
        params = {
            'exchange': exchange,
            'symbol': symbol,
            'start_date': start_date,
            'end_date': end_date,
            'api_key': self.api_key,
            'format': 'csv'
        }
        
        print(f"🔄 Đang tải dữ liệu Options Chain từ {exchange}...")
        start_time = time.time()
        
        response = requests.get(url, params=params, timeout=120)
        response.raise_for_status()
        
        # Lưu file CSV tạm thời
        temp_file = f"data/raw/options_{exchange}_{symbol}_{start_date}_{end_date}.csv"
        
        with open(temp_file, 'wb') as f:
            f.write(response.content)
        
        df = pd.read_csv(temp_file)
        
        elapsed = (time.time() - start_time) * 1000
        print(f"✅ Hoàn thành trong {elapsed:.2f}ms | {len(df)} dòng dữ liệu")
        
        return df
    
    def fetch_funding_rates(self, exchange: str, symbols: list,
                           start_date: str, end_date: str) -> pd.DataFrame:
        """
        Tải dữ liệu tỷ lệ tài trợ (Funding Rates)
        """
        all_data = []
        
        for symbol in symbols:
            url = f"{self.BASE_URL}/export/derivatives/funding-rates"
            params = {
                'exchange': exchange,
                'symbol': symbol,
                'start_date': start_date,
                'end_date': end_date,
                'api_key': self.api_key,
                'format': 'csv'
            }
            
            print(f"🔄 Đang tải Funding Rate: {symbol}")
            response = requests.get(url, params=params, timeout=60)
            response.raise_for_status()
            
            temp_file = f"data/raw/funding_{exchange}_{symbol}.csv"
            with open(temp_file, 'wb') as f:
                f.write(response.content)
            
            df = pd.read_csv(temp_file)
            all_data.append(df)
        
        combined_df = pd.concat(all_data, ignore_index=True)
        combined_df = combined_df.sort_values('timestamp')
        
        return combined_df

Sử dụng class

if __name__ == "__main__": fetcher = TardisDataFetcher() # Tải dữ liệu quyền chọn BTC từ Deribit options_df = fetcher.fetch_options_chain( exchange='deribit', symbol='BTC', start_date='2024-01-01', end_date='2024-03-01' ) # Tải funding rates từ Binance Futures funding_df = fetcher.fetch_funding_rates( exchange='binance', symbols=['BTCUSDT', 'ETHUSDT'], start_date='2024-01-01', end_date='2024-03-01' )

3.2 Xử lý và làm sạch dữ liệu

Dữ liệu thô từ Tardis thường cần được làm sạch trước khi phân tích:
# File: utils/data_loader.py
import pandas as pd
import numpy as np
from datetime import datetime
from typing import Tuple, List

class OptionsDataProcessor:
    """
    Xử lý và làm sạch dữ liệu quyền chọn từ Tardis CSV
    """
    
    def __init__(self, df: pd.DataFrame):
        self.df = df.copy()
        self._validate_columns()
    
    def _validate_columns(self):
        """Kiểm tra các cột bắt buộc"""
        required_cols = ['timestamp', 'strike', 'expiry', 'option_type', 'premium']
        missing = [col for col in required_cols if col not in self.df.columns]
        
        if missing:
            raise ValueError(f"Thiếu cột bắt buộc: {missing}")
    
    def clean_and_normalize(self) -> pd.DataFrame:
        """
        Làm sạch và chuẩn hóa dữ liệu
        """
        # Chuyển đổi timestamp
        self.df['timestamp'] = pd.to_datetime(self.df['timestamp'])
        self.df['expiry'] = pd.to_datetime(self.df['expiry'])
        
        # Tính thời gian đến hết hạn (ngày)
        self.df['days_to_expiry'] = (self.df['expiry'] - self.df['timestamp']).dt.days
        
        # Chuẩn hóa strike price
        self.df['strike_normalized'] = self.df['strike'] / self.df.get('underlying_price', self.df['strike'])
        
        # Xử lý giá trị null
        self.df['premium'] = self.df['premium'].fillna(method='ffill')
        
        # Loại bỏ outliers (giá trị premium âm hoặc quá cao)
        q1 = self.df['premium'].quantile(0.01)
        q99 = self.df['premium'].quantile(0.99)
        self.df = self.df[(self.df['premium'] >= q1) & (self.df['premium'] <= q99)]
        
        # Thêm cột moneyness
        underlying = self.df['underlying_price'].iloc[0] if 'underlying_price' in self.df.columns else self.df['strike'].mean()
        self.df['moneyness'] = np.where(
            self.df['option_type'] == 'call',
            self.df['strike'] / underlying,
            underlying / self.df['strike']
        )
        
        return self.df
    
    def calculate_greeks_proxy(self) -> pd.DataFrame:
        """
        Tính toán các chỉ số Delta, Gamma, Vega proxy từ dữ liệu
        (Lưu ý: Đây là ước tính đơn giản, cần mô hình Black-Scholes đầy đủ)
        """
        # Delta proxy dựa trên moneyness
        self.df['delta_proxy'] = np.where(
            self.df['option_type'] == 'call',
            np.clip((self.df['moneyness'] - 0.95) / 0.1, 0, 1),
            np.clip((1.05 - self.df['moneyness']) / 0.1, 0, 1)
        )
        
        # Gamma proxy (cao nhất gần ATM)
        self.df['gamma_proxy'] = np.exp(-abs(self.df['moneyness'] - 1) * 5) * 0.5
        
        # Vega proxy (cao hơn cho expiry dài hơn)
        self.df['vega_proxy'] = np.sqrt(self.df['days_to_expiry'] + 1) * 0.1
        
        return self.df
    
    def aggregate_by_expiry(self) -> pd.DataFrame:
        """
        Tổng hợp dữ liệu theo ngày hết hạn
        """
        agg_dict = {
            'strike': ['min', 'max', 'count'],
            'premium': ['mean', 'std'],
            'delta_proxy': ['mean'],
            'gamma_proxy': ['sum']
        }
        
        grouped = self.df.groupby(['expiry', 'option_type']).agg(agg_dict)
        grouped.columns = ['_'.join(col).strip() for col in grouped.columns]
        
        return grouped.reset_index()


class FundingRateProcessor:
    """
    Xử lý dữ liệu tỷ lệ tài trợ (Funding Rates)
    """
    
    def __init__(self, df: pd.DataFrame):
        self.df = df.copy()
    
    def clean(self) -> pd.DataFrame:
        """Làm sạch dữ liệu funding rate"""
        self.df['timestamp'] = pd.to_datetime(self.df['timestamp'])
        self.df = self.df.sort_values('timestamp')
        
        # Chuyển đổi funding rate sang tỷ lệ phần trăm
        if 'funding_rate' in self.df.columns:
            self.df['funding_rate_pct'] = self.df['funding_rate'] * 100
        
        # Xử lý giá trị bất thường
        self.df['funding_rate_pct'] = self.df['funding_rate_pct'].clip(-1, 1)
        
        return self.df
    
    def calculate_funding_metrics(self) -> pd.DataFrame:
        """
        Tính toán các chỉ số funding rate
        """
        # Tỷ lệ tài trợ trung bình theo ngày
        daily_funding = self.df.groupby(pd.Grouper(key='timestamp', freq='D'))['funding_rate_pct'].agg(['mean', 'std', 'min', 'max'])
        
        # Tổng funding ước tính (annualized)
        daily_funding['annualized_rate'] = daily_funding['mean'] * 365
        
        # Funding volatility
        daily_funding['funding_volatility'] = daily_funding['std'] * np.sqrt(365)
        
        # Phát hiện funding spikes (> 0.5% một ngày)
        daily_funding['is_spike'] = abs(daily_funding['mean']) > 0.5
        
        return daily_funding
    
    def detect_funding_regimes(self, window: int = 7) -> pd.DataFrame:
        """
        Phát hiện các chế độ thị trường dựa trên funding rate
        """
        self.df['funding_ma'] = self.df['funding_rate_pct'].rolling(window=window).mean()
        self.df['funding_std'] = self.df['funding_rate_pct'].rolling(window=window).std()
        
        # Phân loại regime
        def classify_regime(row):
            if row['funding_ma'] > 0.1:
                return 'bullish_high_funding'
            elif row['funding_ma'] < -0.1:
                return 'bearish_high_funding'
            elif row['funding_std'] > 0.3:
                return 'volatile'
            else:
                return 'neutral'
        
        self.df['regime'] = self.df.apply(classify_regime, axis=1)
        
        return self.df

Phân tích dữ liệu Quyền chọn (Options Chain Analysis)

4.1 Phân tích Open Interest và Volume

Open Interest (OI) là chỉ số quan trọng cho thấy dòng tiền và tâm lý thị trường:
# File: scripts/analyze_options.py
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from utils.data_loader import OptionsDataProcessor

Cài đặt style

plt.style.use('seaborn-v0_8-whitegrid') sns.set_palette("husl") class OptionsAnalyzer: """ Phân tích chuyên sâu dữ liệu quyền chọn """ def __init__(self, df: pd.DataFrame): self.df = df self.processor = OptionsDataProcessor(df) self.cleaned_df = self.processor.clean_and_normalize() self.greeks_df = self.processor.calculate_greeks_proxy() def calculate_max_pain(self) -> float: """ Tính Max Pain - mức giá gây thiệt hại tối đa cho người nắm giữ quyền chọn """ # Nhóm theo strike price strike_oi = self.cleaned_df.groupby('strike').agg({ 'premium': 'count', # Số lượng hợp đồng 'option_type': 'first' }).reset_index() # Tính pain cho mỗi mức giá tiềm năng pain_by_strike = [] for test_price in strike_oi['strike'].unique(): call_pain = 0 put_pain = 0 for _, row in self.cleaned_df[self.cleaned_df['strike'] == test_price].iterrows(): if row['option_type'] == 'call': call_pain += max(0, test_price - row['strike']) else: put_pain += max(0, row['strike'] - test_price) pain_by_strike.append({ 'strike': test_price, 'total_pain': call_pain + put_pain }) pain_df = pd.DataFrame(pain_by_strike) max_pain_strike = pain_df.loc[pain_df['total_pain'].idxmin(), 'strike'] return max_pain_strike def analyze_oi_distribution(self) -> dict: """ Phân tích phân bố Open Interest theo strike price """ # OI cho call options call_oi = self.greeks_df[self.greeks_df['option_type'] == 'call'].groupby('strike').agg({ 'strike_count': 'sum', 'delta_proxy_mean': 'mean' }) # OI cho put options put_oi = self.greeks_df[self.greeks_df['option_type'] == 'put'].groupby('strike').agg({ 'strike_count': 'sum', 'delta_proxy_mean': 'mean' }) # Tính Put/Call ratio total_call_oi = call_oi['strike_count'].sum() total_put_oi = put_oi['strike_count'].sum() pc_ratio = total_put_oi / total_call_oi if total_call_oi > 0 else 0 return { 'call_oi': call_oi, 'put_oi': put_oi, 'pc_ratio': pc_ratio, 'total_oi': total_call_oi + total_put_oi, 'call_dominant_levels': call_oi.nlargest(5, 'strike_count').index.tolist(), 'put_dominant_levels': put_oi.nlargest(5, 'strike_count').index.tolist() } def identify_gamma_exposure(self) -> pd.DataFrame: """ Xác định các mức gamma exposure - vùng có thể gây biến động mạnh """ # Gamma = Delta thay đổi khi giá thay đổi # ATM options có gamma cao nhất self.greeks_df['gamma_exposure'] = ( self.greeks_df['gamma_proxy'] * self.greeks_df['strike_count'] if 'strike_count' in self.greeks_df.columns else 0 ) # Sắp xếp theo gamma exposure gamma_levels = self.greeks_df.nlargest(10, 'gamma_proxy')[ ['strike', 'option_type', 'gamma_proxy', 'delta_proxy'] ] return gamma_levels def generate_options_report(self) -> dict: """ Tạo báo cáo tổng hợp về thị trường quyền chọn """ oi_analysis = self.analyze_oi_distribution() max_pain = self.calculate_max_pain() gamma_levels = self.identify_gamma_exposure() report = { 'summary': { 'total_contracts': len(self.cleaned_df), 'date_range': f"{self.cleaned_df['timestamp'].min()} to {self.cleaned_df['timestamp'].max()}", 'put_call_ratio': round(oi_analysis['pc_ratio'], 3), 'max_pain_strike': max_pain, 'atm_options_count': len(self.greeks_df[(self.greeks_df['moneyness'] > 0.98) & (self.greeks_df['moneyness'] < 1.02)]) }, 'oi_analysis': oi_analysis, 'gamma_levels': gamma_levels.to_dict('records'), 'recommendations': self._generate_recommendations(oi_analysis, max_pain) } return report def _generate_recommendations(self, oi_analysis: dict, max_pain: float) -> list: """ Đưa ra khuyến nghị dựa trên phân tích """ recommendations = [] # Phân tích Put/Call ratio if oi_analysis['pc_ratio'] > 1.5: recommendations.append({ 'type': 'bearish_sentiment', 'message': f"PCR = {oi_analysis['pc_ratio']:.2f} cho thấy tâm lý Bearish, " f"nhiều nhà đầu tư tìm kiếm bảo vệ bên dưới", 'action': 'Cân nhắc các chiến lược hedged' }) elif oi_analysis['pc_ratio'] < 0.6: recommendations.append({ 'type': 'bullish_sentiment', 'message': f"PCR = {oi_analysis['pc_ratio']:.2f} cho thấy tâm lý Bullish mạnh", 'action': 'Xem xét momentum strategies' }) # Max Pain recommendations.append({ 'type': 'max_pain', 'message': f"Max Pain ở mức ${max_pain:,.0f}", 'action': 'Thị trường có xu hướng bị kéo về mức này khi expiry gần' }) return recommendations

Ví dụ sử dụng

if __name__ == "__main__": # Load dữ liệu (giả định đã tải từ Tardis) df = pd.read_csv('data/raw/options_deribit_BTC_2024.csv') analyzer = OptionsAnalyzer(df) report = analyzer.generate_options_report() print("=" * 60) print("BÁO CÁO PHÂN TÍCH QUYỀN CHỌN BTC") print("=" * 60) print(f"Tổng hợp đồng: {report['summary']['total_contracts']}") print(f"PCR: {report['summary']['put_call_ratio']}") print(f"Max Pain: ${report['summary']['max_pain_strike']:,.0f}") print("\nKhuyến nghị:") for rec in report['recommendations']: print(f" • [{rec['type']}] {rec['message']}") print(f" → {rec['action']}")

Phân tích Funding Rates và Correlation

5.1 Chiến lược giao dịch dựa trên Funding Rate

Funding Rate là chỉ số quan trọng để đánh giá tâm lý thị trường và xác định cơ hội arbitrage:
# File: scripts/analyze_funding.py
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from utils.data_loader import FundingRateProcessor

class FundingRateStrategyAnalyzer:
    """
    Phân tích chiến lược giao dịch dựa trên Funding Rates
    """
    
    def __init__(self, df: pd.DataFrame, price_df: pd.DataFrame = None):
        self.df = df
        self.processor = FundingRateProcessor(df)
        self.cleaned_df = self.processor.clean()
        self.price_df = price_df
    
    def calculate_historical_funding_stats(self) -> dict:
        """
        Tính toán thống kê funding rate lịch sử
        """
        stats_dict = {
            'mean': self.cleaned_df['funding_rate_pct'].mean(),
            'median': self.cleaned_df['funding_rate_pct'].median(),
            'std': self.cleaned_df['funding_rate_pct'].std(),
            'min': self.cleaned_df['funding_rate_pct'].min(),
            'max': self.cleaned_df['funding_rate_pct'].max(),
            'p5': self.cleaned_df['funding_rate_pct'].quantile(0.05),
            'p95': self.cleaned_df['funding_rate_pct'].quantile(0.95),
            'annualized_avg': self.cleaned_df['funding_rate_pct'].mean() * 365
        }
        
        return stats_dict
    
    def detect_funding_regimes(self) -> pd.DataFrame:
        """
        Phát hiện các chế độ funding dựa trên thresholds
        """
        df = self.cleaned_df.copy()
        
        # Định nghĩa regimes
        conditions = [
            (df['funding_rate_pct'] > 0.1),    # High positive funding
            (df['funding_rate_pct'] < -0.1),   # High negative funding
            (df['funding_rate_pct'].abs() < 0.02)  # Near zero
        ]
        choices = ['high_positive', 'high_negative', 'neutral']
        
        df['regime'] = np.select(conditions, choices, default='moderate')
        
        # Thống kê theo regime
        regime_stats = df.groupby('regime').agg({
            'funding_rate_pct': ['count', 'mean', 'std'],
            'timestamp': ['min', 'max']
        })
        
        return df, regime_stats
    
    def calculate_funding_price_correlation(self) -> dict:
        """
        Tính correlation giữa funding rate và giá
        """
        if self.price_df is None:
            return {'error': 'Cần cung cấp price_df để tính correlation'}
        
        # Merge data
        merged = pd.merge_asof(
            self.cleaned_df.sort_values('timestamp'),
            self.price_df.sort_values('timestamp'),
            on='timestamp',
            direction='nearest',
            tolerance=pd.Timedelta('1h')
        )
        
        # Tính returns
        merged['price_return'] = merged['close'].pct_change()
        merged['funding_change'] = merged['funding_rate_pct'].diff()
        
        # Correlation
        valid_data = merged.dropna()
        correlation = valid_data['funding_rate_pct'].corr(valid_data['price_return'])
        
        # Granger causality test (đơn giản hóa)
        lag_corr = {}
        for lag in [1, 2, 3, 8, 24]:
            lag_corr[f'lag_{lag}'] = valid_data['funding_rate_pct'].corr(
                valid_data['price_return'].shift(lag)
            )
        
        return {
            'correlation': correlation,
            'lag_correlations': lag_corr,
            'interpretation': self._interpret_correlation(correlation)
        }
    
    def _interpret_correlation(self, corr: float) -> str:
        """Diễn giải hệ số tương quan"""
        abs_corr = abs(corr)
        
        if abs_corr < 0.1:
            return "Gần như không có tương quan"
        elif abs_corr < 0.3:
            return "Tương quan yếu"
        elif abs_corr < 0.5:
            return "Tương quan trung bình"
        elif abs_corr < 0.7:
            return "Tương quan khá mạnh"
        else:
            return "Tương quan mạnh"
    
    def backtest_funding_strategy(self, threshold: float = 0.05, 
                                   holding_period: int = 24) -> pd.DataFrame:
        """
        Backtest chiến lược giao dịch dựa trên funding threshold
        """
        df = self.cleaned_df.copy()
        df = df.sort_values('timestamp')
        
        # Tính signal
        df['signal'] = 0
        df.loc[df['funding_rate_pct'] > threshold, 'signal'] = -1  # Short when high funding
        df.loc[df['funding_rate_pct'] < -threshold, 'signal'] = 1   # Long when negative funding
        
        # Tính returns (giả định position size = 1)
        if self.price_df is not None:
            merged = pd.merge_asof(
                df, 
                self.price_df.sort_values('timestamp'),
                on='timestamp',
                direction='nearest'
            )
            
            merged['strategy_return'] = merged['signal'].shift(1) * merged['close'].pct_change(holding_period)
            
            # Loại bỏ NaN
            valid_returns = merged.dropna()
            
            results = {
                'total_trades': (valid_returns['signal'] != 0).sum(),
                'winning_trades': (valid_returns['strategy_return'] > 0).sum(),
                'total_return': valid_returns['strategy_return'].sum(),
                'avg_return': valid_returns['strategy_return'].mean(),
                'sharpe_ratio': valid_returns['strategy_return'].mean() / valid_returns['strategy_return'].std() * np.sqrt(365) if valid_returns['strategy_return'].std() > 0 else 0,
                'max_drawdown': self._calculate_max_drawdown(valid_returns['strategy_return'].cumsum())
            }
            
            return pd.DataFrame([results])
        
        return pd.DataFrame([{'error': 'Không có dữ liệu giá để backtest'}])
    
    def _calculate_max_drawdown(self, cumulative_returns: pd.Series) -> float:
        """Tính maximum drawdown"""
        running_max = cumulative_returns.expanding().max()
        drawdown = cumulative_returns - running_max