mcli-framework 7.5.1__py3-none-any.whl → 7.6.1__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 (56) hide show
  1. mcli/app/commands_cmd.py +51 -39
  2. mcli/app/completion_helpers.py +4 -13
  3. mcli/app/main.py +21 -25
  4. mcli/app/model_cmd.py +119 -9
  5. mcli/lib/custom_commands.py +16 -11
  6. mcli/ml/api/app.py +1 -5
  7. mcli/ml/dashboard/app.py +2 -2
  8. mcli/ml/dashboard/app_integrated.py +168 -116
  9. mcli/ml/dashboard/app_supabase.py +7 -3
  10. mcli/ml/dashboard/app_training.py +3 -6
  11. mcli/ml/dashboard/components/charts.py +74 -115
  12. mcli/ml/dashboard/components/metrics.py +24 -44
  13. mcli/ml/dashboard/components/tables.py +32 -40
  14. mcli/ml/dashboard/overview.py +102 -78
  15. mcli/ml/dashboard/pages/cicd.py +103 -56
  16. mcli/ml/dashboard/pages/debug_dependencies.py +35 -28
  17. mcli/ml/dashboard/pages/gravity_viz.py +374 -313
  18. mcli/ml/dashboard/pages/monte_carlo_predictions.py +50 -48
  19. mcli/ml/dashboard/pages/predictions_enhanced.py +396 -248
  20. mcli/ml/dashboard/pages/scrapers_and_logs.py +299 -273
  21. mcli/ml/dashboard/pages/test_portfolio.py +153 -121
  22. mcli/ml/dashboard/pages/trading.py +238 -169
  23. mcli/ml/dashboard/pages/workflows.py +129 -84
  24. mcli/ml/dashboard/streamlit_extras_utils.py +70 -79
  25. mcli/ml/dashboard/utils.py +24 -21
  26. mcli/ml/dashboard/warning_suppression.py +6 -4
  27. mcli/ml/database/session.py +16 -5
  28. mcli/ml/mlops/pipeline_orchestrator.py +1 -3
  29. mcli/ml/predictions/monte_carlo.py +6 -18
  30. mcli/ml/trading/alpaca_client.py +95 -96
  31. mcli/ml/trading/migrations.py +76 -40
  32. mcli/ml/trading/models.py +78 -60
  33. mcli/ml/trading/paper_trading.py +92 -74
  34. mcli/ml/trading/risk_management.py +106 -85
  35. mcli/ml/trading/trading_service.py +155 -110
  36. mcli/ml/training/train_model.py +1 -3
  37. mcli/{app → self}/completion_cmd.py +6 -6
  38. mcli/self/self_cmd.py +100 -57
  39. mcli/test/test_cmd.py +30 -0
  40. mcli/workflow/daemon/daemon.py +2 -0
  41. mcli/workflow/model_service/openai_adapter.py +347 -0
  42. mcli/workflow/politician_trading/models.py +6 -2
  43. mcli/workflow/politician_trading/scrapers_corporate_registry.py +39 -88
  44. mcli/workflow/politician_trading/scrapers_free_sources.py +32 -39
  45. mcli/workflow/politician_trading/scrapers_third_party.py +21 -39
  46. mcli/workflow/politician_trading/seed_database.py +70 -89
  47. {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/METADATA +1 -1
  48. {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/RECORD +56 -54
  49. /mcli/{app → self}/logs_cmd.py +0 -0
  50. /mcli/{app → self}/redis_cmd.py +0 -0
  51. /mcli/{app → self}/visual_cmd.py +0 -0
  52. /mcli/{app → test}/cron_test_cmd.py +0 -0
  53. {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/WHEEL +0 -0
  54. {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/entry_points.txt +0 -0
  55. {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/licenses/LICENSE +0 -0
  56. {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,10 @@
1
1
  """Shared utility functions for dashboard pages"""
2
2
 
3
- import os
4
3
  import logging
4
+ import os
5
5
  import warnings
6
6
  from typing import List, Optional
7
+
7
8
  import pandas as pd
8
9
  import streamlit as st
9
10
  from supabase import Client, create_client
@@ -56,7 +57,9 @@ def get_politician_names() -> List[str]:
56
57
 
57
58
  result = client.table("politicians").select("first_name, last_name").execute()
58
59
  names = [f"{row['first_name']} {row['last_name']}" for row in result.data]
59
- return names if names else ["Nancy Pelosi", "Paul Pelosi", "Dan Crenshaw", "Josh Gottheimer"]
60
+ return (
61
+ names if names else ["Nancy Pelosi", "Paul Pelosi", "Dan Crenshaw", "Josh Gottheimer"]
62
+ )
60
63
  except Exception as e:
61
64
  logger.error(f"Failed to get politician names: {e}")
62
65
  return ["Nancy Pelosi", "Paul Pelosi", "Dan Crenshaw", "Josh Gottheimer"]
@@ -70,11 +73,7 @@ def get_disclosures_data() -> pd.DataFrame:
70
73
 
71
74
  try:
72
75
  # First, get total count
73
- count_response = (
74
- client.table("trading_disclosures")
75
- .select("*", count="exact")
76
- .execute()
77
- )
76
+ count_response = client.table("trading_disclosures").select("*", count="exact").execute()
78
77
  total_count = count_response.count
79
78
 
80
79
  if total_count == 0:
@@ -103,26 +102,30 @@ def get_disclosures_data() -> pd.DataFrame:
103
102
  def _generate_demo_disclosures() -> pd.DataFrame:
104
103
  """Generate demo trading disclosure data for testing"""
105
104
  st.info("🔵 Using demo trading data (Supabase unavailable)")
106
-
105
+
107
106
  import random
108
107
  from datetime import datetime, timedelta
109
-
108
+
110
109
  politicians = ["Nancy Pelosi", "Paul Pelosi", "Dan Crenshaw", "Josh Gottheimer"]
111
110
  tickers = ["AAPL", "MSFT", "GOOGL", "AMZN", "NVDA", "TSLA", "META", "AMD"]
112
111
  transaction_types = ["Purchase", "Sale"]
113
-
112
+
114
113
  data = []
115
114
  for _ in range(50):
116
- data.append({
117
- "politician_name": random.choice(politicians),
118
- "ticker_symbol": random.choice(tickers),
119
- "transaction_type": random.choice(transaction_types),
120
- "amount_min": random.randint(1000, 100000),
121
- "amount_max": random.randint(100000, 1000000),
122
- "disclosure_date": (datetime.now() - timedelta(days=random.randint(1, 365))).strftime("%Y-%m-%d"),
123
- "asset_description": f"{random.choice(tickers)} Stock",
124
- })
125
-
115
+ data.append(
116
+ {
117
+ "politician_name": random.choice(politicians),
118
+ "ticker_symbol": random.choice(tickers),
119
+ "transaction_type": random.choice(transaction_types),
120
+ "amount_min": random.randint(1000, 100000),
121
+ "amount_max": random.randint(100000, 1000000),
122
+ "disclosure_date": (
123
+ datetime.now() - timedelta(days=random.randint(1, 365))
124
+ ).strftime("%Y-%m-%d"),
125
+ "asset_description": f"{random.choice(tickers)} Stock",
126
+ }
127
+ )
128
+
126
129
  return pd.DataFrame(data)
127
130
 
128
131
 
@@ -158,4 +161,4 @@ def get_politician_trading_history(politician_name: str) -> pd.DataFrame:
158
161
 
159
162
  except Exception as e:
160
163
  logger.warning(f"Failed to fetch trading history for {politician_name}: {e}")
161
- return pd.DataFrame()
164
+ return pd.DataFrame()
@@ -1,7 +1,7 @@
1
1
  """Warning suppression utilities for Streamlit components used outside runtime context"""
2
2
 
3
- import warnings
4
3
  import logging
4
+ import warnings
5
5
  from contextlib import contextmanager
6
6
 
7
7
 
@@ -14,12 +14,12 @@ def suppress_streamlit_warnings():
14
14
  warnings.filterwarnings("ignore", message=".*No runtime found.*")
15
15
  warnings.filterwarnings("ignore", message=".*Session state does not function.*")
16
16
  warnings.filterwarnings("ignore", message=".*to view this Streamlit app.*")
17
-
17
+
18
18
  # Also suppress logging warnings from Streamlit
19
19
  streamlit_logger = logging.getLogger("streamlit")
20
20
  original_level = streamlit_logger.level
21
21
  streamlit_logger.setLevel(logging.ERROR)
22
-
22
+
23
23
  try:
24
24
  yield
25
25
  finally:
@@ -28,7 +28,9 @@ def suppress_streamlit_warnings():
28
28
 
29
29
  def suppress_streamlit_warnings_decorator(func):
30
30
  """Decorator to suppress Streamlit warnings for a function"""
31
+
31
32
  def wrapper(*args, **kwargs):
32
33
  with suppress_streamlit_warnings():
33
34
  return func(*args, **kwargs)
34
- return wrapper
35
+
36
+ return wrapper
@@ -1,5 +1,8 @@
1
1
  """Database session management"""
2
2
 
3
+ # Synchronous database setup
4
+ # Prioritize DATABASE_URL environment variable over settings
5
+ import os
3
6
  from contextlib import asynccontextmanager, contextmanager
4
7
  from typing import AsyncGenerator, Generator
5
8
 
@@ -12,9 +15,6 @@ from mcli.ml.config import settings
12
15
 
13
16
  from .models import Base
14
17
 
15
- # Synchronous database setup
16
- # Prioritize DATABASE_URL environment variable over settings
17
- import os
18
18
  database_url = os.getenv("DATABASE_URL")
19
19
 
20
20
  # Check if DATABASE_URL has placeholder password
@@ -53,6 +53,7 @@ if not database_url:
53
53
 
54
54
  # Try to connect to poolers
55
55
  import logging
56
+
56
57
  logger = logging.getLogger(__name__)
57
58
 
58
59
  for pooler_url in pooler_urls:
@@ -61,13 +62,18 @@ if not database_url:
61
62
  test_engine = create_engine(pooler_url, pool_pre_ping=True)
62
63
  with test_engine.connect() as conn:
63
64
  from sqlalchemy import text
65
+
64
66
  conn.execute(text("SELECT 1"))
65
67
  database_url = pooler_url
66
- logger.info(f"Successfully connected via pooler: {pooler_url.split('@')[1].split(':')[0]}")
68
+ logger.info(
69
+ f"Successfully connected via pooler: {pooler_url.split('@')[1].split(':')[0]}"
70
+ )
67
71
  test_engine.dispose()
68
72
  break
69
73
  except Exception as e:
70
- logger.warning(f"Failed to connect via {pooler_url.split('@')[1].split(':')[0]}: {e}")
74
+ logger.warning(
75
+ f"Failed to connect via {pooler_url.split('@')[1].split(':')[0]}: {e}"
76
+ )
71
77
  continue
72
78
 
73
79
  if not database_url:
@@ -75,6 +81,7 @@ if not database_url:
75
81
  database_url = pooler_urls[0]
76
82
 
77
83
  import warnings
84
+
78
85
  warnings.warn(
79
86
  "Using Supabase connection pooler with service role key. "
80
87
  "For better performance, set DATABASE_URL with your actual database password. "
@@ -84,6 +91,7 @@ if not database_url:
84
91
  # Default to SQLite for development/testing
85
92
  database_url = "sqlite:///./ml_system.db"
86
93
  import warnings
94
+
87
95
  warnings.warn(
88
96
  "No database credentials found. Using SQLite fallback. "
89
97
  "Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY or DATABASE_URL in environment."
@@ -91,6 +99,7 @@ if not database_url:
91
99
 
92
100
  # Debug: Log which database URL is being used
93
101
  import logging
102
+
94
103
  logger = logging.getLogger(__name__)
95
104
 
96
105
  if "pooler.supabase.com" in database_url:
@@ -151,6 +160,7 @@ try:
151
160
  except (AttributeError, Exception):
152
161
  # Fallback for async engine
153
162
  import os
163
+
154
164
  async_database_url = os.getenv("ASYNC_DATABASE_URL")
155
165
  if not async_database_url:
156
166
  # Convert sync URL to async if possible
@@ -224,6 +234,7 @@ def get_session() -> Generator[Session, None, None]:
224
234
  session = SessionLocal()
225
235
  # Test the connection
226
236
  from sqlalchemy import text
237
+
227
238
  session.execute(text("SELECT 1"))
228
239
  yield session
229
240
  session.commit()
@@ -20,9 +20,7 @@ import torch
20
20
  from ml.features.ensemble_features import EnsembleFeatureBuilder
21
21
  from ml.features.political_features import PoliticalInfluenceFeatures
22
22
  from ml.features.recommendation_engine import RecommendationConfig as FeatureRecommendationConfig
23
- from ml.features.recommendation_engine import (
24
- StockRecommendationEngine,
25
- )
23
+ from ml.features.recommendation_engine import StockRecommendationEngine
26
24
  from ml.features.stock_features import StockRecommendationFeatures
27
25
  from ml.models.ensemble_models import (
28
26
  DeepEnsembleModel,
@@ -49,9 +49,7 @@ class MonteCarloTradingSimulator:
49
49
  self.final_prices: Optional[np.ndarray] = None
50
50
  self.returns: Optional[np.ndarray] = None
51
51
 
52
- def estimate_parameters(
53
- self, historical_prices: pd.Series
54
- ) -> Tuple[float, float]:
52
+ def estimate_parameters(self, historical_prices: pd.Series) -> Tuple[float, float]:
55
53
  """
56
54
  Estimate drift (μ) and volatility (σ) from historical data
57
55
 
@@ -79,9 +77,7 @@ class MonteCarloTradingSimulator:
79
77
 
80
78
  return annual_return, annual_volatility
81
79
 
82
- def simulate_price_paths(
83
- self, drift: float, volatility: float
84
- ) -> np.ndarray:
80
+ def simulate_price_paths(self, drift: float, volatility: float) -> np.ndarray:
85
81
  """
86
82
  Generate Monte Carlo price paths using Geometric Brownian Motion
87
83
 
@@ -100,9 +96,7 @@ class MonteCarloTradingSimulator:
100
96
  paths[:, 0] = self.initial_price
101
97
 
102
98
  # Generate random shocks
103
- random_shocks = np.random.normal(
104
- 0, 1, size=(self.num_simulations, self.days_to_simulate)
105
- )
99
+ random_shocks = np.random.normal(0, 1, size=(self.num_simulations, self.days_to_simulate))
106
100
 
107
101
  # Simulate paths using Geometric Brownian Motion
108
102
  # S(t+dt) = S(t) * exp((μ - σ²/2)dt + σ√dt * Z)
@@ -110,9 +104,7 @@ class MonteCarloTradingSimulator:
110
104
  drift_component = (drift - 0.5 * volatility**2) * dt
111
105
  shock_component = volatility * np.sqrt(dt) * random_shocks[:, t - 1]
112
106
 
113
- paths[:, t] = paths[:, t - 1] * np.exp(
114
- drift_component + shock_component
115
- )
107
+ paths[:, t] = paths[:, t - 1] * np.exp(drift_component + shock_component)
116
108
 
117
109
  self.simulated_paths = paths
118
110
  self.final_prices = paths[:, -1]
@@ -173,9 +165,7 @@ class MonteCarloTradingSimulator:
173
165
 
174
166
  # Plot sample paths
175
167
  num_to_plot = min(num_paths_to_plot, self.num_simulations)
176
- sample_indices = np.random.choice(
177
- self.num_simulations, num_to_plot, replace=False
178
- )
168
+ sample_indices = np.random.choice(self.num_simulations, num_to_plot, replace=False)
179
169
 
180
170
  for idx in sample_indices:
181
171
  fig.add_trace(
@@ -311,9 +301,7 @@ class MonteCarloTradingSimulator:
311
301
  )
312
302
 
313
303
  # Add vertical line at 0% return
314
- fig.add_vline(
315
- x=0, line_dash="dash", line_color="red", annotation_text="0%", row=1, col=2
316
- )
304
+ fig.add_vline(x=0, line_dash="dash", line_color="red", annotation_text="0%", row=1, col=2)
317
305
 
318
306
  fig.update_xaxes(title_text="Price ($)", row=1, col=1)
319
307
  fig.update_xaxes(title_text="Return (%)", row=1, col=2)
@@ -7,19 +7,19 @@ from decimal import Decimal
7
7
  from typing import Dict, List, Optional, Tuple, Union
8
8
 
9
9
  import pandas as pd
10
+ from alpaca.data.historical import StockHistoricalDataClient
11
+ from alpaca.data.requests import StockBarsRequest
12
+ from alpaca.data.timeframe import TimeFrame
10
13
  from alpaca.trading.client import TradingClient
14
+ from alpaca.trading.enums import OrderSide, OrderStatus, TimeInForce
11
15
  from alpaca.trading.requests import (
12
16
  GetOrdersRequest,
13
- MarketOrderRequest,
14
- LimitOrderRequest,
15
17
  GetPortfolioHistoryRequest,
18
+ LimitOrderRequest,
19
+ MarketOrderRequest,
16
20
  )
17
- from alpaca.trading.enums import OrderSide, TimeInForce, OrderStatus
18
- from alpaca.data.historical import StockHistoricalDataClient
19
- from alpaca.data.requests import StockBarsRequest
20
- from alpaca.data.timeframe import TimeFrame
21
- from pydantic import BaseModel, Field
22
21
  from dotenv import load_dotenv
22
+ from pydantic import BaseModel, Field
23
23
 
24
24
  # Load environment variables
25
25
  load_dotenv()
@@ -29,7 +29,7 @@ logger = logging.getLogger(__name__)
29
29
 
30
30
  class TradingConfig(BaseModel):
31
31
  """Configuration for Alpaca trading"""
32
-
32
+
33
33
  api_key: str = Field(..., description="Alpaca API key")
34
34
  secret_key: str = Field(..., description="Alpaca secret key")
35
35
  base_url: str = Field(default="https://paper-api.alpaca.markets", description="Alpaca base URL")
@@ -39,7 +39,7 @@ class TradingConfig(BaseModel):
39
39
 
40
40
  class Position(BaseModel):
41
41
  """Represents a trading position"""
42
-
42
+
43
43
  symbol: str
44
44
  quantity: int
45
45
  side: str # "long" or "short"
@@ -53,7 +53,7 @@ class Position(BaseModel):
53
53
 
54
54
  class Order(BaseModel):
55
55
  """Represents a trading order"""
56
-
56
+
57
57
  id: str
58
58
  symbol: str
59
59
  side: str # "buy" or "sell"
@@ -68,7 +68,7 @@ class Order(BaseModel):
68
68
 
69
69
  class Portfolio(BaseModel):
70
70
  """Represents portfolio information"""
71
-
71
+
72
72
  equity: float
73
73
  cash: float
74
74
  buying_power: float
@@ -80,19 +80,16 @@ class Portfolio(BaseModel):
80
80
 
81
81
  class AlpacaTradingClient:
82
82
  """Client for Alpaca Trading API operations"""
83
-
83
+
84
84
  def __init__(self, config: TradingConfig):
85
85
  self.config = config
86
86
  self.trading_client = TradingClient(
87
- api_key=config.api_key,
88
- secret_key=config.secret_key,
89
- paper=config.paper_trading
87
+ api_key=config.api_key, secret_key=config.secret_key, paper=config.paper_trading
90
88
  )
91
89
  self.data_client = StockHistoricalDataClient(
92
- api_key=config.api_key,
93
- secret_key=config.secret_key
90
+ api_key=config.api_key, secret_key=config.secret_key
94
91
  )
95
-
92
+
96
93
  def get_account(self) -> Dict:
97
94
  """Get account information"""
98
95
  try:
@@ -101,22 +98,32 @@ class AlpacaTradingClient:
101
98
  # Build response with safe attribute access
102
99
  response = {
103
100
  "account_id": account.id,
104
- "equity": float(account.equity) if hasattr(account, 'equity') else 0.0,
105
- "cash": float(account.cash) if hasattr(account, 'cash') else 0.0,
106
- "buying_power": float(account.buying_power) if hasattr(account, 'buying_power') else 0.0,
107
- "currency": account.currency if hasattr(account, 'currency') else "USD",
108
- "status": account.status.value if hasattr(account.status, 'value') else str(account.status),
109
- "trading_blocked": account.trading_blocked if hasattr(account, 'trading_blocked') else False,
110
- "pattern_day_trader": account.pattern_day_trader if hasattr(account, 'pattern_day_trader') else False,
101
+ "equity": float(account.equity) if hasattr(account, "equity") else 0.0,
102
+ "cash": float(account.cash) if hasattr(account, "cash") else 0.0,
103
+ "buying_power": (
104
+ float(account.buying_power) if hasattr(account, "buying_power") else 0.0
105
+ ),
106
+ "currency": account.currency if hasattr(account, "currency") else "USD",
107
+ "status": (
108
+ account.status.value
109
+ if hasattr(account.status, "value")
110
+ else str(account.status)
111
+ ),
112
+ "trading_blocked": (
113
+ account.trading_blocked if hasattr(account, "trading_blocked") else False
114
+ ),
115
+ "pattern_day_trader": (
116
+ account.pattern_day_trader if hasattr(account, "pattern_day_trader") else False
117
+ ),
111
118
  }
112
119
 
113
120
  # Add optional fields that may not exist in all account types
114
- if hasattr(account, 'portfolio_value'):
121
+ if hasattr(account, "portfolio_value"):
115
122
  response["portfolio_value"] = float(account.portfolio_value)
116
123
  else:
117
124
  response["portfolio_value"] = response["equity"]
118
125
 
119
- if hasattr(account, 'long_market_value'):
126
+ if hasattr(account, "long_market_value"):
120
127
  response["unrealized_pl"] = float(account.long_market_value) - float(account.cash)
121
128
  else:
122
129
  response["unrealized_pl"] = 0.0
@@ -127,7 +134,7 @@ class AlpacaTradingClient:
127
134
  except Exception as e:
128
135
  logger.error(f"Failed to get account info: {e}")
129
136
  raise
130
-
137
+
131
138
  def get_positions(self) -> List[Position]:
132
139
  """Get current positions"""
133
140
  try:
@@ -149,7 +156,7 @@ class AlpacaTradingClient:
149
156
  except Exception as e:
150
157
  logger.error(f"Failed to get positions: {e}")
151
158
  return []
152
-
159
+
153
160
  def get_orders(self, status: Optional[str] = None, limit: int = 100) -> List[Order]:
154
161
  """Get orders with optional status filter"""
155
162
  try:
@@ -173,28 +180,26 @@ class AlpacaTradingClient:
173
180
  except Exception as e:
174
181
  logger.error(f"Failed to get orders: {e}")
175
182
  return []
176
-
183
+
177
184
  def place_market_order(
178
- self,
179
- symbol: str,
180
- quantity: int,
181
- side: str,
182
- time_in_force: str = "day"
185
+ self, symbol: str, quantity: int, side: str, time_in_force: str = "day"
183
186
  ) -> Order:
184
187
  """Place a market order"""
185
188
  try:
186
189
  order_side = OrderSide.BUY if side.lower() == "buy" else OrderSide.SELL
187
- time_in_force_enum = TimeInForce.DAY if time_in_force.lower() == "day" else TimeInForce.GTC
188
-
190
+ time_in_force_enum = (
191
+ TimeInForce.DAY if time_in_force.lower() == "day" else TimeInForce.GTC
192
+ )
193
+
189
194
  order_request = MarketOrderRequest(
190
195
  symbol=symbol,
191
196
  qty=quantity,
192
197
  side=order_side,
193
198
  time_in_force=time_in_force_enum,
194
199
  )
195
-
200
+
196
201
  order = self.trading_client.submit_order(order_request)
197
-
202
+
198
203
  return Order(
199
204
  id=order.id,
200
205
  symbol=order.symbol,
@@ -207,20 +212,17 @@ class AlpacaTradingClient:
207
212
  except Exception as e:
208
213
  logger.error(f"Failed to place market order: {e}")
209
214
  raise
210
-
215
+
211
216
  def place_limit_order(
212
- self,
213
- symbol: str,
214
- quantity: int,
215
- side: str,
216
- limit_price: float,
217
- time_in_force: str = "day"
217
+ self, symbol: str, quantity: int, side: str, limit_price: float, time_in_force: str = "day"
218
218
  ) -> Order:
219
219
  """Place a limit order"""
220
220
  try:
221
221
  order_side = OrderSide.BUY if side.lower() == "buy" else OrderSide.SELL
222
- time_in_force_enum = TimeInForce.DAY if time_in_force.lower() == "day" else TimeInForce.GTC
223
-
222
+ time_in_force_enum = (
223
+ TimeInForce.DAY if time_in_force.lower() == "day" else TimeInForce.GTC
224
+ )
225
+
224
226
  order_request = LimitOrderRequest(
225
227
  symbol=symbol,
226
228
  qty=quantity,
@@ -228,9 +230,9 @@ class AlpacaTradingClient:
228
230
  time_in_force=time_in_force_enum,
229
231
  limit_price=limit_price,
230
232
  )
231
-
233
+
232
234
  order = self.trading_client.submit_order(order_request)
233
-
235
+
234
236
  return Order(
235
237
  id=order.id,
236
238
  symbol=order.symbol,
@@ -243,7 +245,7 @@ class AlpacaTradingClient:
243
245
  except Exception as e:
244
246
  logger.error(f"Failed to place limit order: {e}")
245
247
  raise
246
-
248
+
247
249
  def cancel_order(self, order_id: str) -> bool:
248
250
  """Cancel an order"""
249
251
  try:
@@ -252,12 +254,8 @@ class AlpacaTradingClient:
252
254
  except Exception as e:
253
255
  logger.error(f"Failed to cancel order {order_id}: {e}")
254
256
  return False
255
-
256
- def get_portfolio_history(
257
- self,
258
- period: str = "1M",
259
- timeframe: str = "1Day"
260
- ) -> pd.DataFrame:
257
+
258
+ def get_portfolio_history(self, period: str = "1M", timeframe: str = "1Day") -> pd.DataFrame:
261
259
  """Get portfolio history"""
262
260
  try:
263
261
  # Convert period to start/end dates
@@ -274,78 +272,80 @@ class AlpacaTradingClient:
274
272
  start_date = end_date - timedelta(days=365)
275
273
  else:
276
274
  start_date = end_date - timedelta(days=30)
277
-
275
+
278
276
  # Convert timeframe
279
277
  tf = TimeFrame.Day if timeframe == "1Day" else TimeFrame.Hour
280
-
278
+
281
279
  request = GetPortfolioHistoryRequest(
282
280
  start=start_date,
283
281
  end=end_date,
284
282
  timeframe=tf,
285
283
  )
286
-
284
+
287
285
  history = self.trading_client.get_portfolio_history(request)
288
-
286
+
289
287
  # Convert to DataFrame
290
288
  data = []
291
289
  for i, timestamp in enumerate(history.timestamp):
292
- data.append({
293
- "timestamp": timestamp,
294
- "equity": float(history.equity[i]) if history.equity else 0,
295
- "profit_loss": float(history.profit_loss[i]) if history.profit_loss else 0,
296
- "profit_loss_pct": float(history.profit_loss_pct[i]) if history.profit_loss_pct else 0,
297
- })
298
-
290
+ data.append(
291
+ {
292
+ "timestamp": timestamp,
293
+ "equity": float(history.equity[i]) if history.equity else 0,
294
+ "profit_loss": float(history.profit_loss[i]) if history.profit_loss else 0,
295
+ "profit_loss_pct": (
296
+ float(history.profit_loss_pct[i]) if history.profit_loss_pct else 0
297
+ ),
298
+ }
299
+ )
300
+
299
301
  return pd.DataFrame(data)
300
302
  except Exception as e:
301
303
  logger.error(f"Failed to get portfolio history: {e}")
302
304
  return pd.DataFrame()
303
-
305
+
304
306
  def get_stock_data(
305
- self,
306
- symbols: List[str],
307
- start_date: datetime,
308
- end_date: datetime,
309
- timeframe: str = "1Day"
307
+ self, symbols: List[str], start_date: datetime, end_date: datetime, timeframe: str = "1Day"
310
308
  ) -> pd.DataFrame:
311
309
  """Get historical stock data"""
312
310
  try:
313
311
  tf = TimeFrame.Day if timeframe == "1Day" else TimeFrame.Hour
314
-
312
+
315
313
  request = StockBarsRequest(
316
314
  symbol_or_symbols=symbols,
317
315
  timeframe=tf,
318
316
  start=start_date,
319
317
  end=end_date,
320
318
  )
321
-
319
+
322
320
  bars = self.data_client.get_stock_bars(request)
323
-
321
+
324
322
  # Convert to DataFrame
325
323
  data = []
326
324
  for symbol, bar_list in bars.items():
327
325
  for bar in bar_list:
328
- data.append({
329
- "symbol": symbol,
330
- "timestamp": bar.timestamp,
331
- "open": float(bar.open),
332
- "high": float(bar.high),
333
- "low": float(bar.low),
334
- "close": float(bar.close),
335
- "volume": int(bar.volume),
336
- })
337
-
326
+ data.append(
327
+ {
328
+ "symbol": symbol,
329
+ "timestamp": bar.timestamp,
330
+ "open": float(bar.open),
331
+ "high": float(bar.high),
332
+ "low": float(bar.low),
333
+ "close": float(bar.close),
334
+ "volume": int(bar.volume),
335
+ }
336
+ )
337
+
338
338
  return pd.DataFrame(data)
339
339
  except Exception as e:
340
340
  logger.error(f"Failed to get stock data: {e}")
341
341
  return pd.DataFrame()
342
-
342
+
343
343
  def get_portfolio(self) -> Portfolio:
344
344
  """Get complete portfolio information"""
345
345
  try:
346
346
  account = self.get_account()
347
347
  positions = self.get_positions()
348
-
348
+
349
349
  return Portfolio(
350
350
  equity=account["equity"],
351
351
  cash=account["cash"],
@@ -360,7 +360,9 @@ class AlpacaTradingClient:
360
360
  raise
361
361
 
362
362
 
363
- def create_trading_client(api_key: str = None, secret_key: str = None, paper_trading: bool = True) -> AlpacaTradingClient:
363
+ def create_trading_client(
364
+ api_key: str = None, secret_key: str = None, paper_trading: bool = True
365
+ ) -> AlpacaTradingClient:
364
366
  """
365
367
  Create a trading client with the given credentials or from environment variables
366
368
 
@@ -387,10 +389,7 @@ def create_trading_client(api_key: str = None, secret_key: str = None, paper_tra
387
389
  base_url = os.getenv("ALPACA_BASE_URL", "https://paper-api.alpaca.markets")
388
390
 
389
391
  config = TradingConfig(
390
- api_key=api_key,
391
- secret_key=secret_key,
392
- base_url=base_url,
393
- paper_trading=paper_trading
392
+ api_key=api_key, secret_key=secret_key, base_url=base_url, paper_trading=paper_trading
394
393
  )
395
394
  return AlpacaTradingClient(config)
396
395
 
@@ -413,5 +412,5 @@ def get_alpaca_config_from_env() -> Optional[Dict[str, str]]:
413
412
  "api_key": api_key,
414
413
  "secret_key": secret_key,
415
414
  "base_url": base_url,
416
- "is_paper": "paper" in base_url.lower()
417
- }
415
+ "is_paper": "paper" in base_url.lower(),
416
+ }