mcli-framework 7.6.0__py3-none-any.whl → 7.6.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 mcli-framework might be problematic. Click here for more details.
- mcli/app/commands_cmd.py +51 -39
- mcli/app/main.py +10 -2
- mcli/app/model_cmd.py +1 -1
- mcli/lib/custom_commands.py +4 -10
- mcli/ml/api/app.py +1 -5
- mcli/ml/dashboard/app.py +2 -2
- mcli/ml/dashboard/app_integrated.py +168 -116
- mcli/ml/dashboard/app_supabase.py +7 -3
- mcli/ml/dashboard/app_training.py +3 -6
- mcli/ml/dashboard/components/charts.py +74 -115
- mcli/ml/dashboard/components/metrics.py +24 -44
- mcli/ml/dashboard/components/tables.py +32 -40
- mcli/ml/dashboard/overview.py +102 -78
- mcli/ml/dashboard/pages/cicd.py +103 -56
- mcli/ml/dashboard/pages/debug_dependencies.py +35 -28
- mcli/ml/dashboard/pages/gravity_viz.py +374 -313
- mcli/ml/dashboard/pages/monte_carlo_predictions.py +50 -48
- mcli/ml/dashboard/pages/predictions_enhanced.py +396 -248
- mcli/ml/dashboard/pages/scrapers_and_logs.py +299 -273
- mcli/ml/dashboard/pages/test_portfolio.py +153 -121
- mcli/ml/dashboard/pages/trading.py +238 -169
- mcli/ml/dashboard/pages/workflows.py +129 -84
- mcli/ml/dashboard/streamlit_extras_utils.py +70 -79
- mcli/ml/dashboard/utils.py +24 -21
- mcli/ml/dashboard/warning_suppression.py +6 -4
- mcli/ml/database/session.py +16 -5
- mcli/ml/mlops/pipeline_orchestrator.py +1 -3
- mcli/ml/predictions/monte_carlo.py +6 -18
- mcli/ml/trading/alpaca_client.py +95 -96
- mcli/ml/trading/migrations.py +76 -40
- mcli/ml/trading/models.py +78 -60
- mcli/ml/trading/paper_trading.py +92 -74
- mcli/ml/trading/risk_management.py +106 -85
- mcli/ml/trading/trading_service.py +155 -110
- mcli/ml/training/train_model.py +1 -3
- mcli/self/self_cmd.py +71 -57
- mcli/workflow/daemon/daemon.py +2 -0
- mcli/workflow/model_service/openai_adapter.py +6 -2
- mcli/workflow/politician_trading/models.py +6 -2
- mcli/workflow/politician_trading/scrapers_corporate_registry.py +39 -88
- mcli/workflow/politician_trading/scrapers_free_sources.py +32 -39
- mcli/workflow/politician_trading/scrapers_third_party.py +21 -39
- mcli/workflow/politician_trading/seed_database.py +70 -89
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/METADATA +1 -1
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/RECORD +49 -49
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/WHEEL +0 -0
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/top_level.txt +0 -0
mcli/ml/trading/migrations.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""Database migrations for trading functionality"""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
|
|
4
5
|
from sqlalchemy import create_engine, text
|
|
5
6
|
from sqlalchemy.orm import sessionmaker
|
|
6
|
-
|
|
7
|
+
|
|
7
8
|
from mcli.ml.config.settings import get_settings
|
|
9
|
+
from mcli.ml.trading.models import Base
|
|
8
10
|
|
|
9
11
|
logger = logging.getLogger(__name__)
|
|
10
12
|
|
|
@@ -15,13 +17,13 @@ def create_trading_tables():
|
|
|
15
17
|
# Get database URL from settings
|
|
16
18
|
settings = get_settings()
|
|
17
19
|
engine = create_engine(settings.database.url)
|
|
18
|
-
|
|
20
|
+
|
|
19
21
|
# Create all tables
|
|
20
22
|
Base.metadata.create_all(engine)
|
|
21
|
-
|
|
23
|
+
|
|
22
24
|
logger.info("Trading tables created successfully")
|
|
23
25
|
return True
|
|
24
|
-
|
|
26
|
+
|
|
25
27
|
except Exception as e:
|
|
26
28
|
logger.error(f"Failed to create trading tables: {e}")
|
|
27
29
|
return False
|
|
@@ -33,13 +35,13 @@ def drop_trading_tables():
|
|
|
33
35
|
# Get database URL from settings
|
|
34
36
|
settings = get_settings()
|
|
35
37
|
engine = create_engine(settings.database.url)
|
|
36
|
-
|
|
38
|
+
|
|
37
39
|
# Drop all tables
|
|
38
40
|
Base.metadata.drop_all(engine)
|
|
39
|
-
|
|
41
|
+
|
|
40
42
|
logger.info("Trading tables dropped successfully")
|
|
41
43
|
return True
|
|
42
|
-
|
|
44
|
+
|
|
43
45
|
except Exception as e:
|
|
44
46
|
logger.error(f"Failed to drop trading tables: {e}")
|
|
45
47
|
return False
|
|
@@ -53,14 +55,14 @@ def migrate_trading_data():
|
|
|
53
55
|
engine = create_engine(settings.database.url)
|
|
54
56
|
Session = sessionmaker(bind=engine)
|
|
55
57
|
session = Session()
|
|
56
|
-
|
|
58
|
+
|
|
57
59
|
# Check if we need to migrate existing portfolio data
|
|
58
60
|
# This would depend on your existing schema
|
|
59
|
-
|
|
61
|
+
|
|
60
62
|
session.close()
|
|
61
63
|
logger.info("Trading data migration completed")
|
|
62
64
|
return True
|
|
63
|
-
|
|
65
|
+
|
|
64
66
|
except Exception as e:
|
|
65
67
|
logger.error(f"Failed to migrate trading data: {e}")
|
|
66
68
|
return False
|
|
@@ -72,78 +74,112 @@ def verify_trading_schema():
|
|
|
72
74
|
# Get database URL from settings
|
|
73
75
|
settings = get_settings()
|
|
74
76
|
engine = create_engine(settings.database.url)
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
# Check if tables exist
|
|
77
79
|
with engine.connect() as conn:
|
|
78
80
|
# Check for trading_accounts table
|
|
79
81
|
if "sqlite" in settings.database.url:
|
|
80
82
|
# SQLite syntax
|
|
81
|
-
result = conn.execute(
|
|
83
|
+
result = conn.execute(
|
|
84
|
+
text(
|
|
85
|
+
"""
|
|
82
86
|
SELECT name FROM sqlite_master
|
|
83
87
|
WHERE type='table' AND name='trading_accounts';
|
|
84
|
-
"""
|
|
88
|
+
"""
|
|
89
|
+
)
|
|
90
|
+
)
|
|
85
91
|
accounts_exists = result.fetchone() is not None
|
|
86
|
-
|
|
87
|
-
result = conn.execute(
|
|
92
|
+
|
|
93
|
+
result = conn.execute(
|
|
94
|
+
text(
|
|
95
|
+
"""
|
|
88
96
|
SELECT name FROM sqlite_master
|
|
89
97
|
WHERE type='table' AND name='portfolios';
|
|
90
|
-
"""
|
|
98
|
+
"""
|
|
99
|
+
)
|
|
100
|
+
)
|
|
91
101
|
portfolios_exists = result.fetchone() is not None
|
|
92
|
-
|
|
93
|
-
result = conn.execute(
|
|
102
|
+
|
|
103
|
+
result = conn.execute(
|
|
104
|
+
text(
|
|
105
|
+
"""
|
|
94
106
|
SELECT name FROM sqlite_master
|
|
95
107
|
WHERE type='table' AND name='positions';
|
|
96
|
-
"""
|
|
108
|
+
"""
|
|
109
|
+
)
|
|
110
|
+
)
|
|
97
111
|
positions_exists = result.fetchone() is not None
|
|
98
|
-
|
|
99
|
-
result = conn.execute(
|
|
112
|
+
|
|
113
|
+
result = conn.execute(
|
|
114
|
+
text(
|
|
115
|
+
"""
|
|
100
116
|
SELECT name FROM sqlite_master
|
|
101
117
|
WHERE type='table' AND name='trading_orders';
|
|
102
|
-
"""
|
|
118
|
+
"""
|
|
119
|
+
)
|
|
120
|
+
)
|
|
103
121
|
orders_exists = result.fetchone() is not None
|
|
104
122
|
else:
|
|
105
123
|
# PostgreSQL syntax
|
|
106
|
-
result = conn.execute(
|
|
124
|
+
result = conn.execute(
|
|
125
|
+
text(
|
|
126
|
+
"""
|
|
107
127
|
SELECT EXISTS (
|
|
108
128
|
SELECT FROM information_schema.tables
|
|
109
129
|
WHERE table_name = 'trading_accounts'
|
|
110
130
|
);
|
|
111
|
-
"""
|
|
131
|
+
"""
|
|
132
|
+
)
|
|
133
|
+
)
|
|
112
134
|
accounts_exists = result.scalar()
|
|
113
|
-
|
|
114
|
-
result = conn.execute(
|
|
135
|
+
|
|
136
|
+
result = conn.execute(
|
|
137
|
+
text(
|
|
138
|
+
"""
|
|
115
139
|
SELECT EXISTS (
|
|
116
140
|
SELECT FROM information_schema.tables
|
|
117
141
|
WHERE table_name = 'portfolios'
|
|
118
142
|
);
|
|
119
|
-
"""
|
|
143
|
+
"""
|
|
144
|
+
)
|
|
145
|
+
)
|
|
120
146
|
portfolios_exists = result.scalar()
|
|
121
|
-
|
|
122
|
-
result = conn.execute(
|
|
147
|
+
|
|
148
|
+
result = conn.execute(
|
|
149
|
+
text(
|
|
150
|
+
"""
|
|
123
151
|
SELECT EXISTS (
|
|
124
152
|
SELECT FROM information_schema.tables
|
|
125
153
|
WHERE table_name = 'positions'
|
|
126
154
|
);
|
|
127
|
-
"""
|
|
155
|
+
"""
|
|
156
|
+
)
|
|
157
|
+
)
|
|
128
158
|
positions_exists = result.scalar()
|
|
129
|
-
|
|
130
|
-
result = conn.execute(
|
|
159
|
+
|
|
160
|
+
result = conn.execute(
|
|
161
|
+
text(
|
|
162
|
+
"""
|
|
131
163
|
SELECT EXISTS (
|
|
132
164
|
SELECT FROM information_schema.tables
|
|
133
165
|
WHERE table_name = 'trading_orders'
|
|
134
166
|
);
|
|
135
|
-
"""
|
|
167
|
+
"""
|
|
168
|
+
)
|
|
169
|
+
)
|
|
136
170
|
orders_exists = result.scalar()
|
|
137
|
-
|
|
138
|
-
all_tables_exist = all(
|
|
139
|
-
|
|
171
|
+
|
|
172
|
+
all_tables_exist = all(
|
|
173
|
+
[accounts_exists, portfolios_exists, positions_exists, orders_exists]
|
|
174
|
+
)
|
|
175
|
+
|
|
140
176
|
if all_tables_exist:
|
|
141
177
|
logger.info("Trading schema verification successful")
|
|
142
178
|
else:
|
|
143
179
|
logger.warning("Some trading tables are missing")
|
|
144
|
-
|
|
180
|
+
|
|
145
181
|
return all_tables_exist
|
|
146
|
-
|
|
182
|
+
|
|
147
183
|
except Exception as e:
|
|
148
184
|
logger.error(f"Failed to verify trading schema: {e}")
|
|
149
185
|
return False
|
|
@@ -156,9 +192,9 @@ if __name__ == "__main__":
|
|
|
156
192
|
print("✅ Trading tables created successfully")
|
|
157
193
|
else:
|
|
158
194
|
print("❌ Failed to create trading tables")
|
|
159
|
-
|
|
195
|
+
|
|
160
196
|
print("Verifying schema...")
|
|
161
197
|
if verify_trading_schema():
|
|
162
198
|
print("✅ Trading schema verified successfully")
|
|
163
199
|
else:
|
|
164
|
-
print("❌ Trading schema verification failed")
|
|
200
|
+
print("❌ Trading schema verification failed")
|
mcli/ml/trading/models.py
CHANGED
|
@@ -7,20 +7,20 @@ from enum import Enum
|
|
|
7
7
|
from typing import Any, Dict, List, Optional, Union
|
|
8
8
|
from uuid import UUID, uuid4
|
|
9
9
|
|
|
10
|
-
from
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
from sqlalchemy import JSON, Boolean, Column, DateTime
|
|
12
|
+
from sqlalchemy import Enum as SQLEnum
|
|
13
|
+
from sqlalchemy import Float, ForeignKey, Index, Integer, Numeric, String, Text
|
|
14
14
|
from sqlalchemy.dialects.postgresql import UUID as PostgresUUID
|
|
15
15
|
from sqlalchemy.ext.declarative import declarative_base
|
|
16
16
|
from sqlalchemy.orm import relationship
|
|
17
|
-
from pydantic import BaseModel, Field
|
|
18
17
|
|
|
19
18
|
Base = declarative_base()
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
class OrderStatus(Enum):
|
|
23
22
|
"""Order status enumeration"""
|
|
23
|
+
|
|
24
24
|
PENDING = "pending"
|
|
25
25
|
SUBMITTED = "submitted"
|
|
26
26
|
FILLED = "filled"
|
|
@@ -32,6 +32,7 @@ class OrderStatus(Enum):
|
|
|
32
32
|
|
|
33
33
|
class OrderType(Enum):
|
|
34
34
|
"""Order type enumeration"""
|
|
35
|
+
|
|
35
36
|
MARKET = "market"
|
|
36
37
|
LIMIT = "limit"
|
|
37
38
|
STOP = "stop"
|
|
@@ -41,18 +42,21 @@ class OrderType(Enum):
|
|
|
41
42
|
|
|
42
43
|
class OrderSide(Enum):
|
|
43
44
|
"""Order side enumeration"""
|
|
45
|
+
|
|
44
46
|
BUY = "buy"
|
|
45
47
|
SELL = "sell"
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
class PositionSide(Enum):
|
|
49
51
|
"""Position side enumeration"""
|
|
52
|
+
|
|
50
53
|
LONG = "long"
|
|
51
54
|
SHORT = "short"
|
|
52
55
|
|
|
53
56
|
|
|
54
57
|
class PortfolioType(Enum):
|
|
55
58
|
"""Portfolio type enumeration"""
|
|
59
|
+
|
|
56
60
|
TEST = "test"
|
|
57
61
|
LIVE = "live"
|
|
58
62
|
PAPER = "paper"
|
|
@@ -60,6 +64,7 @@ class PortfolioType(Enum):
|
|
|
60
64
|
|
|
61
65
|
class RiskLevel(Enum):
|
|
62
66
|
"""Risk level enumeration"""
|
|
67
|
+
|
|
63
68
|
CONSERVATIVE = "conservative"
|
|
64
69
|
MODERATE = "moderate"
|
|
65
70
|
AGGRESSIVE = "aggressive"
|
|
@@ -68,29 +73,30 @@ class RiskLevel(Enum):
|
|
|
68
73
|
# Database Models
|
|
69
74
|
class TradingAccount(Base):
|
|
70
75
|
"""Trading account information"""
|
|
76
|
+
|
|
71
77
|
__tablename__ = "trading_accounts"
|
|
72
|
-
|
|
78
|
+
|
|
73
79
|
id = Column(PostgresUUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
74
80
|
user_id = Column(PostgresUUID(as_uuid=True), nullable=False) # Remove foreign key for now
|
|
75
81
|
account_name = Column(String(100), nullable=False)
|
|
76
82
|
account_type = Column(SQLEnum(PortfolioType), nullable=False, default=PortfolioType.TEST)
|
|
77
|
-
|
|
83
|
+
|
|
78
84
|
# Alpaca credentials (encrypted)
|
|
79
85
|
alpaca_api_key = Column(String(255), nullable=True)
|
|
80
86
|
alpaca_secret_key = Column(String(255), nullable=True)
|
|
81
87
|
alpaca_base_url = Column(String(255), nullable=True)
|
|
82
|
-
|
|
88
|
+
|
|
83
89
|
# Account settings
|
|
84
90
|
paper_trading = Column(Boolean, default=True)
|
|
85
91
|
risk_level = Column(SQLEnum(RiskLevel), default=RiskLevel.MODERATE)
|
|
86
92
|
max_position_size = Column(Float, default=0.1) # Max 10% per position
|
|
87
93
|
max_portfolio_risk = Column(Float, default=0.2) # Max 20% portfolio risk
|
|
88
|
-
|
|
94
|
+
|
|
89
95
|
# Metadata
|
|
90
96
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
91
97
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
92
98
|
is_active = Column(Boolean, default=True)
|
|
93
|
-
|
|
99
|
+
|
|
94
100
|
# Relationships
|
|
95
101
|
portfolios = relationship("Portfolio", back_populates="trading_account")
|
|
96
102
|
orders = relationship("TradingOrder", back_populates="trading_account")
|
|
@@ -98,18 +104,21 @@ class TradingAccount(Base):
|
|
|
98
104
|
|
|
99
105
|
class Portfolio(Base):
|
|
100
106
|
"""Portfolio information"""
|
|
107
|
+
|
|
101
108
|
__tablename__ = "portfolios"
|
|
102
|
-
|
|
109
|
+
|
|
103
110
|
id = Column(PostgresUUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
104
|
-
trading_account_id = Column(
|
|
111
|
+
trading_account_id = Column(
|
|
112
|
+
PostgresUUID(as_uuid=True), ForeignKey("trading_accounts.id"), nullable=False
|
|
113
|
+
)
|
|
105
114
|
name = Column(String(100), nullable=False)
|
|
106
115
|
description = Column(Text, nullable=True)
|
|
107
|
-
|
|
116
|
+
|
|
108
117
|
# Portfolio settings
|
|
109
118
|
initial_capital = Column(Numeric(15, 2), nullable=False, default=100000.00)
|
|
110
119
|
current_value = Column(Numeric(15, 2), nullable=False, default=100000.00)
|
|
111
120
|
cash_balance = Column(Numeric(15, 2), nullable=False, default=100000.00)
|
|
112
|
-
|
|
121
|
+
|
|
113
122
|
# Performance metrics
|
|
114
123
|
total_return = Column(Float, default=0.0)
|
|
115
124
|
total_return_pct = Column(Float, default=0.0)
|
|
@@ -120,17 +129,17 @@ class Portfolio(Base):
|
|
|
120
129
|
max_drawdown = Column(Float, default=0.0)
|
|
121
130
|
max_drawdown_duration = Column(Integer, default=0)
|
|
122
131
|
volatility = Column(Float, default=0.0)
|
|
123
|
-
|
|
132
|
+
|
|
124
133
|
# Risk metrics
|
|
125
134
|
var_95 = Column(Float, default=0.0) # Value at Risk 95%
|
|
126
135
|
cvar_95 = Column(Float, default=0.0) # Conditional Value at Risk 95%
|
|
127
136
|
beta = Column(Float, default=1.0)
|
|
128
|
-
|
|
137
|
+
|
|
129
138
|
# Status
|
|
130
139
|
is_active = Column(Boolean, default=True)
|
|
131
140
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
132
141
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
133
|
-
|
|
142
|
+
|
|
134
143
|
# Relationships
|
|
135
144
|
trading_account = relationship("TradingAccount", back_populates="portfolios")
|
|
136
145
|
positions = relationship("Position", back_populates="portfolio")
|
|
@@ -140,174 +149,177 @@ class Portfolio(Base):
|
|
|
140
149
|
|
|
141
150
|
class Position(Base):
|
|
142
151
|
"""Individual position in a portfolio"""
|
|
152
|
+
|
|
143
153
|
__tablename__ = "positions"
|
|
144
|
-
|
|
154
|
+
|
|
145
155
|
id = Column(PostgresUUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
146
156
|
portfolio_id = Column(PostgresUUID(as_uuid=True), ForeignKey("portfolios.id"), nullable=False)
|
|
147
157
|
symbol = Column(String(10), nullable=False, index=True)
|
|
148
|
-
|
|
158
|
+
|
|
149
159
|
# Position details
|
|
150
160
|
quantity = Column(Integer, nullable=False)
|
|
151
161
|
side = Column(SQLEnum(PositionSide), nullable=False)
|
|
152
162
|
average_price = Column(Numeric(10, 4), nullable=False)
|
|
153
163
|
current_price = Column(Numeric(10, 4), nullable=False)
|
|
154
|
-
|
|
164
|
+
|
|
155
165
|
# Financial metrics
|
|
156
166
|
market_value = Column(Numeric(15, 2), nullable=False)
|
|
157
167
|
cost_basis = Column(Numeric(15, 2), nullable=False)
|
|
158
168
|
unrealized_pnl = Column(Numeric(15, 2), nullable=False, default=0.0)
|
|
159
169
|
unrealized_pnl_pct = Column(Float, nullable=False, default=0.0)
|
|
160
170
|
realized_pnl = Column(Numeric(15, 2), nullable=False, default=0.0)
|
|
161
|
-
|
|
171
|
+
|
|
162
172
|
# Position sizing
|
|
163
173
|
position_size_pct = Column(Float, nullable=False, default=0.0) # % of portfolio
|
|
164
174
|
weight = Column(Float, nullable=False, default=0.0) # Portfolio weight
|
|
165
|
-
|
|
175
|
+
|
|
166
176
|
# Metadata
|
|
167
177
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
168
178
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
169
|
-
|
|
179
|
+
|
|
170
180
|
# Relationships
|
|
171
181
|
portfolio = relationship("Portfolio", back_populates="positions")
|
|
172
182
|
trades = relationship("TradingOrder", back_populates="position")
|
|
173
|
-
|
|
183
|
+
|
|
174
184
|
# Indexes
|
|
175
|
-
__table_args__ = (
|
|
176
|
-
Index('idx_position_portfolio_symbol', 'portfolio_id', 'symbol'),
|
|
177
|
-
)
|
|
185
|
+
__table_args__ = (Index("idx_position_portfolio_symbol", "portfolio_id", "symbol"),)
|
|
178
186
|
|
|
179
187
|
|
|
180
188
|
class TradingOrder(Base):
|
|
181
189
|
"""Trading order information"""
|
|
190
|
+
|
|
182
191
|
__tablename__ = "trading_orders"
|
|
183
|
-
|
|
192
|
+
|
|
184
193
|
id = Column(PostgresUUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
185
|
-
trading_account_id = Column(
|
|
194
|
+
trading_account_id = Column(
|
|
195
|
+
PostgresUUID(as_uuid=True), ForeignKey("trading_accounts.id"), nullable=False
|
|
196
|
+
)
|
|
186
197
|
portfolio_id = Column(PostgresUUID(as_uuid=True), ForeignKey("portfolios.id"), nullable=False)
|
|
187
198
|
position_id = Column(PostgresUUID(as_uuid=True), ForeignKey("positions.id"), nullable=True)
|
|
188
|
-
|
|
199
|
+
|
|
189
200
|
# Order details
|
|
190
201
|
symbol = Column(String(10), nullable=False, index=True)
|
|
191
202
|
side = Column(SQLEnum(OrderSide), nullable=False)
|
|
192
203
|
order_type = Column(SQLEnum(OrderType), nullable=False)
|
|
193
204
|
quantity = Column(Integer, nullable=False)
|
|
194
|
-
|
|
205
|
+
|
|
195
206
|
# Pricing
|
|
196
207
|
limit_price = Column(Numeric(10, 4), nullable=True)
|
|
197
208
|
stop_price = Column(Numeric(10, 4), nullable=True)
|
|
198
209
|
average_fill_price = Column(Numeric(10, 4), nullable=True)
|
|
199
|
-
|
|
210
|
+
|
|
200
211
|
# Status and execution
|
|
201
212
|
status = Column(SQLEnum(OrderStatus), nullable=False, default=OrderStatus.PENDING)
|
|
202
213
|
filled_quantity = Column(Integer, nullable=False, default=0)
|
|
203
214
|
remaining_quantity = Column(Integer, nullable=False)
|
|
204
|
-
|
|
215
|
+
|
|
205
216
|
# Timestamps
|
|
206
217
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
207
218
|
submitted_at = Column(DateTime, nullable=True)
|
|
208
219
|
filled_at = Column(DateTime, nullable=True)
|
|
209
220
|
cancelled_at = Column(DateTime, nullable=True)
|
|
210
|
-
|
|
221
|
+
|
|
211
222
|
# Additional info
|
|
212
223
|
time_in_force = Column(String(20), default="day")
|
|
213
224
|
extended_hours = Column(Boolean, default=False)
|
|
214
225
|
client_order_id = Column(String(100), nullable=True)
|
|
215
226
|
alpaca_order_id = Column(String(100), nullable=True, index=True)
|
|
216
|
-
|
|
227
|
+
|
|
217
228
|
# Relationships
|
|
218
229
|
trading_account = relationship("TradingAccount", back_populates="orders")
|
|
219
230
|
portfolio = relationship("Portfolio", back_populates="orders")
|
|
220
231
|
position = relationship("Position", back_populates="trades")
|
|
221
|
-
|
|
232
|
+
|
|
222
233
|
# Indexes
|
|
223
234
|
__table_args__ = (
|
|
224
|
-
Index(
|
|
225
|
-
Index(
|
|
235
|
+
Index("idx_order_portfolio_status", "portfolio_id", "status"),
|
|
236
|
+
Index("idx_order_symbol_status", "symbol", "status"),
|
|
226
237
|
)
|
|
227
238
|
|
|
228
239
|
|
|
229
240
|
class PortfolioPerformanceSnapshot(Base):
|
|
230
241
|
"""Daily portfolio performance snapshots"""
|
|
242
|
+
|
|
231
243
|
__tablename__ = "portfolio_performance_snapshots"
|
|
232
|
-
|
|
244
|
+
|
|
233
245
|
id = Column(PostgresUUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
234
246
|
portfolio_id = Column(PostgresUUID(as_uuid=True), ForeignKey("portfolios.id"), nullable=False)
|
|
235
|
-
|
|
247
|
+
|
|
236
248
|
# Snapshot data
|
|
237
249
|
snapshot_date = Column(DateTime, nullable=False, index=True)
|
|
238
250
|
portfolio_value = Column(Numeric(15, 2), nullable=False)
|
|
239
251
|
cash_balance = Column(Numeric(15, 2), nullable=False)
|
|
240
|
-
|
|
252
|
+
|
|
241
253
|
# Daily performance
|
|
242
254
|
daily_return = Column(Numeric(15, 2), nullable=False, default=0.0)
|
|
243
255
|
daily_return_pct = Column(Float, nullable=False, default=0.0)
|
|
244
|
-
|
|
256
|
+
|
|
245
257
|
# Cumulative performance
|
|
246
258
|
total_return = Column(Numeric(15, 2), nullable=False, default=0.0)
|
|
247
259
|
total_return_pct = Column(Float, nullable=False, default=0.0)
|
|
248
|
-
|
|
260
|
+
|
|
249
261
|
# Risk metrics
|
|
250
262
|
volatility = Column(Float, nullable=False, default=0.0)
|
|
251
263
|
sharpe_ratio = Column(Float, nullable=False, default=0.0)
|
|
252
264
|
max_drawdown = Column(Float, nullable=False, default=0.0)
|
|
253
|
-
|
|
265
|
+
|
|
254
266
|
# Position data (JSON)
|
|
255
267
|
positions_data = Column(JSON, nullable=True)
|
|
256
|
-
|
|
268
|
+
|
|
257
269
|
# Metadata
|
|
258
270
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
259
|
-
|
|
271
|
+
|
|
260
272
|
# Relationships
|
|
261
273
|
portfolio = relationship("Portfolio", back_populates="performance_snapshots")
|
|
262
|
-
|
|
274
|
+
|
|
263
275
|
# Indexes
|
|
264
|
-
__table_args__ = (
|
|
265
|
-
Index('idx_snapshot_portfolio_date', 'portfolio_id', 'snapshot_date'),
|
|
266
|
-
)
|
|
276
|
+
__table_args__ = (Index("idx_snapshot_portfolio_date", "portfolio_id", "snapshot_date"),)
|
|
267
277
|
|
|
268
278
|
|
|
269
279
|
class TradingSignal(Base):
|
|
270
280
|
"""Trading signals generated by ML models"""
|
|
281
|
+
|
|
271
282
|
__tablename__ = "trading_signals"
|
|
272
|
-
|
|
283
|
+
|
|
273
284
|
id = Column(PostgresUUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
274
285
|
portfolio_id = Column(PostgresUUID(as_uuid=True), ForeignKey("portfolios.id"), nullable=False)
|
|
275
|
-
|
|
286
|
+
|
|
276
287
|
# Signal details
|
|
277
288
|
symbol = Column(String(10), nullable=False, index=True)
|
|
278
289
|
signal_type = Column(String(20), nullable=False) # "buy", "sell", "hold"
|
|
279
290
|
confidence = Column(Float, nullable=False)
|
|
280
291
|
strength = Column(Float, nullable=False) # Signal strength 0-1
|
|
281
|
-
|
|
292
|
+
|
|
282
293
|
# ML model info
|
|
283
294
|
model_id = Column(String(100), nullable=True)
|
|
284
295
|
model_version = Column(String(50), nullable=True)
|
|
285
296
|
prediction_id = Column(PostgresUUID(as_uuid=True), nullable=True)
|
|
286
|
-
|
|
297
|
+
|
|
287
298
|
# Signal parameters
|
|
288
299
|
target_price = Column(Numeric(10, 4), nullable=True)
|
|
289
300
|
stop_loss = Column(Numeric(10, 4), nullable=True)
|
|
290
301
|
take_profit = Column(Numeric(10, 4), nullable=True)
|
|
291
302
|
position_size = Column(Float, nullable=True) # Suggested position size as % of portfolio
|
|
292
|
-
|
|
303
|
+
|
|
293
304
|
# Metadata
|
|
294
305
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
295
306
|
expires_at = Column(DateTime, nullable=True)
|
|
296
307
|
is_active = Column(Boolean, default=True)
|
|
297
|
-
|
|
308
|
+
|
|
298
309
|
# Relationships
|
|
299
310
|
portfolio = relationship("Portfolio")
|
|
300
|
-
|
|
311
|
+
|
|
301
312
|
# Indexes
|
|
302
313
|
__table_args__ = (
|
|
303
|
-
Index(
|
|
304
|
-
Index(
|
|
314
|
+
Index("idx_signal_portfolio_symbol", "portfolio_id", "symbol"),
|
|
315
|
+
Index("idx_signal_created_active", "created_at", "is_active"),
|
|
305
316
|
)
|
|
306
317
|
|
|
307
318
|
|
|
308
319
|
# Pydantic Models for API
|
|
309
320
|
class TradingAccountCreate(BaseModel):
|
|
310
321
|
"""Create trading account request"""
|
|
322
|
+
|
|
311
323
|
account_name: str = Field(..., min_length=1, max_length=100)
|
|
312
324
|
account_type: PortfolioType = Field(default=PortfolioType.TEST)
|
|
313
325
|
alpaca_api_key: Optional[str] = None
|
|
@@ -320,6 +332,7 @@ class TradingAccountCreate(BaseModel):
|
|
|
320
332
|
|
|
321
333
|
class PortfolioCreate(BaseModel):
|
|
322
334
|
"""Create portfolio request"""
|
|
335
|
+
|
|
323
336
|
name: str = Field(..., min_length=1, max_length=100)
|
|
324
337
|
description: Optional[str] = None
|
|
325
338
|
initial_capital: float = Field(default=100000.0, gt=0)
|
|
@@ -327,6 +340,7 @@ class PortfolioCreate(BaseModel):
|
|
|
327
340
|
|
|
328
341
|
class OrderCreate(BaseModel):
|
|
329
342
|
"""Create order request"""
|
|
343
|
+
|
|
330
344
|
symbol: str = Field(..., min_length=1, max_length=10)
|
|
331
345
|
side: OrderSide
|
|
332
346
|
order_type: OrderType
|
|
@@ -339,6 +353,7 @@ class OrderCreate(BaseModel):
|
|
|
339
353
|
|
|
340
354
|
class PositionResponse(BaseModel):
|
|
341
355
|
"""Position response model"""
|
|
356
|
+
|
|
342
357
|
id: UUID
|
|
343
358
|
symbol: str
|
|
344
359
|
quantity: int
|
|
@@ -358,6 +373,7 @@ class PositionResponse(BaseModel):
|
|
|
358
373
|
|
|
359
374
|
class OrderResponse(BaseModel):
|
|
360
375
|
"""Order response model"""
|
|
376
|
+
|
|
361
377
|
id: UUID
|
|
362
378
|
symbol: str
|
|
363
379
|
side: OrderSide
|
|
@@ -380,6 +396,7 @@ class OrderResponse(BaseModel):
|
|
|
380
396
|
|
|
381
397
|
class PortfolioResponse(BaseModel):
|
|
382
398
|
"""Portfolio response model"""
|
|
399
|
+
|
|
383
400
|
id: UUID
|
|
384
401
|
name: str
|
|
385
402
|
description: Optional[str]
|
|
@@ -402,6 +419,7 @@ class PortfolioResponse(BaseModel):
|
|
|
402
419
|
|
|
403
420
|
class TradingSignalResponse(BaseModel):
|
|
404
421
|
"""Trading signal response model"""
|
|
422
|
+
|
|
405
423
|
id: UUID
|
|
406
424
|
symbol: str
|
|
407
425
|
signal_type: str
|
|
@@ -415,4 +433,4 @@ class TradingSignalResponse(BaseModel):
|
|
|
415
433
|
position_size: Optional[float]
|
|
416
434
|
created_at: datetime
|
|
417
435
|
expires_at: Optional[datetime]
|
|
418
|
-
is_active: bool
|
|
436
|
+
is_active: bool
|