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.

Files changed (49) hide show
  1. mcli/app/commands_cmd.py +51 -39
  2. mcli/app/main.py +10 -2
  3. mcli/app/model_cmd.py +1 -1
  4. mcli/lib/custom_commands.py +4 -10
  5. mcli/ml/api/app.py +1 -5
  6. mcli/ml/dashboard/app.py +2 -2
  7. mcli/ml/dashboard/app_integrated.py +168 -116
  8. mcli/ml/dashboard/app_supabase.py +7 -3
  9. mcli/ml/dashboard/app_training.py +3 -6
  10. mcli/ml/dashboard/components/charts.py +74 -115
  11. mcli/ml/dashboard/components/metrics.py +24 -44
  12. mcli/ml/dashboard/components/tables.py +32 -40
  13. mcli/ml/dashboard/overview.py +102 -78
  14. mcli/ml/dashboard/pages/cicd.py +103 -56
  15. mcli/ml/dashboard/pages/debug_dependencies.py +35 -28
  16. mcli/ml/dashboard/pages/gravity_viz.py +374 -313
  17. mcli/ml/dashboard/pages/monte_carlo_predictions.py +50 -48
  18. mcli/ml/dashboard/pages/predictions_enhanced.py +396 -248
  19. mcli/ml/dashboard/pages/scrapers_and_logs.py +299 -273
  20. mcli/ml/dashboard/pages/test_portfolio.py +153 -121
  21. mcli/ml/dashboard/pages/trading.py +238 -169
  22. mcli/ml/dashboard/pages/workflows.py +129 -84
  23. mcli/ml/dashboard/streamlit_extras_utils.py +70 -79
  24. mcli/ml/dashboard/utils.py +24 -21
  25. mcli/ml/dashboard/warning_suppression.py +6 -4
  26. mcli/ml/database/session.py +16 -5
  27. mcli/ml/mlops/pipeline_orchestrator.py +1 -3
  28. mcli/ml/predictions/monte_carlo.py +6 -18
  29. mcli/ml/trading/alpaca_client.py +95 -96
  30. mcli/ml/trading/migrations.py +76 -40
  31. mcli/ml/trading/models.py +78 -60
  32. mcli/ml/trading/paper_trading.py +92 -74
  33. mcli/ml/trading/risk_management.py +106 -85
  34. mcli/ml/trading/trading_service.py +155 -110
  35. mcli/ml/training/train_model.py +1 -3
  36. mcli/self/self_cmd.py +71 -57
  37. mcli/workflow/daemon/daemon.py +2 -0
  38. mcli/workflow/model_service/openai_adapter.py +6 -2
  39. mcli/workflow/politician_trading/models.py +6 -2
  40. mcli/workflow/politician_trading/scrapers_corporate_registry.py +39 -88
  41. mcli/workflow/politician_trading/scrapers_free_sources.py +32 -39
  42. mcli/workflow/politician_trading/scrapers_third_party.py +21 -39
  43. mcli/workflow/politician_trading/seed_database.py +70 -89
  44. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/METADATA +1 -1
  45. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/RECORD +49 -49
  46. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/WHEEL +0 -0
  47. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/entry_points.txt +0 -0
  48. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/licenses/LICENSE +0 -0
  49. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/top_level.txt +0 -0
@@ -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
- from mcli.ml.trading.models import Base
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(text("""
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(text("""
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(text("""
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(text("""
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(text("""
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(text("""
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(text("""
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(text("""
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([accounts_exists, portfolios_exists, positions_exists, orders_exists])
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 sqlalchemy import (
11
- Boolean, Column, DateTime, Enum as SQLEnum, Float, ForeignKey,
12
- Integer, String, Text, JSON, Numeric, Index
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(PostgresUUID(as_uuid=True), ForeignKey("trading_accounts.id"), nullable=False)
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(PostgresUUID(as_uuid=True), ForeignKey("trading_accounts.id"), nullable=False)
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('idx_order_portfolio_status', 'portfolio_id', 'status'),
225
- Index('idx_order_symbol_status', 'symbol', 'status'),
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('idx_signal_portfolio_symbol', 'portfolio_id', 'symbol'),
304
- Index('idx_signal_created_active', 'created_at', 'is_active'),
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