deltafq 0.1.0__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.0.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.0.dist-info/METADATA +0 -195
- deltafq-0.1.0.dist-info/RECORD +0 -29
- {deltafq-0.1.0.dist-info → deltafq-0.1.2.dist-info}/WHEEL +0 -0
- {deltafq-0.1.0.dist-info → deltafq-0.1.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trading monitoring and alerts for live trading.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from typing import Dict, List, Any, Optional, Callable
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from ..core.base import BaseComponent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TradingMonitor(BaseComponent):
|
|
12
|
+
"""Monitor trading activities and generate alerts."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, **kwargs):
|
|
15
|
+
"""Initialize trading monitor."""
|
|
16
|
+
super().__init__(**kwargs)
|
|
17
|
+
self.alerts = []
|
|
18
|
+
self.monitoring_rules = {}
|
|
19
|
+
self.alert_callbacks = []
|
|
20
|
+
self.monitoring_active = False
|
|
21
|
+
|
|
22
|
+
def initialize(self) -> bool:
|
|
23
|
+
"""Initialize trading monitor."""
|
|
24
|
+
self.logger.info("Initializing trading monitor")
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
def add_monitoring_rule(self, rule_name: str, rule_func: Callable,
|
|
28
|
+
threshold: float = None, **kwargs) -> bool:
|
|
29
|
+
"""Add a monitoring rule."""
|
|
30
|
+
try:
|
|
31
|
+
self.monitoring_rules[rule_name] = {
|
|
32
|
+
'function': rule_func,
|
|
33
|
+
'threshold': threshold,
|
|
34
|
+
'parameters': kwargs,
|
|
35
|
+
'active': True,
|
|
36
|
+
'last_triggered': None
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
self.logger.info(f"Added monitoring rule: {rule_name}")
|
|
40
|
+
return True
|
|
41
|
+
|
|
42
|
+
except Exception as e:
|
|
43
|
+
self.logger.error(f"Failed to add monitoring rule: {str(e)}")
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
def add_alert_callback(self, callback: Callable) -> bool:
|
|
47
|
+
"""Add alert callback function."""
|
|
48
|
+
self.alert_callbacks.append(callback)
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
def start_monitoring(self) -> bool:
|
|
52
|
+
"""Start monitoring."""
|
|
53
|
+
try:
|
|
54
|
+
self.monitoring_active = True
|
|
55
|
+
self.logger.info("Trading monitoring started")
|
|
56
|
+
|
|
57
|
+
# In a real implementation, this would start a monitoring loop
|
|
58
|
+
# For now, we'll provide the framework
|
|
59
|
+
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
self.monitoring_active = False
|
|
64
|
+
self.logger.error(f"Failed to start monitoring: {str(e)}")
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
def stop_monitoring(self) -> bool:
|
|
68
|
+
"""Stop monitoring."""
|
|
69
|
+
try:
|
|
70
|
+
self.monitoring_active = False
|
|
71
|
+
self.logger.info("Trading monitoring stopped")
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
self.logger.error(f"Failed to stop monitoring: {str(e)}")
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
def check_rule(self, rule_name: str, data: Dict[str, Any]) -> bool:
|
|
79
|
+
"""Check a specific monitoring rule."""
|
|
80
|
+
if rule_name not in self.monitoring_rules:
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
rule = self.monitoring_rules[rule_name]
|
|
84
|
+
if not rule['active']:
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
# Execute the rule function
|
|
89
|
+
result = rule['function'](data, rule['threshold'], **rule['parameters'])
|
|
90
|
+
|
|
91
|
+
# Check if rule was triggered
|
|
92
|
+
if result:
|
|
93
|
+
self._trigger_alert(rule_name, data, result)
|
|
94
|
+
rule['last_triggered'] = datetime.now()
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
except Exception as e:
|
|
100
|
+
self.logger.error(f"Rule check failed for {rule_name}: {str(e)}")
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
def check_all_rules(self, data: Dict[str, Any]) -> Dict[str, bool]:
|
|
104
|
+
"""Check all active monitoring rules."""
|
|
105
|
+
results = {}
|
|
106
|
+
|
|
107
|
+
for rule_name in self.monitoring_rules:
|
|
108
|
+
if self.monitoring_rules[rule_name]['active']:
|
|
109
|
+
results[rule_name] = self.check_rule(rule_name, data)
|
|
110
|
+
|
|
111
|
+
return results
|
|
112
|
+
|
|
113
|
+
def _trigger_alert(self, rule_name: str, data: Dict[str, Any], result: Any):
|
|
114
|
+
"""Trigger an alert."""
|
|
115
|
+
alert = {
|
|
116
|
+
'rule_name': rule_name,
|
|
117
|
+
'timestamp': datetime.now(),
|
|
118
|
+
'data': data,
|
|
119
|
+
'result': result,
|
|
120
|
+
'severity': self._determine_severity(rule_name, result)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
self.alerts.append(alert)
|
|
124
|
+
self.logger.warning(f"Alert triggered: {rule_name}")
|
|
125
|
+
|
|
126
|
+
# Notify alert callbacks
|
|
127
|
+
for callback in self.alert_callbacks:
|
|
128
|
+
try:
|
|
129
|
+
callback(alert)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
self.logger.error(f"Alert callback failed: {str(e)}")
|
|
132
|
+
|
|
133
|
+
def _determine_severity(self, rule_name: str, result: Any) -> str:
|
|
134
|
+
"""Determine alert severity."""
|
|
135
|
+
# Simple severity determination based on rule name
|
|
136
|
+
if 'loss' in rule_name.lower() or 'drawdown' in rule_name.lower():
|
|
137
|
+
return 'HIGH'
|
|
138
|
+
elif 'position' in rule_name.lower() or 'concentration' in rule_name.lower():
|
|
139
|
+
return 'MEDIUM'
|
|
140
|
+
else:
|
|
141
|
+
return 'LOW'
|
|
142
|
+
|
|
143
|
+
def get_alerts(self, hours: int = 24) -> List[Dict[str, Any]]:
|
|
144
|
+
"""Get alerts from the last N hours."""
|
|
145
|
+
cutoff_time = datetime.now() - timedelta(hours=hours)
|
|
146
|
+
return [alert for alert in self.alerts if alert['timestamp'] > cutoff_time]
|
|
147
|
+
|
|
148
|
+
def clear_alerts(self, hours: int = None) -> int:
|
|
149
|
+
"""Clear old alerts."""
|
|
150
|
+
if hours is None:
|
|
151
|
+
# Clear all alerts
|
|
152
|
+
count = len(self.alerts)
|
|
153
|
+
self.alerts.clear()
|
|
154
|
+
else:
|
|
155
|
+
# Clear alerts older than specified hours
|
|
156
|
+
cutoff_time = datetime.now() - timedelta(hours=hours)
|
|
157
|
+
original_count = len(self.alerts)
|
|
158
|
+
self.alerts = [alert for alert in self.alerts if alert['timestamp'] > cutoff_time]
|
|
159
|
+
count = original_count - len(self.alerts)
|
|
160
|
+
|
|
161
|
+
self.logger.info(f"Cleared {count} alerts")
|
|
162
|
+
return count
|
|
163
|
+
|
|
164
|
+
def enable_rule(self, rule_name: str) -> bool:
|
|
165
|
+
"""Enable a monitoring rule."""
|
|
166
|
+
if rule_name in self.monitoring_rules:
|
|
167
|
+
self.monitoring_rules[rule_name]['active'] = True
|
|
168
|
+
return True
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
def disable_rule(self, rule_name: str) -> bool:
|
|
172
|
+
"""Disable a monitoring rule."""
|
|
173
|
+
if rule_name in self.monitoring_rules:
|
|
174
|
+
self.monitoring_rules[rule_name]['active'] = False
|
|
175
|
+
return True
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
def get_monitoring_status(self) -> Dict[str, Any]:
|
|
179
|
+
"""Get monitoring status."""
|
|
180
|
+
return {
|
|
181
|
+
'monitoring_active': self.monitoring_active,
|
|
182
|
+
'total_rules': len(self.monitoring_rules),
|
|
183
|
+
'active_rules': sum(1 for rule in self.monitoring_rules.values() if rule['active']),
|
|
184
|
+
'total_alerts': len(self.alerts),
|
|
185
|
+
'recent_alerts': len(self.get_alerts(hours=1)),
|
|
186
|
+
'rules': {name: {
|
|
187
|
+
'active': rule['active'],
|
|
188
|
+
'last_triggered': rule['last_triggered']
|
|
189
|
+
} for name, rule in self.monitoring_rules.items()}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Real-time risk control for live trading.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Any, Optional, List
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from ..core.base import BaseComponent
|
|
8
|
+
from ..core.exceptions import TradingError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LiveRiskControl(BaseComponent):
|
|
12
|
+
"""Real-time risk control system."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, max_position_size: float = 0.1, max_daily_loss: float = 0.05,
|
|
15
|
+
max_drawdown: float = 0.15, **kwargs):
|
|
16
|
+
"""Initialize risk control."""
|
|
17
|
+
super().__init__(**kwargs)
|
|
18
|
+
self.max_position_size = max_position_size
|
|
19
|
+
self.max_daily_loss = max_daily_loss
|
|
20
|
+
self.max_drawdown = max_drawdown
|
|
21
|
+
self.daily_pnl = 0.0
|
|
22
|
+
self.peak_equity = 0.0
|
|
23
|
+
self.risk_limits = {}
|
|
24
|
+
self.alert_callbacks = []
|
|
25
|
+
|
|
26
|
+
def initialize(self) -> bool:
|
|
27
|
+
"""Initialize risk control."""
|
|
28
|
+
self.logger.info("Initializing live risk control")
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
def add_alert_callback(self, callback) -> bool:
|
|
32
|
+
"""Add alert callback function."""
|
|
33
|
+
self.alert_callbacks.append(callback)
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
def check_position_risk(self, symbol: str, quantity: float, portfolio_value: float,
|
|
37
|
+
current_price: float) -> bool:
|
|
38
|
+
"""Check if position size is within risk limits."""
|
|
39
|
+
try:
|
|
40
|
+
position_value = abs(quantity) * current_price
|
|
41
|
+
position_ratio = position_value / portfolio_value
|
|
42
|
+
|
|
43
|
+
# Check maximum position size
|
|
44
|
+
if position_ratio > self.max_position_size:
|
|
45
|
+
self._trigger_alert("POSITION_SIZE_EXCEEDED", {
|
|
46
|
+
'symbol': symbol,
|
|
47
|
+
'position_ratio': position_ratio,
|
|
48
|
+
'max_allowed': self.max_position_size
|
|
49
|
+
})
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
# Check symbol-specific limits
|
|
53
|
+
if symbol in self.risk_limits:
|
|
54
|
+
symbol_limit = self.risk_limits[symbol]
|
|
55
|
+
if position_ratio > symbol_limit:
|
|
56
|
+
self._trigger_alert("SYMBOL_LIMIT_EXCEEDED", {
|
|
57
|
+
'symbol': symbol,
|
|
58
|
+
'position_ratio': position_ratio,
|
|
59
|
+
'symbol_limit': symbol_limit
|
|
60
|
+
})
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
self.logger.error(f"Risk check failed: {str(e)}")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
def check_daily_loss_limit(self, current_equity: float, initial_equity: float) -> bool:
|
|
70
|
+
"""Check if daily loss limit is exceeded."""
|
|
71
|
+
try:
|
|
72
|
+
daily_pnl = current_equity - initial_equity
|
|
73
|
+
daily_pnl_ratio = daily_pnl / initial_equity
|
|
74
|
+
|
|
75
|
+
if daily_pnl_ratio < -self.max_daily_loss:
|
|
76
|
+
self._trigger_alert("DAILY_LOSS_LIMIT_EXCEEDED", {
|
|
77
|
+
'daily_pnl': daily_pnl,
|
|
78
|
+
'daily_pnl_ratio': daily_pnl_ratio,
|
|
79
|
+
'max_daily_loss': self.max_daily_loss
|
|
80
|
+
})
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
except Exception as e:
|
|
86
|
+
self.logger.error(f"Daily loss check failed: {str(e)}")
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
def check_drawdown_limit(self, current_equity: float) -> bool:
|
|
90
|
+
"""Check if maximum drawdown is exceeded."""
|
|
91
|
+
try:
|
|
92
|
+
if current_equity > self.peak_equity:
|
|
93
|
+
self.peak_equity = current_equity
|
|
94
|
+
|
|
95
|
+
if self.peak_equity > 0:
|
|
96
|
+
drawdown = (self.peak_equity - current_equity) / self.peak_equity
|
|
97
|
+
|
|
98
|
+
if drawdown > self.max_drawdown:
|
|
99
|
+
self._trigger_alert("DRAWDOWN_LIMIT_EXCEEDED", {
|
|
100
|
+
'current_drawdown': drawdown,
|
|
101
|
+
'max_drawdown': self.max_drawdown,
|
|
102
|
+
'peak_equity': self.peak_equity,
|
|
103
|
+
'current_equity': current_equity
|
|
104
|
+
})
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
self.logger.error(f"Drawdown check failed: {str(e)}")
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
def check_concentration_risk(self, positions: Dict[str, float], portfolio_value: float) -> bool:
|
|
114
|
+
"""Check portfolio concentration risk."""
|
|
115
|
+
try:
|
|
116
|
+
total_position_value = sum(abs(value) for value in positions.values())
|
|
117
|
+
|
|
118
|
+
if total_position_value > portfolio_value * 0.95: # 95% of portfolio
|
|
119
|
+
self._trigger_alert("CONCENTRATION_RISK_HIGH", {
|
|
120
|
+
'total_position_value': total_position_value,
|
|
121
|
+
'portfolio_value': portfolio_value,
|
|
122
|
+
'concentration_ratio': total_position_value / portfolio_value
|
|
123
|
+
})
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
self.logger.error(f"Concentration risk check failed: {str(e)}")
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
def set_symbol_limit(self, symbol: str, limit: float) -> bool:
|
|
133
|
+
"""Set position size limit for specific symbol."""
|
|
134
|
+
try:
|
|
135
|
+
self.risk_limits[symbol] = limit
|
|
136
|
+
self.logger.info(f"Set risk limit for {symbol}: {limit:.2%}")
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
except Exception as e:
|
|
140
|
+
self.logger.error(f"Failed to set symbol limit: {str(e)}")
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
def comprehensive_risk_check(self, symbol: str, quantity: float, portfolio_value: float,
|
|
144
|
+
current_price: float, current_equity: float,
|
|
145
|
+
initial_equity: float, positions: Dict[str, float]) -> Dict[str, bool]:
|
|
146
|
+
"""Perform comprehensive risk checks."""
|
|
147
|
+
results = {
|
|
148
|
+
'position_risk': self.check_position_risk(symbol, quantity, portfolio_value, current_price),
|
|
149
|
+
'daily_loss': self.check_daily_loss_limit(current_equity, initial_equity),
|
|
150
|
+
'drawdown': self.check_drawdown_limit(current_equity),
|
|
151
|
+
'concentration': self.check_concentration_risk(positions, portfolio_value)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Overall risk check passes only if all individual checks pass
|
|
155
|
+
results['overall'] = all(results.values())
|
|
156
|
+
|
|
157
|
+
return results
|
|
158
|
+
|
|
159
|
+
def _trigger_alert(self, alert_type: str, details: Dict[str, Any]):
|
|
160
|
+
"""Trigger risk alert."""
|
|
161
|
+
alert = {
|
|
162
|
+
'type': alert_type,
|
|
163
|
+
'timestamp': datetime.now(),
|
|
164
|
+
'details': details
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
self.logger.warning(f"Risk alert: {alert_type}")
|
|
168
|
+
|
|
169
|
+
# Notify alert callbacks
|
|
170
|
+
for callback in self.alert_callbacks:
|
|
171
|
+
try:
|
|
172
|
+
callback(alert)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
self.logger.error(f"Alert callback failed: {str(e)}")
|
|
175
|
+
|
|
176
|
+
def reset_daily_limits(self):
|
|
177
|
+
"""Reset daily risk limits (call at start of trading day)."""
|
|
178
|
+
self.daily_pnl = 0.0
|
|
179
|
+
self.logger.info("Daily risk limits reset")
|
|
180
|
+
|
|
181
|
+
def get_risk_status(self) -> Dict[str, Any]:
|
|
182
|
+
"""Get current risk status."""
|
|
183
|
+
return {
|
|
184
|
+
'max_position_size': self.max_position_size,
|
|
185
|
+
'max_daily_loss': self.max_daily_loss,
|
|
186
|
+
'max_drawdown': self.max_drawdown,
|
|
187
|
+
'current_daily_pnl': self.daily_pnl,
|
|
188
|
+
'peak_equity': self.peak_equity,
|
|
189
|
+
'symbol_limits': dict(self.risk_limits),
|
|
190
|
+
'active_alerts': len(self.alert_callbacks)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
deltafq/strategy/__init__.py
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
"""
|
|
2
|
+
Strategy module for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .base_strategy import BaseStrategy
|
|
6
|
+
from .signal_generator import SignalGenerator
|
|
7
|
+
from .portfolio import Portfolio
|
|
8
|
+
from .risk_manager import RiskManager
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"BaseStrategy",
|
|
12
|
+
"SignalGenerator",
|
|
13
|
+
"Portfolio",
|
|
14
|
+
"RiskManager"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base strategy class for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import Dict, Any, Optional
|
|
8
|
+
from ..core.base import BaseComponent
|
|
9
|
+
from ..core.exceptions import StrategyError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseStrategy(BaseComponent):
|
|
13
|
+
"""Base class for all trading strategies."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, name: str = None, **kwargs):
|
|
16
|
+
"""Initialize base strategy."""
|
|
17
|
+
super().__init__(name=name, **kwargs)
|
|
18
|
+
self.signals = pd.DataFrame()
|
|
19
|
+
self.positions = pd.DataFrame()
|
|
20
|
+
|
|
21
|
+
def initialize(self) -> bool:
|
|
22
|
+
"""Initialize the strategy."""
|
|
23
|
+
self.logger.info(f"Initializing strategy: {self.name}")
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def generate_signals(self, data: pd.DataFrame) -> pd.DataFrame:
|
|
28
|
+
"""Generate trading signals from market data."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def run(self, data: pd.DataFrame) -> Dict[str, Any]:
|
|
32
|
+
"""Run the strategy on given data."""
|
|
33
|
+
try:
|
|
34
|
+
self.logger.info(f"Running strategy: {self.name}")
|
|
35
|
+
signals = self.generate_signals(data)
|
|
36
|
+
return {
|
|
37
|
+
"strategy_name": self.name,
|
|
38
|
+
"signals": signals,
|
|
39
|
+
"performance": self._calculate_performance(signals, data)
|
|
40
|
+
}
|
|
41
|
+
except Exception as e:
|
|
42
|
+
raise StrategyError(f"Strategy execution failed: {str(e)}")
|
|
43
|
+
|
|
44
|
+
def _calculate_performance(self, signals: pd.DataFrame, data: pd.DataFrame) -> Dict[str, float]:
|
|
45
|
+
"""Calculate basic performance metrics."""
|
|
46
|
+
# Placeholder implementation
|
|
47
|
+
return {
|
|
48
|
+
"total_return": 0.0,
|
|
49
|
+
"sharpe_ratio": 0.0,
|
|
50
|
+
"max_drawdown": 0.0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Portfolio management for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from typing import Dict, List, Optional
|
|
7
|
+
from ..core.base import BaseComponent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Portfolio(BaseComponent):
|
|
11
|
+
"""Portfolio management system."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, initial_capital: float = 100000, **kwargs):
|
|
14
|
+
"""Initialize portfolio."""
|
|
15
|
+
super().__init__(**kwargs)
|
|
16
|
+
self.initial_capital = initial_capital
|
|
17
|
+
self.cash = initial_capital
|
|
18
|
+
self.positions = {}
|
|
19
|
+
self.trades = []
|
|
20
|
+
|
|
21
|
+
def initialize(self) -> bool:
|
|
22
|
+
"""Initialize portfolio."""
|
|
23
|
+
self.logger.info(f"Initializing portfolio with capital: {self.initial_capital}")
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
def get_position(self, symbol: str) -> float:
|
|
27
|
+
"""Get current position for symbol."""
|
|
28
|
+
return self.positions.get(symbol, 0.0)
|
|
29
|
+
|
|
30
|
+
def get_portfolio_value(self, prices: Dict[str, float]) -> float:
|
|
31
|
+
"""Calculate total portfolio value."""
|
|
32
|
+
total_value = self.cash
|
|
33
|
+
for symbol, quantity in self.positions.items():
|
|
34
|
+
if symbol in prices:
|
|
35
|
+
total_value += quantity * prices[symbol]
|
|
36
|
+
return total_value
|
|
37
|
+
|
|
38
|
+
def execute_trade(self, symbol: str, quantity: int, price: float, commission: float = 0.001):
|
|
39
|
+
"""Execute a trade."""
|
|
40
|
+
cost = quantity * price * (1 + commission)
|
|
41
|
+
|
|
42
|
+
if quantity > 0: # Buy
|
|
43
|
+
if cost <= self.cash:
|
|
44
|
+
self.cash -= cost
|
|
45
|
+
self.positions[symbol] = self.positions.get(symbol, 0) + quantity
|
|
46
|
+
self.trades.append({
|
|
47
|
+
'symbol': symbol,
|
|
48
|
+
'quantity': quantity,
|
|
49
|
+
'price': price,
|
|
50
|
+
'type': 'buy',
|
|
51
|
+
'timestamp': pd.Timestamp.now()
|
|
52
|
+
})
|
|
53
|
+
self.logger.info(f"Bought {quantity} shares of {symbol} at {price}")
|
|
54
|
+
else:
|
|
55
|
+
self.logger.warning(f"Insufficient cash for trade: {symbol}")
|
|
56
|
+
else: # Sell
|
|
57
|
+
quantity = abs(quantity)
|
|
58
|
+
if self.positions.get(symbol, 0) >= quantity:
|
|
59
|
+
self.cash += quantity * price * (1 - commission)
|
|
60
|
+
self.positions[symbol] -= quantity
|
|
61
|
+
self.trades.append({
|
|
62
|
+
'symbol': symbol,
|
|
63
|
+
'quantity': -quantity,
|
|
64
|
+
'price': price,
|
|
65
|
+
'type': 'sell',
|
|
66
|
+
'timestamp': pd.Timestamp.now()
|
|
67
|
+
})
|
|
68
|
+
self.logger.info(f"Sold {quantity} shares of {symbol} at {price}")
|
|
69
|
+
else:
|
|
70
|
+
self.logger.warning(f"Insufficient position for trade: {symbol}")
|
|
71
|
+
|
|
72
|
+
def get_portfolio_summary(self, prices: Dict[str, float]) -> Dict[str, float]:
|
|
73
|
+
"""Get portfolio summary."""
|
|
74
|
+
total_value = self.get_portfolio_value(prices)
|
|
75
|
+
return {
|
|
76
|
+
'total_value': total_value,
|
|
77
|
+
'cash': self.cash,
|
|
78
|
+
'total_return': (total_value - self.initial_capital) / self.initial_capital,
|
|
79
|
+
'positions': dict(self.positions)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Risk management for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Any, Optional
|
|
6
|
+
from ..core.base import BaseComponent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RiskManager(BaseComponent):
|
|
10
|
+
"""Risk management system."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, max_position_size: float = 0.1, max_drawdown: float = 0.2, **kwargs):
|
|
13
|
+
"""Initialize risk manager."""
|
|
14
|
+
super().__init__(**kwargs)
|
|
15
|
+
self.max_position_size = max_position_size
|
|
16
|
+
self.max_drawdown = max_drawdown
|
|
17
|
+
self.peak_value = 0.0
|
|
18
|
+
|
|
19
|
+
def initialize(self) -> bool:
|
|
20
|
+
"""Initialize risk manager."""
|
|
21
|
+
self.logger.info("Initializing risk manager")
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
def check_position_size(self, symbol: str, quantity: float, portfolio_value: float) -> bool:
|
|
25
|
+
"""Check if position size is within limits."""
|
|
26
|
+
position_value = abs(quantity) * self._get_current_price(symbol)
|
|
27
|
+
position_ratio = position_value / portfolio_value
|
|
28
|
+
|
|
29
|
+
if position_ratio > self.max_position_size:
|
|
30
|
+
self.logger.warning(f"Position size exceeds limit: {symbol}")
|
|
31
|
+
return False
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
def check_drawdown(self, current_value: float) -> bool:
|
|
35
|
+
"""Check if drawdown is within limits."""
|
|
36
|
+
if current_value > self.peak_value:
|
|
37
|
+
self.peak_value = current_value
|
|
38
|
+
|
|
39
|
+
drawdown = (self.peak_value - current_value) / self.peak_value
|
|
40
|
+
|
|
41
|
+
if drawdown > self.max_drawdown:
|
|
42
|
+
self.logger.warning(f"Drawdown exceeds limit: {drawdown:.2%}")
|
|
43
|
+
return False
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
def _get_current_price(self, symbol: str) -> float:
|
|
47
|
+
"""Get current price for symbol (placeholder)."""
|
|
48
|
+
# This would be replaced with actual price fetching
|
|
49
|
+
return 100.0
|
|
50
|
+
|
|
51
|
+
def get_risk_metrics(self, portfolio_value: float) -> Dict[str, float]:
|
|
52
|
+
"""Get current risk metrics."""
|
|
53
|
+
drawdown = 0.0
|
|
54
|
+
if self.peak_value > 0:
|
|
55
|
+
drawdown = (self.peak_value - portfolio_value) / self.peak_value
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
'current_drawdown': drawdown,
|
|
59
|
+
'max_drawdown_limit': self.max_drawdown,
|
|
60
|
+
'peak_value': self.peak_value,
|
|
61
|
+
'max_position_size': self.max_position_size
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Signal generator for trading strategies.
|
|
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 SignalGenerator(BaseComponent):
|
|
12
|
+
"""Generate trading signals from market data."""
|
|
13
|
+
|
|
14
|
+
def initialize(self) -> bool:
|
|
15
|
+
"""Initialize signal generator."""
|
|
16
|
+
self.logger.info("Initializing signal generator")
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
def moving_average_crossover(self, data: pd.DataFrame, fast_period: int = 10, slow_period: int = 20) -> pd.Series:
|
|
20
|
+
"""Generate signals based on moving average crossover."""
|
|
21
|
+
if 'close' not in data.columns:
|
|
22
|
+
raise ValueError("Data must contain 'close' column")
|
|
23
|
+
|
|
24
|
+
# Calculate moving averages
|
|
25
|
+
fast_ma = data['close'].rolling(window=fast_period).mean()
|
|
26
|
+
slow_ma = data['close'].rolling(window=slow_period).mean()
|
|
27
|
+
|
|
28
|
+
# Generate signals: 1 for buy, -1 for sell, 0 for hold
|
|
29
|
+
signals = np.where(fast_ma > slow_ma, 1, np.where(fast_ma < slow_ma, -1, 0))
|
|
30
|
+
|
|
31
|
+
self.logger.info(f"Generated MA crossover signals: {fast_period}/{slow_period}")
|
|
32
|
+
return pd.Series(signals, index=data.index)
|
|
33
|
+
|
|
34
|
+
def rsi_signals(self, data: pd.DataFrame, period: int = 14, oversold: float = 30, overbought: float = 70) -> pd.Series:
|
|
35
|
+
"""Generate signals based on RSI."""
|
|
36
|
+
if 'close' not in data.columns:
|
|
37
|
+
raise ValueError("Data must contain 'close' column")
|
|
38
|
+
|
|
39
|
+
# Calculate RSI (simplified)
|
|
40
|
+
delta = data['close'].diff()
|
|
41
|
+
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
|
|
42
|
+
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
|
|
43
|
+
rs = gain / loss
|
|
44
|
+
rsi = 100 - (100 / (1 + rs))
|
|
45
|
+
|
|
46
|
+
# Generate signals
|
|
47
|
+
signals = np.where(rsi < oversold, 1, np.where(rsi > overbought, -1, 0))
|
|
48
|
+
|
|
49
|
+
self.logger.info(f"Generated RSI signals: period={period}")
|
|
50
|
+
return pd.Series(signals, index=data.index)
|
|
51
|
+
|
|
52
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trading module for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .simulator import PaperTradingSimulator
|
|
6
|
+
from .broker import Broker
|
|
7
|
+
from .order_manager import OrderManager
|
|
8
|
+
from .position_manager import PositionManager
|
|
9
|
+
from .execution import ExecutionEngine
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"PaperTradingSimulator",
|
|
13
|
+
"Broker",
|
|
14
|
+
"OrderManager",
|
|
15
|
+
"PositionManager",
|
|
16
|
+
"ExecutionEngine"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|