deltafq 0.4.0__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.
- deltafq/__init__.py +29 -0
- deltafq/backtest/__init__.py +32 -0
- deltafq/backtest/engine.py +145 -0
- deltafq/backtest/metrics.py +74 -0
- deltafq/backtest/performance.py +350 -0
- deltafq/charts/__init__.py +14 -0
- deltafq/charts/performance.py +319 -0
- deltafq/charts/price.py +64 -0
- deltafq/charts/signals.py +181 -0
- deltafq/core/__init__.py +18 -0
- deltafq/core/base.py +21 -0
- deltafq/core/config.py +62 -0
- deltafq/core/exceptions.py +34 -0
- deltafq/core/logger.py +44 -0
- deltafq/data/__init__.py +16 -0
- deltafq/data/cleaner.py +39 -0
- deltafq/data/fetcher.py +58 -0
- deltafq/data/storage.py +264 -0
- deltafq/data/validator.py +51 -0
- deltafq/indicators/__init__.py +14 -0
- deltafq/indicators/fundamental.py +28 -0
- deltafq/indicators/talib_indicators.py +67 -0
- deltafq/indicators/technical.py +251 -0
- deltafq/live/__init__.py +16 -0
- deltafq/live/connection.py +235 -0
- deltafq/live/data_feed.py +158 -0
- deltafq/live/monitoring.py +191 -0
- deltafq/live/risk_control.py +192 -0
- deltafq/strategy/__init__.py +12 -0
- deltafq/strategy/base.py +34 -0
- deltafq/strategy/signals.py +193 -0
- deltafq/trader/__init__.py +16 -0
- deltafq/trader/broker.py +119 -0
- deltafq/trader/engine.py +174 -0
- deltafq/trader/order_manager.py +110 -0
- deltafq/trader/position_manager.py +92 -0
- deltafq-0.4.0.dist-info/METADATA +115 -0
- deltafq-0.4.0.dist-info/RECORD +42 -0
- deltafq-0.4.0.dist-info/WHEEL +5 -0
- deltafq-0.4.0.dist-info/entry_points.txt +2 -0
- deltafq-0.4.0.dist-info/licenses/LICENSE +21 -0
- deltafq-0.4.0.dist-info/top_level.txt +1 -0
deltafq/trader/broker.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Broker interface for live trading.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import Dict, List, Optional, Any
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from ..core.base import BaseComponent
|
|
10
|
+
from ..core.exceptions import TradingError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Broker(BaseComponent, ABC):
|
|
14
|
+
"""Abstract broker interface for live trading."""
|
|
15
|
+
|
|
16
|
+
def initialize(self) -> bool:
|
|
17
|
+
"""Initialize broker connection."""
|
|
18
|
+
self.logger.info("Initializing broker connection")
|
|
19
|
+
return self._connect()
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def _connect(self) -> bool:
|
|
23
|
+
"""Connect to broker."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def place_order(self, symbol: str, quantity: int, order_type: str,
|
|
28
|
+
price: Optional[float] = None) -> str:
|
|
29
|
+
"""Place an order with the broker."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def cancel_order(self, order_id: str) -> bool:
|
|
34
|
+
"""Cancel an order."""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def get_order_status(self, order_id: str) -> Dict[str, Any]:
|
|
39
|
+
"""Get order status."""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def get_account_info(self) -> Dict[str, Any]:
|
|
44
|
+
"""Get account information."""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def get_positions(self) -> Dict[str, Dict[str, Any]]:
|
|
49
|
+
"""Get current positions."""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def get_current_price(self, symbol: str) -> float:
|
|
54
|
+
"""Get current price for symbol."""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class MockBroker(Broker):
|
|
59
|
+
"""Mock broker for testing purposes."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, **kwargs):
|
|
62
|
+
"""Initialize mock broker."""
|
|
63
|
+
super().__init__(**kwargs)
|
|
64
|
+
self.orders = {}
|
|
65
|
+
self.positions = {}
|
|
66
|
+
self.account_balance = 100000
|
|
67
|
+
self.order_counter = 0
|
|
68
|
+
|
|
69
|
+
def _connect(self) -> bool:
|
|
70
|
+
"""Mock connection."""
|
|
71
|
+
self.logger.info("Connected to mock broker")
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
def place_order(self, symbol: str, quantity: int, order_type: str,
|
|
75
|
+
price: Optional[float] = None) -> str:
|
|
76
|
+
"""Place a mock order."""
|
|
77
|
+
self.order_counter += 1
|
|
78
|
+
order_id = f"MOCK_{self.order_counter}"
|
|
79
|
+
|
|
80
|
+
self.orders[order_id] = {
|
|
81
|
+
'symbol': symbol,
|
|
82
|
+
'quantity': quantity,
|
|
83
|
+
'order_type': order_type,
|
|
84
|
+
'price': price,
|
|
85
|
+
'status': 'pending',
|
|
86
|
+
'timestamp': datetime.now()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
self.logger.info(f"Mock order placed: {order_id}")
|
|
90
|
+
return order_id
|
|
91
|
+
|
|
92
|
+
def cancel_order(self, order_id: str) -> bool:
|
|
93
|
+
"""Cancel a mock order."""
|
|
94
|
+
if order_id in self.orders:
|
|
95
|
+
self.orders[order_id]['status'] = 'cancelled'
|
|
96
|
+
self.logger.info(f"Mock order cancelled: {order_id}")
|
|
97
|
+
return True
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
def get_order_status(self, order_id: str) -> Dict[str, Any]:
|
|
101
|
+
"""Get mock order status."""
|
|
102
|
+
return self.orders.get(order_id, {})
|
|
103
|
+
|
|
104
|
+
def get_account_info(self) -> Dict[str, Any]:
|
|
105
|
+
"""Get mock account info."""
|
|
106
|
+
return {
|
|
107
|
+
'balance': self.account_balance,
|
|
108
|
+
'buying_power': self.account_balance,
|
|
109
|
+
'equity': self.account_balance
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
def get_positions(self) -> Dict[str, Dict[str, Any]]:
|
|
113
|
+
"""Get mock positions."""
|
|
114
|
+
return self.positions
|
|
115
|
+
|
|
116
|
+
def get_current_price(self, symbol: str) -> float:
|
|
117
|
+
"""Get mock current price."""
|
|
118
|
+
# Return a mock price
|
|
119
|
+
return 100.0
|
deltafq/trader/engine.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trade execution engine for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from typing import Dict, List, Optional, Any
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from ..core.base import BaseComponent
|
|
9
|
+
from ..core.exceptions import TradingError
|
|
10
|
+
from .order_manager import OrderManager
|
|
11
|
+
from .position_manager import PositionManager
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ExecutionEngine(BaseComponent):
|
|
15
|
+
"""
|
|
16
|
+
Trade execution engine for real-time trading.
|
|
17
|
+
Supports paper trading (broker=None) and live trading (broker=Broker).
|
|
18
|
+
Paper trading manages cash internally. Live trading uses broker for account info.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, broker=None, initial_capital: Optional[float] = None,
|
|
22
|
+
commission: float = 0.001, **kwargs):
|
|
23
|
+
"""
|
|
24
|
+
Initialize execution engine.
|
|
25
|
+
Args:
|
|
26
|
+
broker: Broker for live trading. None for paper trading.
|
|
27
|
+
initial_capital: Initial capital for paper trading. Defaults to 1000000.
|
|
28
|
+
commission: Commission rate for paper trading. Defaults to 0.001.
|
|
29
|
+
"""
|
|
30
|
+
super().__init__(**kwargs)
|
|
31
|
+
self.broker = broker
|
|
32
|
+
self.order_manager = OrderManager()
|
|
33
|
+
self.position_manager = PositionManager()
|
|
34
|
+
|
|
35
|
+
# Paper trading mode: manage cash internally
|
|
36
|
+
if broker is None:
|
|
37
|
+
self.initial_capital = initial_capital if initial_capital is not None else 1000000
|
|
38
|
+
self.cash = self.initial_capital
|
|
39
|
+
self.commission = commission
|
|
40
|
+
self.trades: List[Dict[str, Any]] = []
|
|
41
|
+
self.is_paper_trading = True
|
|
42
|
+
else:
|
|
43
|
+
# Live trading mode: get account info from broker
|
|
44
|
+
self.cash = None
|
|
45
|
+
self.commission = None
|
|
46
|
+
self.trades = []
|
|
47
|
+
self.is_paper_trading = False
|
|
48
|
+
|
|
49
|
+
def initialize(self) -> bool:
|
|
50
|
+
"""Initialize execution engine."""
|
|
51
|
+
if self.is_paper_trading:
|
|
52
|
+
self.logger.info(f"Initializing paper trading execution engine with capital: {self.initial_capital}")
|
|
53
|
+
else:
|
|
54
|
+
self.logger.info("Initializing live trading execution engine")
|
|
55
|
+
|
|
56
|
+
if self.broker:
|
|
57
|
+
return self.broker.initialize()
|
|
58
|
+
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
def execute_order(self, symbol: str, quantity: int, order_type: str = "limit",
|
|
62
|
+
price: Optional[float] = None, timestamp: Optional[datetime] = None) -> str:
|
|
63
|
+
"""Execute an order. Default is limit order (price required)."""
|
|
64
|
+
try:
|
|
65
|
+
# Validate price for limit orders
|
|
66
|
+
if order_type == "limit" and price is None:
|
|
67
|
+
raise TradingError("Price is required for limit orders")
|
|
68
|
+
|
|
69
|
+
# Create order
|
|
70
|
+
order_id = self.order_manager.create_order(
|
|
71
|
+
symbol=symbol,
|
|
72
|
+
quantity=quantity,
|
|
73
|
+
order_type=order_type,
|
|
74
|
+
price=price
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Execute through broker
|
|
78
|
+
if self.broker:
|
|
79
|
+
broker_order_id = self.broker.place_order(
|
|
80
|
+
symbol=symbol,
|
|
81
|
+
quantity=quantity,
|
|
82
|
+
order_type=order_type,
|
|
83
|
+
price=price
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Update order with broker ID
|
|
87
|
+
order = self.order_manager.get_order(order_id)
|
|
88
|
+
if order:
|
|
89
|
+
order['broker_order_id'] = broker_order_id
|
|
90
|
+
|
|
91
|
+
self.logger.info(f"Order executed through broker: {order_id} -> {broker_order_id}")
|
|
92
|
+
else:
|
|
93
|
+
self._execute_paper_trade(order_id, price, timestamp)
|
|
94
|
+
self.logger.info(f"Order executed in paper trading: {order_id}, timestamp: {timestamp}")
|
|
95
|
+
|
|
96
|
+
return order_id
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
raise TradingError(f"Failed to execute order: {str(e)}")
|
|
100
|
+
|
|
101
|
+
def _execute_paper_trade(self, order_id: str, execution_price: float, timestamp: Optional[datetime] = None):
|
|
102
|
+
"""Execute paper trading with cash management."""
|
|
103
|
+
order = self.order_manager.get_order(order_id)
|
|
104
|
+
if not order:
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
symbol = order['symbol']
|
|
108
|
+
quantity = order['quantity']
|
|
109
|
+
timestamp = timestamp or datetime.now()
|
|
110
|
+
|
|
111
|
+
if quantity > 0: # Buy
|
|
112
|
+
gross_cost = quantity * execution_price
|
|
113
|
+
commission_amount = gross_cost * self.commission
|
|
114
|
+
total_cost = gross_cost + commission_amount
|
|
115
|
+
|
|
116
|
+
if total_cost <= self.cash:
|
|
117
|
+
self.cash -= total_cost
|
|
118
|
+
self.position_manager.add_position(symbol, quantity, execution_price)
|
|
119
|
+
self.order_manager.mark_executed(order_id, execution_price)
|
|
120
|
+
|
|
121
|
+
# Record trade (unified record with full details)
|
|
122
|
+
self.trades.append({
|
|
123
|
+
'order_id': order_id,
|
|
124
|
+
'symbol': symbol,
|
|
125
|
+
'quantity': quantity,
|
|
126
|
+
'price': execution_price,
|
|
127
|
+
'type': 'buy',
|
|
128
|
+
'timestamp': timestamp,
|
|
129
|
+
'commission': commission_amount,
|
|
130
|
+
'cost': total_cost
|
|
131
|
+
})
|
|
132
|
+
else:
|
|
133
|
+
self.logger.warning(f"Insufficient cash for buy: need {total_cost:.2f}, have {self.cash:.2f}")
|
|
134
|
+
self.order_manager.cancel_order(order_id)
|
|
135
|
+
else: # Sell
|
|
136
|
+
quantity = abs(quantity)
|
|
137
|
+
if self.position_manager.can_sell(symbol, quantity):
|
|
138
|
+
gross_revenue = quantity * execution_price
|
|
139
|
+
commission_amount = gross_revenue * self.commission
|
|
140
|
+
net_revenue = gross_revenue - commission_amount
|
|
141
|
+
|
|
142
|
+
# Calculate profit/loss
|
|
143
|
+
buy_cost = self._get_latest_buy_cost(symbol)
|
|
144
|
+
profit_loss = net_revenue - buy_cost if buy_cost else net_revenue
|
|
145
|
+
|
|
146
|
+
self.position_manager.reduce_position(symbol, quantity, execution_price)
|
|
147
|
+
self.cash += net_revenue
|
|
148
|
+
self.order_manager.mark_executed(order_id, execution_price)
|
|
149
|
+
|
|
150
|
+
# Record trade (unified record with full details)
|
|
151
|
+
self.trades.append({
|
|
152
|
+
'order_id': order_id,
|
|
153
|
+
'symbol': symbol,
|
|
154
|
+
'quantity': quantity,
|
|
155
|
+
'price': execution_price,
|
|
156
|
+
'type': 'sell',
|
|
157
|
+
'timestamp': timestamp,
|
|
158
|
+
'commission': commission_amount,
|
|
159
|
+
'gross_revenue': gross_revenue,
|
|
160
|
+
'net_revenue': net_revenue,
|
|
161
|
+
'buy_cost': buy_cost,
|
|
162
|
+
'profit_loss': profit_loss
|
|
163
|
+
})
|
|
164
|
+
else:
|
|
165
|
+
self.logger.warning(f"Insufficient position for sell: {symbol}, need {quantity}")
|
|
166
|
+
self.order_manager.cancel_order(order_id)
|
|
167
|
+
|
|
168
|
+
def _get_latest_buy_cost(self, symbol: str) -> float:
|
|
169
|
+
"""Get the latest buy cost for a symbol (for PnL calculation)."""
|
|
170
|
+
for trade in reversed(self.trades):
|
|
171
|
+
if trade.get('symbol') == symbol and trade.get('type') == 'buy':
|
|
172
|
+
return float(trade.get('cost', 0.0))
|
|
173
|
+
return 0.0
|
|
174
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Order management system for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from typing import Dict, List, Optional, Any
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from ..core.base import BaseComponent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OrderManager(BaseComponent):
|
|
12
|
+
"""Manage trading orders."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, **kwargs):
|
|
15
|
+
"""Initialize order manager."""
|
|
16
|
+
super().__init__(**kwargs)
|
|
17
|
+
self.orders = {}
|
|
18
|
+
self.order_counter = 0
|
|
19
|
+
|
|
20
|
+
def initialize(self) -> bool:
|
|
21
|
+
"""Initialize order manager."""
|
|
22
|
+
self.logger.info("Initializing order manager")
|
|
23
|
+
return True
|
|
24
|
+
|
|
25
|
+
def create_order(self, symbol: str, quantity: int, order_type: str = "limit",
|
|
26
|
+
price: Optional[float] = None, stop_price: Optional[float] = None) -> str:
|
|
27
|
+
"""Create a new order."""
|
|
28
|
+
self.order_counter += 1
|
|
29
|
+
order_id = f"ORD_{self.order_counter:06d}"
|
|
30
|
+
|
|
31
|
+
order = {
|
|
32
|
+
'id': order_id,
|
|
33
|
+
'symbol': symbol,
|
|
34
|
+
'quantity': quantity,
|
|
35
|
+
'order_type': order_type,
|
|
36
|
+
'price': price,
|
|
37
|
+
'stop_price': stop_price,
|
|
38
|
+
'status': 'pending',
|
|
39
|
+
'created_at': datetime.now(),
|
|
40
|
+
'updated_at': datetime.now()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
self.orders[order_id] = order
|
|
44
|
+
self.logger.info(f"Order created: {order_id}")
|
|
45
|
+
return order_id
|
|
46
|
+
|
|
47
|
+
def get_order(self, order_id: str) -> Optional[Dict[str, Any]]:
|
|
48
|
+
"""Get order by ID."""
|
|
49
|
+
return self.orders.get(order_id)
|
|
50
|
+
|
|
51
|
+
def update_order_status(self, order_id: str, status: str) -> bool:
|
|
52
|
+
"""Update order status."""
|
|
53
|
+
if order_id in self.orders:
|
|
54
|
+
self.orders[order_id]['status'] = status
|
|
55
|
+
self.orders[order_id]['updated_at'] = datetime.now()
|
|
56
|
+
return True
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
def mark_executed(self, order_id: str, execution_price: Optional[float] = None) -> bool:
|
|
60
|
+
"""Mark order as executed."""
|
|
61
|
+
if order_id in self.orders:
|
|
62
|
+
self.orders[order_id]['status'] = 'executed'
|
|
63
|
+
self.orders[order_id]['execution_price'] = execution_price
|
|
64
|
+
self.orders[order_id]['executed_at'] = datetime.now()
|
|
65
|
+
self.orders[order_id]['updated_at'] = datetime.now()
|
|
66
|
+
return True
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
def cancel_order(self, order_id: str) -> bool:
|
|
70
|
+
"""Cancel an order."""
|
|
71
|
+
if order_id in self.orders and self.orders[order_id]['status'] == 'pending':
|
|
72
|
+
self.orders[order_id]['status'] = 'cancelled'
|
|
73
|
+
self.orders[order_id]['updated_at'] = datetime.now()
|
|
74
|
+
return True
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
def get_orders_by_symbol(self, symbol: str) -> List[Dict[str, Any]]:
|
|
78
|
+
"""Get all orders for a symbol."""
|
|
79
|
+
return [order for order in self.orders.values() if order['symbol'] == symbol]
|
|
80
|
+
|
|
81
|
+
def get_orders_by_status(self, status: str) -> List[Dict[str, Any]]:
|
|
82
|
+
"""Get all orders with specific status."""
|
|
83
|
+
return [order for order in self.orders.values() if order['status'] == status]
|
|
84
|
+
|
|
85
|
+
def get_pending_orders(self) -> List[Dict[str, Any]]:
|
|
86
|
+
"""Get all pending orders."""
|
|
87
|
+
return self.get_orders_by_status('pending')
|
|
88
|
+
|
|
89
|
+
def get_executed_orders(self) -> List[Dict[str, Any]]:
|
|
90
|
+
"""Get all executed orders."""
|
|
91
|
+
return self.get_orders_by_status('executed')
|
|
92
|
+
|
|
93
|
+
def get_order_history(self) -> List[Dict[str, Any]]:
|
|
94
|
+
"""Get complete order history."""
|
|
95
|
+
return list(self.orders.values())
|
|
96
|
+
|
|
97
|
+
def cleanup_old_orders(self, days: int = 30) -> int:
|
|
98
|
+
"""Clean up old orders."""
|
|
99
|
+
cutoff_date = datetime.now() - pd.Timedelta(days=days)
|
|
100
|
+
old_orders = [
|
|
101
|
+
order_id for order_id, order in self.orders.items()
|
|
102
|
+
if order['created_at'] < cutoff_date and order['status'] in ['executed', 'cancelled']
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
for order_id in old_orders:
|
|
106
|
+
del self.orders[order_id]
|
|
107
|
+
|
|
108
|
+
self.logger.info(f"Cleaned up {len(old_orders)} old orders")
|
|
109
|
+
return len(old_orders)
|
|
110
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Position management for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Optional
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from ..core.base import BaseComponent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PositionManager(BaseComponent):
|
|
11
|
+
"""Manage trading positions."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, **kwargs):
|
|
14
|
+
"""Initialize position manager."""
|
|
15
|
+
super().__init__(**kwargs)
|
|
16
|
+
self.positions = {}
|
|
17
|
+
|
|
18
|
+
def initialize(self) -> bool:
|
|
19
|
+
"""Initialize position manager."""
|
|
20
|
+
self.logger.info("Initializing position manager")
|
|
21
|
+
return True
|
|
22
|
+
|
|
23
|
+
def add_position(self, symbol: str, quantity: int, price: Optional[float] = None) -> bool:
|
|
24
|
+
"""Add to existing position or create new position."""
|
|
25
|
+
if symbol in self.positions:
|
|
26
|
+
# Update existing position
|
|
27
|
+
current_quantity = self.positions[symbol]['quantity']
|
|
28
|
+
current_avg_price = self.positions[symbol]['avg_price']
|
|
29
|
+
|
|
30
|
+
new_quantity = current_quantity + quantity
|
|
31
|
+
if price:
|
|
32
|
+
new_avg_price = ((current_quantity * current_avg_price) + (quantity * price)) / new_quantity
|
|
33
|
+
else:
|
|
34
|
+
new_avg_price = current_avg_price
|
|
35
|
+
|
|
36
|
+
self.positions[symbol]['quantity'] = new_quantity
|
|
37
|
+
self.positions[symbol]['avg_price'] = new_avg_price
|
|
38
|
+
self.positions[symbol]['updated_at'] = datetime.now()
|
|
39
|
+
else:
|
|
40
|
+
# Create new position
|
|
41
|
+
self.positions[symbol] = {
|
|
42
|
+
'symbol': symbol,
|
|
43
|
+
'quantity': quantity,
|
|
44
|
+
'avg_price': price or 0.0,
|
|
45
|
+
'created_at': datetime.now(),
|
|
46
|
+
'updated_at': datetime.now()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
self.logger.info(f"Position updated: {symbol} -> {self.positions[symbol]['quantity']}")
|
|
50
|
+
return True
|
|
51
|
+
|
|
52
|
+
def reduce_position(self, symbol: str, quantity: int, price: Optional[float] = None) -> bool:
|
|
53
|
+
"""Reduce existing position."""
|
|
54
|
+
if symbol not in self.positions:
|
|
55
|
+
self.logger.warning(f"No position found for symbol: {symbol}")
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
current_quantity = self.positions[symbol]['quantity']
|
|
59
|
+
if current_quantity < quantity:
|
|
60
|
+
self.logger.warning(f"Insufficient position: {symbol}")
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
new_quantity = current_quantity - quantity
|
|
64
|
+
|
|
65
|
+
if new_quantity == 0:
|
|
66
|
+
del self.positions[symbol]
|
|
67
|
+
else:
|
|
68
|
+
self.positions[symbol]['quantity'] = new_quantity
|
|
69
|
+
self.positions[symbol]['updated_at'] = datetime.now()
|
|
70
|
+
|
|
71
|
+
self.logger.info(f"Position reduced: {symbol} -> {new_quantity}")
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
def get_position(self, symbol: str) -> int:
|
|
75
|
+
"""Get current position quantity for symbol."""
|
|
76
|
+
return self.positions.get(symbol, {}).get('quantity', 0)
|
|
77
|
+
|
|
78
|
+
def get_all_positions(self) -> Dict[str, int]:
|
|
79
|
+
"""Get all current positions."""
|
|
80
|
+
return {symbol: pos['quantity'] for symbol, pos in self.positions.items()}
|
|
81
|
+
|
|
82
|
+
def can_sell(self, symbol: str, quantity: int) -> bool:
|
|
83
|
+
"""Check if we can sell the specified quantity."""
|
|
84
|
+
return self.get_position(symbol) >= quantity
|
|
85
|
+
|
|
86
|
+
def close_position(self, symbol: str, price: Optional[float] = None) -> bool:
|
|
87
|
+
"""Close entire position for symbol."""
|
|
88
|
+
if symbol not in self.positions:
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
quantity = self.positions[symbol]['quantity']
|
|
92
|
+
return self.reduce_position(symbol, quantity, price)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deltafq
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: A comprehensive Python quantitative finance library
|
|
5
|
+
Author-email: DeltaF <leek_li@outlook.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Delta-F/deltafq
|
|
8
|
+
Project-URL: Repository, https://github.com/Delta-F/deltafq
|
|
9
|
+
Project-URL: Issues, https://github.com/Delta-F/deltafq/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: numpy>=1.21.0
|
|
25
|
+
Requires-Dist: pandas>=1.3.0
|
|
26
|
+
Requires-Dist: scipy>=1.7.0
|
|
27
|
+
Requires-Dist: matplotlib>=3.4.0
|
|
28
|
+
Requires-Dist: yfinance>=0.1.70
|
|
29
|
+
Requires-Dist: requests>=2.25.0
|
|
30
|
+
Provides-Extra: viz
|
|
31
|
+
Requires-Dist: plotly>=5.0.0; extra == "viz"
|
|
32
|
+
Provides-Extra: talib
|
|
33
|
+
Requires-Dist: TA-Lib>=0.4.24; extra == "talib"
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=6.2.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-cov>=2.12.0; extra == "dev"
|
|
37
|
+
Requires-Dist: black>=21.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: flake8>=3.9.0; extra == "dev"
|
|
39
|
+
Requires-Dist: sphinx>=4.0.0; extra == "dev"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# DeltaFQ
|
|
43
|
+
|
|
44
|
+
Modern Python library for strategy research, backtesting, paper/live trading, and streamlined reporting.
|
|
45
|
+
|
|
46
|
+
## Highlights
|
|
47
|
+
|
|
48
|
+
- **Clean architecture**: `data` → `strategy` (signals) → `backtest` (execution + metrics)
|
|
49
|
+
- **Execution engine**: Unified order routing for paper/live trading via a `Broker` abstraction
|
|
50
|
+
- **Indicators**: Rich `TechnicalIndicators` (SMA/EMA/RSI/KDJ/BOLL/ATR/…)
|
|
51
|
+
- **Signals**: Simple, composable `SignalGenerator` helpers (e.g., Bollinger `touch`/`cross`/`cross_current`)
|
|
52
|
+
- **Reports**: Console-friendly summary with i18n (Chinese/English) powered by `PerformanceReporter`
|
|
53
|
+
- **Charts**: `PerformanceChart` delivers Matplotlib or Plotly (optional) performance dashboards
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install deltafq
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 60-second Quick Start (Bollinger strategy)
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import deltafq as dfq
|
|
65
|
+
|
|
66
|
+
symbol = "AAPL"
|
|
67
|
+
fetcher = dfq.data.DataFetcher()
|
|
68
|
+
indicators = dfq.indicators.TechnicalIndicators()
|
|
69
|
+
generator = dfq.strategy.SignalGenerator()
|
|
70
|
+
engine = dfq.backtest.BacktestEngine(initial_capital=100_000)
|
|
71
|
+
reporter = dfq.backtest.PerformanceReporter()
|
|
72
|
+
chart = dfq.charts.PerformanceChart()
|
|
73
|
+
|
|
74
|
+
data = fetcher.fetch_data(symbol, "2023-01-01", "2023-12-31", clean=True)
|
|
75
|
+
bands = indicators.boll(data["Close"], period=20, std_dev=2)
|
|
76
|
+
signals = generator.boll_signals(price=data["Close"], bands=bands, method="cross_current")
|
|
77
|
+
|
|
78
|
+
trades_df, values_df = engine.run_backtest(symbol, signals, data["Close"], strategy_name="BOLL")
|
|
79
|
+
|
|
80
|
+
# Text summary (zh/en available)
|
|
81
|
+
reporter.print_summary(
|
|
82
|
+
symbol=symbol,
|
|
83
|
+
trades_df=trades_df,
|
|
84
|
+
values_df=values_df,
|
|
85
|
+
title=f"{symbol} BOLL Strategy",
|
|
86
|
+
language="en",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Optional performance dashboard (Matplotlib by default; set use_plotly=True for interactive charts)
|
|
90
|
+
chart.plot_backtest_charts(values_df=values_df, benchmark_close=data["Close"], title=f"{symbol} BOLL Strategy")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## What’s inside
|
|
94
|
+
|
|
95
|
+
- `deltafq/data`: fetching, cleaning, validation
|
|
96
|
+
- `deltafq/indicators`: classic TA indicators
|
|
97
|
+
- `deltafq/strategy`: signal generation + BaseStrategy helpers
|
|
98
|
+
- `deltafq/backtest`: execution via `ExecutionEngine`; reporting via `PerformanceReporter`
|
|
99
|
+
- `deltafq/charts`: signal and performance charts (Matplotlib + optional Plotly)
|
|
100
|
+
|
|
101
|
+
## Examples
|
|
102
|
+
|
|
103
|
+
See the `examples/` folder for ready-to-run scripts:
|
|
104
|
+
|
|
105
|
+
- `04_backtest_result.py`: Bollinger strategy summary + charts
|
|
106
|
+
- `05_visualize_charts.py`: standalone visualization demos
|
|
107
|
+
- `06_base_strategy`: implement a moving-average cross using `BaseStrategy`
|
|
108
|
+
|
|
109
|
+
## Contributing
|
|
110
|
+
|
|
111
|
+
Contributions are welcome! Please open an issue or submit a PR.
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT License – see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
deltafq/__init__.py,sha256=aSYOpWiM3MuD_bF61ckgxNGSOFnW7Q1VE_t5eg4sNPA,535
|
|
2
|
+
deltafq/backtest/__init__.py,sha256=RNOK7AwB2wdJrgcYXc3ok5E7pax3-rzI_g73dEv7aYc,771
|
|
3
|
+
deltafq/backtest/engine.py,sha256=D-wbIHcawdgMvZikUvgLB7hpwYVVpaEY2gSH1yu4jtE,6283
|
|
4
|
+
deltafq/backtest/metrics.py,sha256=zxJuxTrVOkZPlSAbrnfS6l61LEPx4Vr8I8nHX588jOI,2331
|
|
5
|
+
deltafq/backtest/performance.py,sha256=Xi6J0GDrt0_0r-4cY8TVNNt6SUzPimaV2RDCXC0J7Xs,14091
|
|
6
|
+
deltafq/charts/__init__.py,sha256=-vKa-dhgt_f3EfBf5-QgUVwTNSnoudxhbUXuJWvARmQ,249
|
|
7
|
+
deltafq/charts/performance.py,sha256=P0povd-9rCqm4IYbREd4-U0xzr96zvFybCuHl2LVAkU,13861
|
|
8
|
+
deltafq/charts/price.py,sha256=8oq0_Z_ZX5hoM2BtKYgHNM6JjYxBqSXLA9pA04UQ3SU,2251
|
|
9
|
+
deltafq/charts/signals.py,sha256=cz7EGE7LBQXM7d1Xf3LMXnl5lH2rZKJKgKzLjfLBRcM,7678
|
|
10
|
+
deltafq/core/__init__.py,sha256=ZIc0Bb9n09Hpqk1Zv2fTKFNIu9NgUCMs3XGbRQGWiJQ,329
|
|
11
|
+
deltafq/core/base.py,sha256=2wgZ5BoN5-tHyQDgEwVBT6AByFsgLIKN9KEUVbOzCG8,510
|
|
12
|
+
deltafq/core/config.py,sha256=9S7APw7zayCxjzcRh71zX7jqJGDlpVMcWoW-lcJnwBY,1840
|
|
13
|
+
deltafq/core/exceptions.py,sha256=58zP4yMgeWj4aLCv3SLbTGmmh6psoxVWWu-QkdtC1bM,655
|
|
14
|
+
deltafq/core/logger.py,sha256=FVVaYjJmWlN2DCpKEUTU_4HuzqCsx7uohLwgHxevOuM,1293
|
|
15
|
+
deltafq/data/__init__.py,sha256=xSrxuhlC3Rvlp2I5_yz3Cp-XqxDG_3j08ecawaoy1Fk,291
|
|
16
|
+
deltafq/data/cleaner.py,sha256=RuqqPgNHizJ16e0TUAtS3pnftphxvRBKKAhfGtXs7aQ,1289
|
|
17
|
+
deltafq/data/fetcher.py,sha256=mGqoEwRAotCPgqlum2OGpjztN1hsNwilkAm4K1MPW_g,2060
|
|
18
|
+
deltafq/data/storage.py,sha256=hKurYcWN0jlBGd6TqYle-BMooZ2q_CwoGjaM7BRWeOs,10889
|
|
19
|
+
deltafq/data/validator.py,sha256=hFCYXaHNGlqEIcc8hv8F1Nc88JaBzI9nzgylePKVRaw,1813
|
|
20
|
+
deltafq/indicators/__init__.py,sha256=3iMKAj6DL3Hu01ZwtCE8ynUkU2o4cdoLSDddOFLceyY,294
|
|
21
|
+
deltafq/indicators/fundamental.py,sha256=tT6OsdfR7114QUBaDG8_4QH_3k68FVitgh5bmhmYS7E,827
|
|
22
|
+
deltafq/indicators/talib_indicators.py,sha256=EpvnUXpOCYNDzAT60X-kLo0iHVmRg59E-MtF7KkHNeg,3424
|
|
23
|
+
deltafq/indicators/technical.py,sha256=dnXUEfru4XedU1jcB6mXthB7ZPIsQyBwC2iHoKOSBUg,10763
|
|
24
|
+
deltafq/live/__init__.py,sha256=nEB4zVk68zAJEaCcP5os6cJ8jc6cm-RlvL0yw3q9z-c,322
|
|
25
|
+
deltafq/live/connection.py,sha256=efjTC6T-37wmELIAJUXDJnzcj0zGislPswLxq54rmOI,8613
|
|
26
|
+
deltafq/live/data_feed.py,sha256=Cl7UTKgwKeNvuQ2HyLHsPgt0V_Kh5PU76XmW4c1V10w,5784
|
|
27
|
+
deltafq/live/monitoring.py,sha256=-akC_mhtlCbdbKExMGhTour9M0R89C84KulpHsM1p40,7067
|
|
28
|
+
deltafq/live/risk_control.py,sha256=rYijF7K_YkV-CaDUEGn6BoV851GT-llBw-p4HRwEHxY,7773
|
|
29
|
+
deltafq/strategy/__init__.py,sha256=VsoXJrtLbKy9z1Mwnp1m7RkB7bewjeBU0HcQyVAycZs,176
|
|
30
|
+
deltafq/strategy/base.py,sha256=R2zCs2oVm68B2MgHYtEBC3_olZG1T82dT4AiLAiZFSA,1235
|
|
31
|
+
deltafq/strategy/signals.py,sha256=a2I0I-FfA5dlMLOVT7FSdxIX0SOhu4jtte8lVwyI0b4,8807
|
|
32
|
+
deltafq/trader/__init__.py,sha256=AoxAD0QHECZUn8xTY_VvpzD4olQ0GTJZTglVSco3yr0,296
|
|
33
|
+
deltafq/trader/broker.py,sha256=e0E2LJO9CXP3w0mcsijkYfhqOxZ2Est0P0iPFS985x8,3644
|
|
34
|
+
deltafq/trader/engine.py,sha256=YA3KQhQ6qmcQzE26ljNkswVFnBHHvdl0sAcbxIhseMU,7423
|
|
35
|
+
deltafq/trader/order_manager.py,sha256=rSo5OrF_7I-zeYZ1FG3R80ucLTAHF6cpXQI1pMM7ETc,4154
|
|
36
|
+
deltafq/trader/position_manager.py,sha256=fJ2MbjTS5TMEDw5kqHqY3_KLKxcGLS2hvAUb1skbsjw,3621
|
|
37
|
+
deltafq-0.4.0.dist-info/licenses/LICENSE,sha256=_NsOVmjUmvFiFNOp8vowVeILNMu9CRuudY6PeVIikSA,1084
|
|
38
|
+
deltafq-0.4.0.dist-info/METADATA,sha256=LUqGqYmMvByViGr3FTVMnVxNqtWYQo2Oit-J86PoKaY,4319
|
|
39
|
+
deltafq-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
40
|
+
deltafq-0.4.0.dist-info/entry_points.txt,sha256=TVBHZfYgwrYpVZfsBonsJriXHkZm1CvBvnDW7--cxl0,45
|
|
41
|
+
deltafq-0.4.0.dist-info/top_level.txt,sha256=j1Q3ce7BEqdXVZd-mlHiJBDHq3iJGiKRKEXPW8xHLHo,8
|
|
42
|
+
deltafq-0.4.0.dist-info/RECORD,,
|