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,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
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trade execution engine for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from typing import Dict, List, Optional, Any
|
|
7
|
+
from ..core.base import BaseComponent
|
|
8
|
+
from ..core.exceptions import TradingError
|
|
9
|
+
from .order_manager import OrderManager
|
|
10
|
+
from .position_manager import PositionManager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ExecutionEngine(BaseComponent):
|
|
14
|
+
"""Trade execution engine."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, broker=None, **kwargs):
|
|
17
|
+
"""Initialize execution engine."""
|
|
18
|
+
super().__init__(**kwargs)
|
|
19
|
+
self.broker = broker
|
|
20
|
+
self.order_manager = OrderManager()
|
|
21
|
+
self.position_manager = PositionManager()
|
|
22
|
+
self.execution_history = []
|
|
23
|
+
|
|
24
|
+
def initialize(self) -> bool:
|
|
25
|
+
"""Initialize execution engine."""
|
|
26
|
+
self.logger.info("Initializing execution engine")
|
|
27
|
+
|
|
28
|
+
if self.broker:
|
|
29
|
+
return self.broker.initialize()
|
|
30
|
+
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
def execute_order(self, symbol: str, quantity: int, order_type: str = "market",
|
|
34
|
+
price: Optional[float] = None) -> str:
|
|
35
|
+
"""Execute an order through the broker."""
|
|
36
|
+
try:
|
|
37
|
+
# Create order
|
|
38
|
+
order_id = self.order_manager.create_order(
|
|
39
|
+
symbol=symbol,
|
|
40
|
+
quantity=quantity,
|
|
41
|
+
order_type=order_type,
|
|
42
|
+
price=price
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Execute through broker
|
|
46
|
+
if self.broker:
|
|
47
|
+
broker_order_id = self.broker.place_order(
|
|
48
|
+
symbol=symbol,
|
|
49
|
+
quantity=quantity,
|
|
50
|
+
order_type=order_type,
|
|
51
|
+
price=price
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Update order with broker ID
|
|
55
|
+
order = self.order_manager.get_order(order_id)
|
|
56
|
+
if order:
|
|
57
|
+
order['broker_order_id'] = broker_order_id
|
|
58
|
+
|
|
59
|
+
self.logger.info(f"Order executed through broker: {order_id} -> {broker_order_id}")
|
|
60
|
+
else:
|
|
61
|
+
# Simulate execution
|
|
62
|
+
current_price = self._get_simulated_price(symbol)
|
|
63
|
+
self._simulate_execution(order_id, current_price)
|
|
64
|
+
self.logger.info(f"Order executed in simulation: {order_id}")
|
|
65
|
+
|
|
66
|
+
return order_id
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
raise TradingError(f"Failed to execute order: {str(e)}")
|
|
70
|
+
|
|
71
|
+
def _simulate_execution(self, order_id: str, execution_price: float):
|
|
72
|
+
"""Simulate order execution."""
|
|
73
|
+
order = self.order_manager.get_order(order_id)
|
|
74
|
+
if not order:
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
# Mark as executed
|
|
78
|
+
self.order_manager.mark_executed(order_id, execution_price)
|
|
79
|
+
|
|
80
|
+
# Update position
|
|
81
|
+
symbol = order['symbol']
|
|
82
|
+
quantity = order['quantity']
|
|
83
|
+
|
|
84
|
+
if quantity > 0: # Buy
|
|
85
|
+
self.position_manager.add_position(symbol, quantity, execution_price)
|
|
86
|
+
else: # Sell
|
|
87
|
+
self.position_manager.reduce_position(symbol, abs(quantity), execution_price)
|
|
88
|
+
|
|
89
|
+
# Record execution
|
|
90
|
+
self.execution_history.append({
|
|
91
|
+
'order_id': order_id,
|
|
92
|
+
'symbol': symbol,
|
|
93
|
+
'quantity': quantity,
|
|
94
|
+
'execution_price': execution_price,
|
|
95
|
+
'timestamp': pd.Timestamp.now()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
def _get_simulated_price(self, symbol: str) -> float:
|
|
99
|
+
"""Get simulated price for symbol."""
|
|
100
|
+
# Simple simulation - in real implementation this would come from market data
|
|
101
|
+
base_prices = {
|
|
102
|
+
'AAPL': 150.0,
|
|
103
|
+
'GOOGL': 2500.0,
|
|
104
|
+
'MSFT': 300.0,
|
|
105
|
+
'TSLA': 200.0
|
|
106
|
+
}
|
|
107
|
+
return base_prices.get(symbol, 100.0)
|
|
108
|
+
|
|
109
|
+
def get_execution_summary(self) -> Dict[str, Any]:
|
|
110
|
+
"""Get execution summary."""
|
|
111
|
+
return {
|
|
112
|
+
'total_orders': len(self.order_manager.get_order_history()),
|
|
113
|
+
'executed_orders': len(self.order_manager.get_executed_orders()),
|
|
114
|
+
'pending_orders': len(self.order_manager.get_pending_orders()),
|
|
115
|
+
'total_positions': len(self.position_manager.get_all_positions()),
|
|
116
|
+
'execution_history': self.execution_history
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def cancel_order(self, order_id: str) -> bool:
|
|
120
|
+
"""Cancel an order."""
|
|
121
|
+
try:
|
|
122
|
+
# Cancel in order manager
|
|
123
|
+
success = self.order_manager.cancel_order(order_id)
|
|
124
|
+
|
|
125
|
+
# Cancel with broker if available
|
|
126
|
+
if self.broker and success:
|
|
127
|
+
order = self.order_manager.get_order(order_id)
|
|
128
|
+
if order and 'broker_order_id' in order:
|
|
129
|
+
self.broker.cancel_order(order['broker_order_id'])
|
|
130
|
+
|
|
131
|
+
if success:
|
|
132
|
+
self.logger.info(f"Order cancelled: {order_id}")
|
|
133
|
+
|
|
134
|
+
return success
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
self.logger.error(f"Failed to cancel order: {str(e)}")
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
def get_order_status(self, order_id: str) -> Dict[str, Any]:
|
|
141
|
+
"""Get order status."""
|
|
142
|
+
order = self.order_manager.get_order(order_id)
|
|
143
|
+
if not order:
|
|
144
|
+
return {}
|
|
145
|
+
|
|
146
|
+
# Get status from broker if available
|
|
147
|
+
if self.broker and 'broker_order_id' in order:
|
|
148
|
+
broker_status = self.broker.get_order_status(order['broker_order_id'])
|
|
149
|
+
order.update(broker_status)
|
|
150
|
+
|
|
151
|
+
return order
|
|
152
|
+
|
|
153
|
+
def sync_with_broker(self) -> bool:
|
|
154
|
+
"""Sync orders and positions with broker."""
|
|
155
|
+
if not self.broker:
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
# Sync positions
|
|
160
|
+
broker_positions = self.broker.get_positions()
|
|
161
|
+
# Update position manager with broker positions
|
|
162
|
+
|
|
163
|
+
# Sync order statuses
|
|
164
|
+
for order_id, order in self.order_manager.orders.items():
|
|
165
|
+
if 'broker_order_id' in order:
|
|
166
|
+
broker_status = self.broker.get_order_status(order['broker_order_id'])
|
|
167
|
+
order.update(broker_status)
|
|
168
|
+
|
|
169
|
+
self.logger.info("Synced with broker")
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
self.logger.error(f"Failed to sync with broker: {str(e)}")
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
|
|
@@ -0,0 +1,111 @@
|
|
|
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 = "market",
|
|
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
|
+
|
|
111
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Position management for DeltaFQ.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Optional, Any
|
|
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
|
+
self.position_history = []
|
|
18
|
+
|
|
19
|
+
def initialize(self) -> bool:
|
|
20
|
+
"""Initialize position manager."""
|
|
21
|
+
self.logger.info("Initializing position manager")
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
def add_position(self, symbol: str, quantity: int, price: Optional[float] = None) -> bool:
|
|
25
|
+
"""Add to existing position or create new position."""
|
|
26
|
+
try:
|
|
27
|
+
if symbol in self.positions:
|
|
28
|
+
# Update existing position
|
|
29
|
+
current_quantity = self.positions[symbol]['quantity']
|
|
30
|
+
current_avg_price = self.positions[symbol]['avg_price']
|
|
31
|
+
|
|
32
|
+
new_quantity = current_quantity + quantity
|
|
33
|
+
if price:
|
|
34
|
+
new_avg_price = ((current_quantity * current_avg_price) + (quantity * price)) / new_quantity
|
|
35
|
+
else:
|
|
36
|
+
new_avg_price = current_avg_price
|
|
37
|
+
|
|
38
|
+
self.positions[symbol]['quantity'] = new_quantity
|
|
39
|
+
self.positions[symbol]['avg_price'] = new_avg_price
|
|
40
|
+
self.positions[symbol]['updated_at'] = datetime.now()
|
|
41
|
+
else:
|
|
42
|
+
# Create new position
|
|
43
|
+
self.positions[symbol] = {
|
|
44
|
+
'symbol': symbol,
|
|
45
|
+
'quantity': quantity,
|
|
46
|
+
'avg_price': price or 0.0,
|
|
47
|
+
'created_at': datetime.now(),
|
|
48
|
+
'updated_at': datetime.now()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Record in history
|
|
52
|
+
self.position_history.append({
|
|
53
|
+
'symbol': symbol,
|
|
54
|
+
'quantity': quantity,
|
|
55
|
+
'price': price,
|
|
56
|
+
'action': 'add',
|
|
57
|
+
'timestamp': datetime.now()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
self.logger.info(f"Position updated: {symbol} -> {self.positions[symbol]['quantity']}")
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
self.logger.error(f"Failed to add position: {str(e)}")
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
def reduce_position(self, symbol: str, quantity: int, price: Optional[float] = None) -> bool:
|
|
68
|
+
"""Reduce existing position."""
|
|
69
|
+
try:
|
|
70
|
+
if symbol not in self.positions:
|
|
71
|
+
self.logger.warning(f"No position found for symbol: {symbol}")
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
current_quantity = self.positions[symbol]['quantity']
|
|
75
|
+
if current_quantity < quantity:
|
|
76
|
+
self.logger.warning(f"Insufficient position: {symbol}")
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
new_quantity = current_quantity - quantity
|
|
80
|
+
|
|
81
|
+
if new_quantity == 0:
|
|
82
|
+
# Close position
|
|
83
|
+
del self.positions[symbol]
|
|
84
|
+
else:
|
|
85
|
+
# Update position
|
|
86
|
+
self.positions[symbol]['quantity'] = new_quantity
|
|
87
|
+
self.positions[symbol]['updated_at'] = datetime.now()
|
|
88
|
+
|
|
89
|
+
# Record in history
|
|
90
|
+
self.position_history.append({
|
|
91
|
+
'symbol': symbol,
|
|
92
|
+
'quantity': -quantity,
|
|
93
|
+
'price': price,
|
|
94
|
+
'action': 'reduce',
|
|
95
|
+
'timestamp': datetime.now()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
self.logger.info(f"Position reduced: {symbol} -> {new_quantity}")
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
except Exception as e:
|
|
102
|
+
self.logger.error(f"Failed to reduce position: {str(e)}")
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
def get_position(self, symbol: str) -> int:
|
|
106
|
+
"""Get current position quantity for symbol."""
|
|
107
|
+
return self.positions.get(symbol, {}).get('quantity', 0)
|
|
108
|
+
|
|
109
|
+
def get_all_positions(self) -> Dict[str, int]:
|
|
110
|
+
"""Get all current positions."""
|
|
111
|
+
return {symbol: pos['quantity'] for symbol, pos in self.positions.items()}
|
|
112
|
+
|
|
113
|
+
def get_position_details(self, symbol: str) -> Optional[Dict[str, Any]]:
|
|
114
|
+
"""Get detailed position information."""
|
|
115
|
+
return self.positions.get(symbol)
|
|
116
|
+
|
|
117
|
+
def get_all_position_details(self) -> Dict[str, Dict[str, Any]]:
|
|
118
|
+
"""Get all position details."""
|
|
119
|
+
return dict(self.positions)
|
|
120
|
+
|
|
121
|
+
def can_sell(self, symbol: str, quantity: int) -> bool:
|
|
122
|
+
"""Check if we can sell the specified quantity."""
|
|
123
|
+
current_position = self.get_position(symbol)
|
|
124
|
+
return current_position >= quantity
|
|
125
|
+
|
|
126
|
+
def get_position_value(self, symbol: str, current_price: float) -> float:
|
|
127
|
+
"""Calculate position value at current price."""
|
|
128
|
+
quantity = self.get_position(symbol)
|
|
129
|
+
return quantity * current_price
|
|
130
|
+
|
|
131
|
+
def get_total_position_value(self, current_prices: Dict[str, float]) -> float:
|
|
132
|
+
"""Calculate total position value."""
|
|
133
|
+
total_value = 0.0
|
|
134
|
+
for symbol, position in self.positions.items():
|
|
135
|
+
if symbol in current_prices:
|
|
136
|
+
total_value += position['quantity'] * current_prices[symbol]
|
|
137
|
+
return total_value
|
|
138
|
+
|
|
139
|
+
def close_position(self, symbol: str, price: Optional[float] = None) -> bool:
|
|
140
|
+
"""Close entire position for symbol."""
|
|
141
|
+
if symbol not in self.positions:
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
quantity = self.positions[symbol]['quantity']
|
|
145
|
+
return self.reduce_position(symbol, quantity, price)
|
|
146
|
+
|
|
147
|
+
def close_all_positions(self, current_prices: Dict[str, float]) -> Dict[str, bool]:
|
|
148
|
+
"""Close all positions."""
|
|
149
|
+
results = {}
|
|
150
|
+
symbols = list(self.positions.keys())
|
|
151
|
+
|
|
152
|
+
for symbol in symbols:
|
|
153
|
+
results[symbol] = self.close_position(symbol, current_prices.get(symbol))
|
|
154
|
+
|
|
155
|
+
return results
|
|
156
|
+
|
|
157
|
+
|