deltafq 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of deltafq might be problematic. Click here for more details.
- deltafq/__init__.py +30 -31
- deltafq/backtest/__init__.py +17 -7
- deltafq/backtest/engine.py +99 -52
- deltafq/backtest/metrics.py +113 -0
- deltafq/backtest/performance.py +81 -0
- deltafq/backtest/reporter.py +91 -0
- deltafq/core/__init__.py +19 -0
- deltafq/core/base.py +37 -0
- deltafq/core/config.py +63 -0
- deltafq/core/exceptions.py +35 -0
- deltafq/core/logger.py +46 -0
- deltafq/data/__init__.py +17 -7
- deltafq/data/cleaner.py +41 -0
- deltafq/data/fetcher.py +52 -0
- deltafq/data/storage.py +56 -0
- deltafq/data/validator.py +52 -0
- deltafq/indicators/__init__.py +17 -8
- deltafq/indicators/momentum.py +56 -23
- deltafq/indicators/technical.py +59 -0
- deltafq/indicators/trend.py +129 -61
- deltafq/indicators/volatility.py +67 -27
- deltafq/live/__init__.py +17 -0
- deltafq/live/connection.py +235 -0
- deltafq/live/data_feed.py +159 -0
- deltafq/live/monitoring.py +192 -0
- deltafq/live/risk_control.py +193 -0
- deltafq/strategy/__init__.py +17 -6
- deltafq/strategy/base_strategy.py +53 -0
- deltafq/strategy/portfolio.py +82 -0
- deltafq/strategy/risk_manager.py +64 -0
- deltafq/strategy/signal_generator.py +52 -0
- deltafq/trading/__init__.py +19 -0
- deltafq/trading/broker.py +119 -0
- deltafq/trading/execution.py +176 -0
- deltafq/trading/order_manager.py +111 -0
- deltafq/trading/position_manager.py +157 -0
- deltafq/trading/simulator.py +150 -0
- deltafq-0.1.2.dist-info/METADATA +110 -0
- deltafq-0.1.2.dist-info/RECORD +43 -0
- deltafq-0.1.2.dist-info/entry_points.txt +2 -0
- {deltafq-0.1.1.dist-info → deltafq-0.1.2.dist-info}/licenses/LICENSE +21 -22
- deltafq/backtest/result.py +0 -45
- deltafq/data/base.py +0 -30
- deltafq/data/loader.py +0 -63
- deltafq/optimization/__init__.py +0 -6
- deltafq/optimization/grid_search.py +0 -41
- deltafq/performance/__init__.py +0 -6
- deltafq/performance/metrics.py +0 -37
- deltafq/risk/__init__.py +0 -7
- deltafq/risk/metrics.py +0 -33
- deltafq/risk/position.py +0 -39
- deltafq/strategy/base.py +0 -44
- deltafq/trade/__init__.py +0 -6
- deltafq/trade/broker.py +0 -40
- deltafq/utils/__init__.py +0 -6
- deltafq/utils/time.py +0 -32
- deltafq-0.1.1.dist-info/METADATA +0 -202
- deltafq-0.1.1.dist-info/RECORD +0 -29
- {deltafq-0.1.1.dist-info → deltafq-0.1.2.dist-info}/WHEEL +0 -0
- {deltafq-0.1.1.dist-info → deltafq-0.1.2.dist-info}/top_level.txt +0 -0
deltafq/__init__.py
CHANGED
|
@@ -1,31 +1,30 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
|
|
20
|
-
__all__ = [
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"strategy",
|
|
24
|
-
"backtest",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
"""
|
|
2
|
+
DeltaFQ - A comprehensive Python quantitative finance library.
|
|
3
|
+
|
|
4
|
+
This library provides tools for strategy development, backtesting,
|
|
5
|
+
paper trading, and live trading.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
__author__ = "DeltaF"
|
|
10
|
+
|
|
11
|
+
# Import core modules
|
|
12
|
+
from . import core
|
|
13
|
+
from . import data
|
|
14
|
+
from . import strategy
|
|
15
|
+
from . import backtest
|
|
16
|
+
from . import indicators
|
|
17
|
+
from . import trading
|
|
18
|
+
from . import live
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"core",
|
|
22
|
+
"data",
|
|
23
|
+
"strategy",
|
|
24
|
+
"backtest",
|
|
25
|
+
"indicators",
|
|
26
|
+
"trading",
|
|
27
|
+
"live"
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
deltafq/backtest/__init__.py
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
"""
|
|
2
|
+
Backtesting module for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .engine import BacktestEngine
|
|
6
|
+
from .performance import PerformanceAnalyzer
|
|
7
|
+
from .metrics import MetricsCalculator
|
|
8
|
+
from .reporter import BacktestReporter
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"BacktestEngine",
|
|
12
|
+
"PerformanceAnalyzer",
|
|
13
|
+
"MetricsCalculator",
|
|
14
|
+
"BacktestReporter"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
deltafq/backtest/engine.py
CHANGED
|
@@ -1,52 +1,99 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
data:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
1
|
+
"""
|
|
2
|
+
Backtesting engine for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
7
|
+
from ..core.base import BaseComponent
|
|
8
|
+
from ..core.exceptions import BacktestError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BacktestEngine(BaseComponent):
|
|
12
|
+
"""Backtesting engine for strategy testing."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, initial_capital: float = 100000, commission: float = 0.001, **kwargs):
|
|
15
|
+
"""Initialize backtest engine."""
|
|
16
|
+
super().__init__(**kwargs)
|
|
17
|
+
self.initial_capital = initial_capital
|
|
18
|
+
self.commission = commission
|
|
19
|
+
self.results = None
|
|
20
|
+
|
|
21
|
+
def initialize(self) -> bool:
|
|
22
|
+
"""Initialize backtest engine."""
|
|
23
|
+
self.logger.info(f"Initializing backtest engine with capital: {self.initial_capital}")
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
def run_backtest(self, strategy, data: pd.DataFrame) -> Dict[str, Any]:
|
|
27
|
+
"""Run backtest for given strategy and data."""
|
|
28
|
+
try:
|
|
29
|
+
self.logger.info("Starting backtest")
|
|
30
|
+
|
|
31
|
+
# Initialize portfolio
|
|
32
|
+
cash = self.initial_capital
|
|
33
|
+
positions = {}
|
|
34
|
+
trades = []
|
|
35
|
+
|
|
36
|
+
# Run strategy on data
|
|
37
|
+
for i, (date, row) in enumerate(data.iterrows()):
|
|
38
|
+
# Get signals from strategy
|
|
39
|
+
signals = strategy.generate_signals(data.iloc[:i+1])
|
|
40
|
+
|
|
41
|
+
if not signals.empty and i > 0:
|
|
42
|
+
signal = signals.iloc[-1]
|
|
43
|
+
|
|
44
|
+
# Execute trades based on signals
|
|
45
|
+
if signal != 0: # Buy or sell signal
|
|
46
|
+
symbol = 'STOCK' # Simplified for single asset
|
|
47
|
+
price = row['close']
|
|
48
|
+
quantity = int(signal * cash * 0.1 / price) # Use 10% of cash
|
|
49
|
+
|
|
50
|
+
if quantity > 0 and quantity * price <= cash:
|
|
51
|
+
# Buy
|
|
52
|
+
cost = quantity * price * (1 + self.commission)
|
|
53
|
+
cash -= cost
|
|
54
|
+
positions[symbol] = positions.get(symbol, 0) + quantity
|
|
55
|
+
trades.append({
|
|
56
|
+
'date': date,
|
|
57
|
+
'symbol': symbol,
|
|
58
|
+
'quantity': quantity,
|
|
59
|
+
'price': price,
|
|
60
|
+
'type': 'buy'
|
|
61
|
+
})
|
|
62
|
+
elif quantity < 0 and positions.get(symbol, 0) >= abs(quantity):
|
|
63
|
+
# Sell
|
|
64
|
+
quantity = abs(quantity)
|
|
65
|
+
proceeds = quantity * price * (1 - self.commission)
|
|
66
|
+
cash += proceeds
|
|
67
|
+
positions[symbol] -= quantity
|
|
68
|
+
trades.append({
|
|
69
|
+
'date': date,
|
|
70
|
+
'symbol': symbol,
|
|
71
|
+
'quantity': -quantity,
|
|
72
|
+
'price': price,
|
|
73
|
+
'type': 'sell'
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
# Calculate final portfolio value
|
|
77
|
+
final_price = data['close'].iloc[-1]
|
|
78
|
+
final_value = cash + sum(positions.values()) * final_price
|
|
79
|
+
|
|
80
|
+
self.results = {
|
|
81
|
+
'initial_capital': self.initial_capital,
|
|
82
|
+
'final_value': final_value,
|
|
83
|
+
'total_return': (final_value - self.initial_capital) / self.initial_capital,
|
|
84
|
+
'trades': trades,
|
|
85
|
+
'final_positions': positions,
|
|
86
|
+
'final_cash': cash
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
self.logger.info(f"Backtest completed. Total return: {self.results['total_return']:.2%}")
|
|
90
|
+
return self.results
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
raise BacktestError(f"Backtest failed: {str(e)}")
|
|
94
|
+
|
|
95
|
+
def get_results(self) -> Optional[Dict[str, Any]]:
|
|
96
|
+
"""Get backtest results."""
|
|
97
|
+
return self.results
|
|
98
|
+
|
|
99
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backtest metrics calculation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import numpy as np
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from ..core.base import BaseComponent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MetricsCalculator(BaseComponent):
|
|
12
|
+
"""Calculate various backtest metrics."""
|
|
13
|
+
|
|
14
|
+
def initialize(self) -> bool:
|
|
15
|
+
"""Initialize metrics calculator."""
|
|
16
|
+
self.logger.info("Initializing metrics calculator")
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
def calculate_trade_metrics(self, trades: list) -> Dict[str, Any]:
|
|
20
|
+
"""Calculate trade-based metrics."""
|
|
21
|
+
if not trades:
|
|
22
|
+
return {}
|
|
23
|
+
|
|
24
|
+
trade_df = pd.DataFrame(trades)
|
|
25
|
+
|
|
26
|
+
# Separate buy and sell trades
|
|
27
|
+
buy_trades = trade_df[trade_df['type'] == 'buy']
|
|
28
|
+
sell_trades = trade_df[trade_df['type'] == 'sell']
|
|
29
|
+
|
|
30
|
+
# Calculate P&L for each trade (simplified)
|
|
31
|
+
pnl = []
|
|
32
|
+
for _, sell_trade in sell_trades.iterrows():
|
|
33
|
+
# Find corresponding buy trade
|
|
34
|
+
buy_trade = buy_trades[buy_trades['symbol'] == sell_trade['symbol']].iloc[0]
|
|
35
|
+
trade_pnl = (sell_trade['price'] - buy_trade['price']) * sell_trade['quantity']
|
|
36
|
+
pnl.append(trade_pnl)
|
|
37
|
+
|
|
38
|
+
if pnl:
|
|
39
|
+
pnl_series = pd.Series(pnl)
|
|
40
|
+
return {
|
|
41
|
+
'total_trades': len(pnl),
|
|
42
|
+
'winning_trades': (pnl_series > 0).sum(),
|
|
43
|
+
'losing_trades': (pnl_series < 0).sum(),
|
|
44
|
+
'win_rate': (pnl_series > 0).mean(),
|
|
45
|
+
'avg_win': pnl_series[pnl_series > 0].mean() if (pnl_series > 0).any() else 0,
|
|
46
|
+
'avg_loss': pnl_series[pnl_series < 0].mean() if (pnl_series < 0).any() else 0,
|
|
47
|
+
'total_pnl': pnl_series.sum()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {}
|
|
51
|
+
|
|
52
|
+
def calculate_risk_metrics(self, returns: pd.Series) -> Dict[str, float]:
|
|
53
|
+
"""Calculate risk-related metrics."""
|
|
54
|
+
if len(returns) == 0:
|
|
55
|
+
return {}
|
|
56
|
+
|
|
57
|
+
# Value at Risk (VaR)
|
|
58
|
+
var_95 = np.percentile(returns, 5)
|
|
59
|
+
var_99 = np.percentile(returns, 1)
|
|
60
|
+
|
|
61
|
+
# Conditional Value at Risk (CVaR)
|
|
62
|
+
cvar_95 = returns[returns <= var_95].mean()
|
|
63
|
+
cvar_99 = returns[returns <= var_99].mean()
|
|
64
|
+
|
|
65
|
+
# Skewness and Kurtosis
|
|
66
|
+
skewness = returns.skew()
|
|
67
|
+
kurtosis = returns.kurtosis()
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
'var_95': var_95,
|
|
71
|
+
'var_99': var_99,
|
|
72
|
+
'cvar_95': cvar_95,
|
|
73
|
+
'cvar_99': cvar_99,
|
|
74
|
+
'skewness': skewness,
|
|
75
|
+
'kurtosis': kurtosis
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
def calculate_portfolio_metrics(self, portfolio_values: pd.Series) -> Dict[str, float]:
|
|
79
|
+
"""Calculate portfolio-level metrics."""
|
|
80
|
+
if len(portfolio_values) < 2:
|
|
81
|
+
return {}
|
|
82
|
+
|
|
83
|
+
returns = portfolio_values.pct_change().dropna()
|
|
84
|
+
|
|
85
|
+
# Rolling metrics
|
|
86
|
+
rolling_returns = returns.rolling(window=252) # Annual window
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
'total_return': (portfolio_values.iloc[-1] / portfolio_values.iloc[0]) - 1,
|
|
90
|
+
'annualized_return': (1 + returns.mean()) ** 252 - 1,
|
|
91
|
+
'volatility': returns.std() * np.sqrt(252),
|
|
92
|
+
'sharpe_ratio': returns.mean() / returns.std() * np.sqrt(252) if returns.std() > 0 else 0,
|
|
93
|
+
'max_drawdown': self._calculate_max_drawdown(portfolio_values),
|
|
94
|
+
'calmar_ratio': self._calculate_calmar_ratio(returns)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
def _calculate_max_drawdown(self, values: pd.Series) -> float:
|
|
98
|
+
"""Calculate maximum drawdown."""
|
|
99
|
+
peak = values.expanding().max()
|
|
100
|
+
drawdown = (values - peak) / peak
|
|
101
|
+
return drawdown.min()
|
|
102
|
+
|
|
103
|
+
def _calculate_calmar_ratio(self, returns: pd.Series) -> float:
|
|
104
|
+
"""Calculate Calmar ratio."""
|
|
105
|
+
annual_return = (1 + returns.mean()) ** 252 - 1
|
|
106
|
+
max_dd = abs(self._calculate_max_drawdown((1 + returns).cumprod()))
|
|
107
|
+
|
|
108
|
+
if max_dd == 0:
|
|
109
|
+
return float('inf') if annual_return > 0 else 0.0
|
|
110
|
+
|
|
111
|
+
return annual_return / max_dd
|
|
112
|
+
|
|
113
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Performance analysis for backtests.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import numpy as np
|
|
7
|
+
from typing import Dict, List, Any
|
|
8
|
+
from ..core.base import BaseComponent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PerformanceAnalyzer(BaseComponent):
|
|
12
|
+
"""Analyze backtest performance."""
|
|
13
|
+
|
|
14
|
+
def initialize(self) -> bool:
|
|
15
|
+
"""Initialize performance analyzer."""
|
|
16
|
+
self.logger.info("Initializing performance analyzer")
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
def calculate_returns(self, prices: pd.Series) -> pd.Series:
|
|
20
|
+
"""Calculate returns from price series."""
|
|
21
|
+
return prices.pct_change().dropna()
|
|
22
|
+
|
|
23
|
+
def calculate_sharpe_ratio(self, returns: pd.Series, risk_free_rate: float = 0.02) -> float:
|
|
24
|
+
"""Calculate Sharpe ratio."""
|
|
25
|
+
if len(returns) == 0 or returns.std() == 0:
|
|
26
|
+
return 0.0
|
|
27
|
+
|
|
28
|
+
excess_returns = returns - risk_free_rate / 252 # Daily risk-free rate
|
|
29
|
+
return excess_returns.mean() / returns.std() * np.sqrt(252)
|
|
30
|
+
|
|
31
|
+
def calculate_max_drawdown(self, returns: pd.Series) -> float:
|
|
32
|
+
"""Calculate maximum drawdown."""
|
|
33
|
+
cumulative = (1 + returns).cumprod()
|
|
34
|
+
running_max = cumulative.expanding().max()
|
|
35
|
+
drawdown = (cumulative - running_max) / running_max
|
|
36
|
+
return drawdown.min()
|
|
37
|
+
|
|
38
|
+
def calculate_volatility(self, returns: pd.Series) -> float:
|
|
39
|
+
"""Calculate annualized volatility."""
|
|
40
|
+
return returns.std() * np.sqrt(252)
|
|
41
|
+
|
|
42
|
+
def analyze_performance(self, returns: pd.Series, benchmark_returns: pd.Series = None) -> Dict[str, float]:
|
|
43
|
+
"""Comprehensive performance analysis."""
|
|
44
|
+
analysis = {
|
|
45
|
+
'total_return': (1 + returns).prod() - 1,
|
|
46
|
+
'annualized_return': (1 + returns).prod() ** (252 / len(returns)) - 1,
|
|
47
|
+
'volatility': self.calculate_volatility(returns),
|
|
48
|
+
'sharpe_ratio': self.calculate_sharpe_ratio(returns),
|
|
49
|
+
'max_drawdown': self.calculate_max_drawdown(returns),
|
|
50
|
+
'win_rate': (returns > 0).mean(),
|
|
51
|
+
'profit_factor': self._calculate_profit_factor(returns)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if benchmark_returns is not None:
|
|
55
|
+
analysis['alpha'] = self._calculate_alpha(returns, benchmark_returns)
|
|
56
|
+
analysis['beta'] = self._calculate_beta(returns, benchmark_returns)
|
|
57
|
+
|
|
58
|
+
return analysis
|
|
59
|
+
|
|
60
|
+
def _calculate_profit_factor(self, returns: pd.Series) -> float:
|
|
61
|
+
"""Calculate profit factor."""
|
|
62
|
+
positive_returns = returns[returns > 0].sum()
|
|
63
|
+
negative_returns = abs(returns[returns < 0].sum())
|
|
64
|
+
|
|
65
|
+
if negative_returns == 0:
|
|
66
|
+
return float('inf') if positive_returns > 0 else 0.0
|
|
67
|
+
|
|
68
|
+
return positive_returns / negative_returns
|
|
69
|
+
|
|
70
|
+
def _calculate_alpha(self, returns: pd.Series, benchmark_returns: pd.Series) -> float:
|
|
71
|
+
"""Calculate alpha."""
|
|
72
|
+
# Simplified calculation
|
|
73
|
+
return returns.mean() - benchmark_returns.mean()
|
|
74
|
+
|
|
75
|
+
def _calculate_beta(self, returns: pd.Series, benchmark_returns: pd.Series) -> float:
|
|
76
|
+
"""Calculate beta."""
|
|
77
|
+
if benchmark_returns.var() == 0:
|
|
78
|
+
return 0.0
|
|
79
|
+
return returns.cov(benchmark_returns) / benchmark_returns.var()
|
|
80
|
+
|
|
81
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backtest report generation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
7
|
+
from ..core.base import BaseComponent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BacktestReporter(BaseComponent):
|
|
11
|
+
"""Generate backtest reports."""
|
|
12
|
+
|
|
13
|
+
def initialize(self) -> bool:
|
|
14
|
+
"""Initialize backtest reporter."""
|
|
15
|
+
self.logger.info("Initializing backtest reporter")
|
|
16
|
+
return True
|
|
17
|
+
|
|
18
|
+
def generate_summary_report(self, results: Dict[str, Any]) -> str:
|
|
19
|
+
"""Generate a summary report."""
|
|
20
|
+
report = f"""
|
|
21
|
+
=== Backtest Summary Report ===
|
|
22
|
+
|
|
23
|
+
Initial Capital: ${results.get('initial_capital', 0):,.2f}
|
|
24
|
+
Final Value: ${results.get('final_value', 0):,.2f}
|
|
25
|
+
Total Return: {results.get('total_return', 0):.2%}
|
|
26
|
+
|
|
27
|
+
Trades:
|
|
28
|
+
- Total Trades: {len(results.get('trades', []))}
|
|
29
|
+
- Final Cash: ${results.get('final_cash', 0):,.2f}
|
|
30
|
+
- Final Positions: {results.get('final_positions', {})}
|
|
31
|
+
|
|
32
|
+
=== End of Report ===
|
|
33
|
+
"""
|
|
34
|
+
return report
|
|
35
|
+
|
|
36
|
+
def generate_detailed_report(self, results: Dict[str, Any], performance_metrics: Dict[str, float]) -> str:
|
|
37
|
+
"""Generate detailed report with performance metrics."""
|
|
38
|
+
report = f"""
|
|
39
|
+
=== Detailed Backtest Report ===
|
|
40
|
+
|
|
41
|
+
PORTFOLIO PERFORMANCE
|
|
42
|
+
====================
|
|
43
|
+
Initial Capital: ${results.get('initial_capital', 0):,.2f}
|
|
44
|
+
Final Value: ${results.get('final_value', 0):,.2f}
|
|
45
|
+
Total Return: {results.get('total_return', 0):.2%}
|
|
46
|
+
|
|
47
|
+
PERFORMANCE METRICS
|
|
48
|
+
==================
|
|
49
|
+
Annualized Return: {performance_metrics.get('annualized_return', 0):.2%}
|
|
50
|
+
Volatility: {performance_metrics.get('volatility', 0):.2%}
|
|
51
|
+
Sharpe Ratio: {performance_metrics.get('sharpe_ratio', 0):.2f}
|
|
52
|
+
Maximum Drawdown: {performance_metrics.get('max_drawdown', 0):.2%}
|
|
53
|
+
Calmar Ratio: {performance_metrics.get('calmar_ratio', 0):.2f}
|
|
54
|
+
|
|
55
|
+
TRADING METRICS
|
|
56
|
+
===============
|
|
57
|
+
Total Trades: {len(results.get('trades', []))}
|
|
58
|
+
Final Cash: ${results.get('final_cash', 0):,.2f}
|
|
59
|
+
Final Positions: {results.get('final_positions', {})}
|
|
60
|
+
|
|
61
|
+
=== End of Detailed Report ===
|
|
62
|
+
"""
|
|
63
|
+
return report
|
|
64
|
+
|
|
65
|
+
def save_report(self, report: str, filename: str) -> bool:
|
|
66
|
+
"""Save report to file."""
|
|
67
|
+
try:
|
|
68
|
+
with open(filename, 'w') as f:
|
|
69
|
+
f.write(report)
|
|
70
|
+
self.logger.info(f"Report saved to: {filename}")
|
|
71
|
+
return True
|
|
72
|
+
except Exception as e:
|
|
73
|
+
self.logger.error(f"Failed to save report: {str(e)}")
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
def export_trades_to_csv(self, trades: list, filename: str) -> bool:
|
|
77
|
+
"""Export trades to CSV file."""
|
|
78
|
+
try:
|
|
79
|
+
if trades:
|
|
80
|
+
trade_df = pd.DataFrame(trades)
|
|
81
|
+
trade_df.to_csv(filename, index=False)
|
|
82
|
+
self.logger.info(f"Trades exported to: {filename}")
|
|
83
|
+
return True
|
|
84
|
+
else:
|
|
85
|
+
self.logger.warning("No trades to export")
|
|
86
|
+
return False
|
|
87
|
+
except Exception as e:
|
|
88
|
+
self.logger.error(f"Failed to export trades: {str(e)}")
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
|
deltafq/core/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core functionality for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .config import Config
|
|
6
|
+
from .logger import Logger
|
|
7
|
+
from .exceptions import DeltaFQError, DataError, TradingError
|
|
8
|
+
from .base import BaseComponent
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Config",
|
|
12
|
+
"Logger",
|
|
13
|
+
"DeltaFQError",
|
|
14
|
+
"DataError",
|
|
15
|
+
"TradingError",
|
|
16
|
+
"BaseComponent"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
deltafq/core/base.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes for DeltaFQ components.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
from .logger import Logger
|
|
8
|
+
from .config import Config
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseComponent(ABC):
|
|
12
|
+
"""Base class for all DeltaFQ components."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, name: str = None, config: Config = None):
|
|
15
|
+
"""Initialize base component."""
|
|
16
|
+
self.name = name or self.__class__.__name__
|
|
17
|
+
self.config = config or Config()
|
|
18
|
+
self.logger = Logger(self.name)
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def initialize(self) -> bool:
|
|
22
|
+
"""Initialize the component."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
def cleanup(self):
|
|
26
|
+
"""Cleanup resources."""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def get_info(self) -> Dict[str, Any]:
|
|
30
|
+
"""Get component information."""
|
|
31
|
+
return {
|
|
32
|
+
"name": self.name,
|
|
33
|
+
"class": self.__class__.__name__,
|
|
34
|
+
"config": self.config.config
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
deltafq/core/config.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Config:
|
|
11
|
+
"""Configuration manager for DeltaFQ."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, config_file: str = None):
|
|
14
|
+
"""Initialize configuration."""
|
|
15
|
+
self.config = self._load_default_config()
|
|
16
|
+
if config_file and os.path.exists(config_file):
|
|
17
|
+
self._load_config_file(config_file)
|
|
18
|
+
|
|
19
|
+
def _load_default_config(self) -> Dict[str, Any]:
|
|
20
|
+
"""Load default configuration."""
|
|
21
|
+
return {
|
|
22
|
+
"data": {
|
|
23
|
+
"cache_dir": "./data_cache",
|
|
24
|
+
"default_source": "yahoo"
|
|
25
|
+
},
|
|
26
|
+
"trading": {
|
|
27
|
+
"initial_capital": 100000,
|
|
28
|
+
"commission": 0.001,
|
|
29
|
+
"slippage": 0.0005
|
|
30
|
+
},
|
|
31
|
+
"logging": {
|
|
32
|
+
"level": "INFO",
|
|
33
|
+
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def _load_config_file(self, config_file: str):
|
|
38
|
+
"""Load configuration from file."""
|
|
39
|
+
# Placeholder for config file loading
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
43
|
+
"""Get configuration value."""
|
|
44
|
+
keys = key.split('.')
|
|
45
|
+
value = self.config
|
|
46
|
+
for k in keys:
|
|
47
|
+
if isinstance(value, dict) and k in value:
|
|
48
|
+
value = value[k]
|
|
49
|
+
else:
|
|
50
|
+
return default
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
def set(self, key: str, value: Any):
|
|
54
|
+
"""Set configuration value."""
|
|
55
|
+
keys = key.split('.')
|
|
56
|
+
config = self.config
|
|
57
|
+
for k in keys[:-1]:
|
|
58
|
+
if k not in config:
|
|
59
|
+
config[k] = {}
|
|
60
|
+
config = config[k]
|
|
61
|
+
config[keys[-1]] = value
|
|
62
|
+
|
|
63
|
+
|