דוחות Backtest מקצועיים: מדדים ודשבורדים
הרצת backtest, קיבלת תשואה סופית של 340% — ועכשיו מה? בלי דוח מפורט, זה רק מספר. ראינו אסטרטגיות עם אחוז זכייה מעל 80% שעדיין הפסידו 60% מההון בחודש — כי משיכה מקסימלית (max drawdown) לא חושבה ופיזור העסקאות לא נותח. פיתוח דוחות backtest מוכנים לשימוש אינו רק פליטת מספרים; זו מערכת שחושפת סיכונים נסתרים, משווה השערות, ומצדיקה החלטות. הניסיון שלנו — מעל 5 שנים במסחר קריפטו ו-DeFi — מאפשר לנו לספק דוחות שבאמת נמצאים בשימוש בפועל. אוטמטנו חישובי מדדים כך שלא תבזבז זמן על גיליונות אלקטרוניים ידניים. סוחרים רבים מפסידים כסף כשהם מסתמכים רק על P&L — דוח מפורט עם מדדים מותאמי סיכון מונע זאת.
למה דוחות ברוקר סטנדרטיים לא מתאימים
דוחות ברוקר סטנדרטיים מספקים רק P&L ותשואה פשוטה. למסחר אלגוריתמי, אתה צריך מדדים מותאמי סיכון: Sharpe, Sortino, משך משיכה, פקטור רווח. בלעדיהם, אי אפשר לדעת אם אסטרטגיה תשרוד זעזוע שוק או רצף הפסדים. אנחנו משתמשים ב-pandas, plotly, ובמחלקות נתונים משלנו (ראה קוד למטה). כל הצינור אוטומטי — מייבוא עסקאות ועד דשבורד HTML אינטראקטיבי. יחס Sharpe נמצא בשימוש נרחב בפיננסים (Sharpe ratio). אנחנו מבטיחים שכל דוח עובר בדיקת עקביות.
אילו מלכודות נסתרות backtest טיפוסי מסתיר?
לקוח אחד הגיע עם אסטרטגיית Binance Futures שהראתה Sharpe של 2.3. הרצנו דוח עם חישוב משיכה נכון — ומצאנו ארבע משיכות מתחת ל-40%- . הסיבה: האסטרטגיה לא הייתה שורדת 2-3 עסקאות מפסידות רצופות. בדוח, הוספנו סעיף "משך משיכה מקסימלי" ו-"Rolling Sharpe". הלקוח עבד מחדש על לוגיקת ניהול הסיכונים שלו — ותוך חודש ה-Sharpe עלה ל-3.8. הגישה שלנו מהירה פי 3 מחישובי Excel ידניים. עם ניסיון של מעל 5 שנים ו-50+ פרויקטים שהושלמו, אנחנו מספקים דוחות אמינים. צוות האנליסטים המוסמכים שלנו מבטיח חישוב מדדים מדויק.
מה כלול
אנחנו מספקים חבילה מלאה:
- דשבורד HTML אינטראקטיבי (Plotly) עם עקומת הון, משיכה, מפת חום של תשואות חודשיות, והתפלגות P&L.
- קבצי מקור בפורמט JSON/CSV עבור מערכת ניהול הסיכונים שלך.
- תיעוד: פירוש של כל גרף ומדד.
- הדרכת צוות: כיצד לעדכן את הדוח כשהאסטרטגיה משתנה.
- תמיכת התאמה: אנחנו עוזרים לשלב את הדוח עם ה-API שלך.
זו לא עבודה חד-פעמית: אנחנו נשארים בקשר כדי לחדד מדדים ככל שעולות דרישות חדשות. פיתוח דוח מתחיל ב-$1,500 וכולל הערכת פרויקט חינם.
הרכב הדוח
- דשבורדים: עקומת הון, משיכה, מפת חום של תשואות חודשיות, התפלגות P&L (נבנה עם
Binance Futuresאוplotly). - מדדים: Sharpe, Sortino, Calmar, פקטור רווח, אחוז זכייה, ממוצע רווח/הפסד, משך עסקה.
- פורמטים: JSON, CSV, HTML עבור ניהול הסיכונים שלך.
- תיעוד: כיצד לפרש כל גרף.
- תמיכה לאחר מסירה: אנחנו עוזרים להתאים את הדוח לאסטרטגיות חדשות.
איך לפרש מדדים?
בואו נשווה טעויות נפוצות. אחוז זכייה גבוה לא יציל אותך אם ההפסד הממוצע גדול פי 3 מהרווח הממוצע. פקטור רווח (רווח גולמי / הפסד גולמי) צריך להיות > 2. יחס Sortino עדיף על Sharpe — הוא מעניש רק תנודתיות שלילית. טבלת הפירוש שלנו:
| מדד | טוב | סביר | חלש |
|---|---|---|---|
| יחס Sharpe | > 2.0 | 1.0–2.0 | < 1.0 |
| יחס Sortino | > 2.5 | 1.5–2.5 | < 1.5 |
| משיכה מקסימלית | < 15% | 15–30% | > 30% |
| פקטור רווח | > 2.0 | 1.5–2.0 | < 1.5 |
| אחוז זכייה | > 55% (מעקב אחר מגמה) | 45–55% | < 45% |
חשוב: אחוז זכייה ללא יחס סיכון/תגמול הוא מלכודת. פקטור רווח ותוחלת חשובים יותר. לניתוח אסטרטגיות מתקדם, אנחנו גם משלבים אופטימיזציית walk-forward וסימולציית מונטה קרלו.
השוואת פורמטי דוח
| פורמט | יתרונות | חסרונות |
|---|---|---|
| HTML (Plotly) | גרפים אינטראקטיביים, קל לשיתוף | לא מתאים לעיבוד אוטומטי |
| JSON | קריא למכונה, אינטגרציית API | דורש ויזואליזציה נפרדת |
| CSV | אוניברסלי, נפתח ב-Excel | אין גרפים, קבצים גדולים |
איך אנחנו בונים את הדוח: שלב אחר שלב
- ייבוא נתונים — ייצוא עסקאות מ-API או CSV.
- חישוב מדדים — חישוב Sharpe, Sortino, משיכה, ומדדים נוספים.
- יצירת גרפים — עקומת הון, משיכה, מפת חום חודשית, התפלגות P&L.
- ולידציה — בדיקה מול נתוני המקור, אימות עקביות.
- מסירה — דשבורד HTML + קבצי מקור (JSON/CSV).
צור קשר להערכת פרויקט חינם. הזמן את הדוח שלך היום וקבל ייעוץ על אופטימיזציית מדדים.
הצג קוד
from dataclasses import dataclass
from typing import Optional
import pandas as pd
import numpy as np
@dataclass
class BacktestReport:
# Сводные метрики
initial_capital: float
final_capital: float
total_return_pct: float
annual_return_pct: float
# Risk-adjusted
sharpe_ratio: float
sortino_ratio: float
calmar_ratio: float
# Drawdown
max_drawdown_pct: float
avg_drawdown_pct: float
max_drawdown_duration_days: int
# Торговля
total_trades: int
win_rate: float
profit_factor: float
avg_win_pct: float
avg_loss_pct: float
best_trade_pct: float
worst_trade_pct: float
avg_trade_duration_hours: float
# Комиссии
total_commission: float
commission_as_pct_of_pnl: float
# Временные серии
equity_curve: pd.Series
monthly_returns: pd.DataFrame
trade_list: pd.DataFrame
def compute_all_metrics(equity_curve: pd.Series, trades: list[dict]) -> BacktestReport:
returns = equity_curve.pct_change().dropna()
annual_factor = 252
# Базовые
total_return = (equity_curve.iloc[-1] / equity_curve.iloc[0]) - 1
days = (equity_curve.index[-1] - equity_curve.index[0]).days
annual_return = (1 + total_return) ** (365 / max(days, 1)) - 1
# Sharpe (risk-free rate = 0 для крипто)
sharpe = (returns.mean() * annual_factor) / (returns.std() * np.sqrt(annual_factor)) if returns.std() > 0 else 0
# Sortino (только downside volatility)
downside = returns[returns < 0].std()
sortino = (returns.mean() * annual_factor) / (downside * np.sqrt(annual_factor)) if downside > 0 else 0
# Drawdown
rolling_max = equity_curve.cummax()
drawdown_series = (equity_curve - rolling_max) / rolling_max
max_dd = drawdown_series.min()
# Max drawdown duration
in_drawdown = drawdown_series < 0
dd_start = None
max_duration = 0
for date, is_dd in in_drawdown.items():
if is_dd and dd_start is None:
dd_start = date
elif not is_dd and dd_start is not None:
duration = (date - dd_start).days
max_duration = max(max_duration, duration)
dd_start = None
# Calmar
calmar = annual_return / abs(max_dd) if max_dd != 0 else 0
# Trade-level
trades_df = pd.DataFrame(trades)
if not trades_df.empty:
winning = trades_df[trades_df['pnl'] > 0]
losing = trades_df[trades_df['pnl'] < 0]
win_rate = len(winning) / len(trades_df)
gross_profit = winning['pnl'].sum()
gross_loss = abs(losing['pnl'].sum())
profit_factor = gross_profit / gross_loss if gross_loss > 0 else float('inf')
avg_win_pct = (winning['pnl'] / winning['entry_value'] * 100).mean() if not winning.empty else 0
avg_loss_pct = (losing['pnl'] / losing['entry_value'] * 100).mean() if not losing.empty else 0
else:
win_rate = profit_factor = avg_win_pct = avg_loss_pct = 0
return BacktestReport(
initial_capital=equity_curve.iloc[0],
final_capital=equity_curve.iloc[-1],
total_return_pct=total_return * 100,
annual_return_pct=annual_return * 100,
sharpe_ratio=round(sharpe, 3),
sortino_ratio=round(sortino, 3),
calmar_ratio=round(calmar, 3),
max_drawdown_pct=max_dd * 100,
avg_drawdown_pct=drawdown_series[drawdown_series < 0].mean() * 100,
max_drawdown_duration_days=max_duration,
total_trades=len(trades),
win_rate=win_rate,
profit_factor=profit_factor,
avg_win_pct=avg_win_pct,
avg_loss_pct=avg_loss_pct,
equity_curve=equity_curve,
trade_list=trades_df,
)
def compute_monthly_returns(equity_curve: pd.Series) -> pd.DataFrame:
"""Создаём матрицу месячных доходностей для heatmap"""
monthly = equity_curve.resample('ME').last()
monthly_returns = monthly.pct_change().dropna()
# Создаём матрицу год × месяц
matrix = monthly_returns.groupby([
monthly_returns.index.year,
monthly_returns.index.month
]).first().unstack()
matrix.columns = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
return matrix * 100 # в процентах
def generate_html_report(report: BacktestReport, strategy_name: str) -> str:
import plotly.graph_objects as go
from plotly.subplots import make_subplots
fig = make_subplots(
rows=3, cols=2,
subplot_titles=['Equity Curve', 'Drawdown', 'Monthly Returns', 'Trade P&L Distribution', 'Win/Loss', 'Rolling Sharpe'],
)
# Equity curve
fig.add_trace(go.Scatter(x=report.equity_curve.index, y=report.equity_curve.values,
name='Portfolio', line=dict(color='#00C853')), row=1, col=1)
# Drawdown
rolling_max = report.equity_curve.cummax()
drawdown = (report.equity_curve - rolling_max) / rolling_max * 100
fig.add_trace(go.Scatter(x=drawdown.index, y=drawdown.values,
fill='tozeroy', name='Drawdown', line=dict(color='#FF5252')), row=1, col=2)
# P&L distribution
if not report.trade_list.empty:
pnl_pct = report.trade_list['pnl'] / report.trade_list['entry_value'] * 100
fig.add_trace(go.Histogram(x=pnl_pct, name='Trade P&L %', nbinsx=30), row=2, col=1)
fig.update_layout(
title=f'Backtest Report: {strategy_name}',
height=1000,
showlegend=False,
template='plotly_dark',
)
return fig.to_html(include_plotlyjs='cdn')







