┌─────────────────────────────────────────────────────────────────────────┐
│ FRONTEND (React/TypeScript) │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ PositionsPage │ │ Position Forms │ │ CSV Upload UI │ │
│ │ (UI Layer) │ │ (Add/Edit) │ │ (Import/Export) │ │
│ └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │ │
│ └─────────────────────┴──────────────────────┘ │
│ │ │
│ ┌────────────▼────────────┐ │
│ │ positionService.ts │ │
│ │ (API Client Layer) │ │
│ └────────────┬────────────┘ │
└─────────────────────────────────┼─────────────────────────────────────┘
│ HTTP/REST
│
┌─────────────────────────────────▼─────────────────────────────────────┐
│ BACKEND (FastAPI/Python) │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ API Layer (FastAPI) │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ position_routes.py │ │ │
│ │ │ • POST /api/positions/stocks │ │ │
│ │ │ • GET /api/positions/stocks │ │ │
│ │ │ • POST /api/positions/import/stocks │ │ │
│ │ │ • GET /api/positions/export/stocks │ │ │
│ │ │ • GET /api/positions/summary │ │ │
│ │ │ • POST /api/positions/enrich │ │ │
│ │ └────────────────────┬───────────────────────────────────┘ │ │
│ └───────────────────────┼──────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────▼──────────────────────────────────────┐ │
│ │ Service Layer │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ ┌────────────┐ │ │
│ │ │ CSVPosition │ │ Position │ │ Position │ │ │
│ │ │ Service │ │ Enrichment │ │ Context │ │ │
│ │ │ (Import/Export) │ │ Service │ │ Service │ │ │
│ │ │ │ │ (Greeks/Metrics) │ │ (AI Agent) │ │ │
│ │ └────────┬─────────┘ └────────┬─────────┘ └─────┬──────┘ │ │
│ │ │ │ │ │ │
│ │ └─────────────────────┴───────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────▼────────────┐ │ │
│ │ │ PositionManager │ │ │
│ │ │ (Core Storage) │ │ │
│ │ └────────────┬────────────┘ │ │
│ └─────────────────────────────────┼─────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────▼─────────────────────────────┐ │
│ │ External Integrations │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │
│ │ │ yfinance │ │ Greeks │ │ Black-Scholes │ │ │
│ │ │ (Market │ │ Calculator │ │ (Option │ │ │
│ │ │ Data) │ │ (Greeks) │ │ Pricing) │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Data Persistence │ │
│ │ ┌──────────────────┐ ┌──────────────────────────────────┐ │ │
│ │ │ positions.json │ │ conversation_memory.json │ │ │
│ │ │ (Position Data) │ │ (AI Conversation History) │ │ │
│ │ └──────────────────┘ └──────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ AI Agent Layer │ │
│ │ ┌──────────────────┐ ┌──────────────────────────────────┐ │ │
│ │ │ Recommendation │ │ Multi-Agent System │ │ │
│ │ │ Engine │ │ (Technical, Fundamental, │ │ │
│ │ │ (6 Scorers) │ │ Sentiment, Risk, Earnings) │ │ │
│ │ └──────────────────┘ └──────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────┘
User Action: Upload CSV File
│
▼
┌────────────────────┐
│ Frontend UI │
│ (File Upload) │
└─────────┬──────────┘
│ FormData with file
▼
┌────────────────────────────────┐
│ API: POST /import/stocks │
│ position_routes.py │
└─────────┬──────────────────────┘
│ CSV content (string)
▼
┌────────────────────────────────┐
│ CSVPositionService │
│ • Parse CSV │
│ • Validate each row │
│ • Check data types │
└─────────┬──────────────────────┘
│ Validated position data
▼
┌────────────────────────────────┐
│ PositionManager │
│ • add_stock_position() │
│ • add_option_position() │
│ • save_positions() │
└─────────┬──────────────────────┘
│ Write to disk
▼
┌────────────────────────────────┐
│ data/positions.json │
│ { │
│ "stocks": {...}, │
│ "options": {...} │
│ } │
└─────────┬──────────────────────┘
│ Trigger enrichment
▼
┌────────────────────────────────┐
│ PositionEnrichmentService │
│ • Fetch real-time prices │
│ • Calculate Greeks │
│ • Compute P&L │
└─────────┬──────────────────────┘
│ Enriched positions
▼
┌────────────────────────────────┐
│ Response to Frontend │
│ { │
│ "success": 3, │
│ "failed": 0, │
│ "position_ids": [...] │
│ } │
└────────────────────────────────┘
Trigger: User clicks "Refresh Data" OR Auto-enrichment on create
│
▼
┌────────────────────────────────┐
│ API: POST /positions/enrich │
│ position_routes.py │
└─────────┬──────────────────────┘
│
▼
┌────────────────────────────────┐
│ PositionEnrichmentService │
│ enrich_all_positions() │
└─────────┬──────────────────────┘
│
├─────────────────────────────────┐
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ For Each Stock: │ │ For Each Option: │
│ │ │ │
│ 1. Fetch from │ │ 1. Fetch from │
│ yfinance │ │ yfinance │
│ ├─ Current price │ │ ├─ Underlying │
│ ├─ PE ratio │ │ ├─ Option chain │
│ ├─ Dividend │ │ ├─ IV │
│ └─ Market cap │ │ └─ Last price │
│ │ │ │
│ 2. Calculate │ │ 2. Calculate │
│ ├─ P&L ($) │ │ ├─ P&L ($) │
│ ├─ P&L (%) │ │ ├─ P&L (%) │
│ └─ Status │ │ ├─ Greeks (BS) │
│ │ │ ├─ Intrinsic val │
│ │ │ ├─ Break-even │
│ │ │ └─ Risk level │
└──────────┬───────────┘ └──────────┬───────────┘
│ │
└───────────┬───────────────────┘
│
▼
┌────────────────────────┐
│ PositionManager │
│ save_positions() │
└────────────────────────┘
│
▼
┌────────────────────────┐
│ data/positions.json │
│ (Updated with │
│ enriched data) │
└────────────────────────┘
User Query: "Should I hold my AAPL calls?"
│
▼
┌────────────────────────────────┐
│ Frontend / API Request │
└─────────┬──────────────────────┘
│
▼
┌────────────────────────────────┐
│ PositionContextService │
│ create_agent_context() │
└─────────┬──────────────────────┘
│
├─────────────────────────────────┐
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ Get Conversation │ │ Get Position Data │
│ History │ │ │
│ ├─ Last 10 messages │ │ ├─ Portfolio summary│
│ ├─ User queries │ │ ├─ AAPL positions │
│ └─ Agent responses │ │ ├─ Enriched metrics │
│ │ │ └─ Greeks, P&L │
└──────────┬───────────┘ └──────────┬───────────┘
│ │
└───────────┬───────────────────┘
│
▼
┌────────────────────────┐
│ Format Context │
│ for AI Agent │
│ │
│ # Recent Conversation │
│ User: ... │
│ │
│ # Portfolio Summary │
│ Total Value: $X │
│ │
│ # AAPL Positions │
│ - Stock: 100 @ $150 │
│ - Call: 20 @ $5.75 │
│ Delta: 0.65 │
│ Days: 36 │
└────────────────────────┘
│
▼
┌────────────────────────┐
│ Pass to AI Agent │
│ (Recommendation │
│ Engine / Multi-Agent)│
└────────────────────────┘
│
▼
┌────────────────────────┐
│ Agent analyzes with │
│ position context: │
│ • Current P&L │
│ • Greeks │
│ • Risk level │
│ • Days to expiry │
│ • Market conditions │
└────────────────────────┘
│
▼
┌────────────────────────┐
│ Agent Response │
│ "Based on your AAPL │
│ calls (Delta 0.65, │
│ 36 days left), I │
│ recommend..." │
└────────────────────────┘
│
▼
┌────────────────────────┐
│ Log Interaction │
│ to Conversation │
│ Memory │
└────────────────────────┘
# 1. User uploads CSV file via frontend
# Frontend: positionService.ts
async importPositions(type: 'stocks' | 'options', file: File) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch(
`${API_BASE_URL}/api/positions/import/${type}`,
{ method: 'POST', body: formData }
);
return response.json();
}
# 2. API receives request
# Backend: position_routes.py
@router.post("/import/stocks", response_model=CSVImportResponse)
async def import_stock_positions(
file: UploadFile = File(...),
replace_existing: bool = Query(False)
):
# Read file content
content = await file.read()
csv_content = content.decode('utf-8')
# Import positions
results = csv_service.import_stock_positions(
csv_content,
replace_existing
)
# Enrich imported positions
if results['success'] > 0:
enrichment_service.enrich_all_positions()
return CSVImportResponse(**results)
# 3. CSV Service validates and imports
# Backend: csv_position_service.py
def import_stock_positions(self, csv_content: str, replace_existing: bool):
results = {'success': 0, 'failed': 0, 'errors': [], 'position_ids': []}
reader = csv.DictReader(io.StringIO(csv_content))
for row_num, row in enumerate(reader, start=2):
# Validate row
is_valid, error_msg = self.validate_stock_row(row, row_num)
if not is_valid:
results['failed'] += 1
results['errors'].append(error_msg)
continue
# Add position
try:
position_id = self.position_manager.add_stock_position(
symbol=row['symbol'].upper(),
quantity=int(row['quantity']),
entry_price=float(row['entry_price']),
# ... other fields
)
results['success'] += 1
results['position_ids'].append(position_id)
except Exception as e:
results['failed'] += 1
results['errors'].append(f"Row {row_num}: {str(e)}")
return results
# 4. Position Manager stores data
# Backend: position_manager.py
def add_stock_position(self, symbol, quantity, entry_price, **kwargs):
position_id = f"STK_{symbol}_{datetime.now().strftime('%Y%m%d%H%M%S')}"
position = StockPosition(
symbol=symbol.upper(),
quantity=quantity,
entry_price=entry_price,
position_id=position_id,
**kwargs
)
self.stock_positions[position_id] = position
self.save_positions() # Write to data/positions.json
return position_id# Backend: position_enrichment_service.py
def enrich_option_position(self, position: OptionPosition) -> OptionPosition:
# 1. Get underlying stock data
market_data = self.get_stock_data(position.symbol)
underlying_price = market_data.get('current_price')
hv = market_data.get('historical_volatility', 0.3)
# 2. Get option chain data from yfinance
option_data = self.get_option_chain_data(
position.symbol,
position.expiration_date,
position.strike,
position.option_type
)
# 3. If no option data, estimate using Black-Scholes
if not option_data and underlying_price:
bs_price = black_scholes.black_scholes_price(
option_type=position.option_type,
underlying_price=underlying_price,
strike=position.strike,
time_to_expiry=position.time_to_expiry(),
volatility=hv,
risk_free_rate=self.get_risk_free_rate()
)
option_data = {
'last_price': bs_price,
'implied_volatility': hv
}
# 4. Calculate metrics
position.calculate_metrics(market_data, option_data)
# 5. Calculate Greeks
if position.implied_volatility and underlying_price:
greeks = self.calculate_option_greeks(
underlying_price=underlying_price,
strike=position.strike,
time_to_expiry=position.time_to_expiry(),
volatility=position.implied_volatility,
option_type=position.option_type
)
position.delta = greeks.get('delta')
position.gamma = greeks.get('gamma')
position.theta = greeks.get('theta')
position.vega = greeks.get('vega')
position.rho = greeks.get('rho')
# 6. Calculate probability of profit
if position.delta:
if position.option_type.lower() == 'call':
position.probability_of_profit = abs(position.delta) * 100
else:
position.probability_of_profit = (1 - abs(position.delta)) * 100
return position# Backend: position_context_service.py
def create_agent_context(
self,
conversation_id: str,
include_summary: bool = True,
include_positions: bool = False,
symbol: str = None
) -> str:
context_parts = []
# 1. Add conversation history
recent_messages = self.conversation_memory.get_conversation(
conversation_id,
limit=10
)
if recent_messages:
context_parts.append("# Recent Conversation\n")
for msg in recent_messages[-5:]:
role = msg['role'].capitalize()
content = msg['content'][:200]
context_parts.append(f"**{role}**: {content}\n")
# 2. Add portfolio summary
if include_summary:
summary = self.enrichment_service.get_enriched_portfolio_summary()
context_parts.append(f"""
# Portfolio Summary
**Total Positions**: {summary['total_stocks']} stocks, {summary['total_options']} options
**Total Value**: ${summary.get('total_current_value', 0):,.2f}
**Total P&L**: ${summary.get('total_pnl', 0):,.2f} ({summary.get('total_pnl_pct', 0):.2f}%)
""")
# 3. Add detailed positions
if include_positions:
if symbol:
positions = self.position_manager.get_positions_by_symbol(symbol)
else:
positions = {
'stocks': self.position_manager.get_all_stock_positions(),
'options': self.position_manager.get_all_option_positions()
}
# Format positions for agent
for pos in positions['options']:
self.enrichment_service.enrich_option_position(pos)
context_parts.append(f"""
### {pos.symbol} ${pos.strike} {pos.option_type.upper()}
- **P&L**: ${pos.unrealized_pnl:.2f} ({pos.unrealized_pnl_pct:.2f}%)
- **Delta**: {pos.delta:.3f}, **Theta**: {pos.theta:.3f}
- **Days to Expiry**: {pos.days_to_expiry()}
- **Risk Level**: {pos.get_risk_level()}
""")
return "\n".join(context_parts)
# Usage in recommendation engine
from src.agents.position_context_service import PositionContextService
context_service = PositionContextService()
# Create context for agent
context = context_service.create_agent_context(
conversation_id="user_123_session_1",
include_summary=True,
include_positions=True,
symbol="AAPL"
)
# Pass to recommendation engine
recommendation = recommendation_engine.analyze_with_context(
symbol="AAPL",
context=context
)
# Log interaction
context_service.log_agent_interaction(
conversation_id="user_123_session_1",
user_query="Should I hold my AAPL calls?",
agent_response=recommendation,
positions_accessed=["OPT_AAPL_CALL_180_20251121"]
)# csv_position_service.py
class CSVPositionService:
def __init__(self, position_manager: PositionManager):
self.position_manager = position_manager # Dependency injection
def import_stock_positions(self, csv_content: str):
# Validates CSV, then calls position_manager
position_id = self.position_manager.add_stock_position(...)
return results# position_enrichment_service.py
class PositionEnrichmentService:
def __init__(self, position_manager: PositionManager):
self.position_manager = position_manager
def enrich_all_positions(self):
# Gets positions from manager
for position in self.position_manager.get_all_stock_positions():
self.enrich_stock_position(position)
# Saves back to manager
self.position_manager.save_positions()# position_context_service.py
class PositionContextService:
def __init__(
self,
position_manager: PositionManager = None,
enrichment_service: PositionEnrichmentService = None,
conversation_memory: ConversationMemory = None
):
self.position_manager = position_manager or PositionManager()
self.enrichment_service = enrichment_service or \
PositionEnrichmentService(self.position_manager)
self.conversation_memory = conversation_memory or ConversationMemory()# position_routes.py
from ..data.position_manager import PositionManager
from ..data.csv_position_service import CSVPositionService
from ..data.position_enrichment_service import PositionEnrichmentService
# Initialize services (singleton pattern)
position_manager = PositionManager()
csv_service = CSVPositionService(position_manager)
enrichment_service = PositionEnrichmentService(position_manager)
@router.post("/import/stocks")
async def import_stock_positions(file: UploadFile):
# Uses csv_service
results = csv_service.import_stock_positions(csv_content)
# Uses enrichment_service
if results['success'] > 0:
enrichment_service.enrich_all_positions()
return results1. FRONTEND REQUEST
┌─────────────────────────────────────┐
│ User clicks "Import CSV" │
│ Selects file: stock_positions.csv │
└─────────────────┬───────────────────┘
│
┌─────────────────▼───────────────────┐
│ positionService.ts │
│ importPositions('stocks', file) │
│ │
│ FormData: │
│ file: <File object> │
└─────────────────┬───────────────────┘
│ HTTP POST
│ multipart/form-data
2. API ENDPOINT │
┌─────────────────▼───────────────────┐
│ POST /api/positions/import/stocks │
│ position_routes.py │
│ │
│ async def import_stock_positions( │
│ file: UploadFile, │
│ replace_existing: bool = False │
│ ) │
└─────────────────┬───────────────────┘
│
3. READ FILE │
┌─────────────────▼───────────────────┐
│ content = await file.read() │
│ csv_content = content.decode('utf-8')│
│ │
│ CSV Content: │
│ "symbol,quantity,entry_price,... │
│ AAPL,100,150.50,..." │
└─────────────────┬───────────────────┘
│
4. CSV SERVICE │
┌─────────────────▼───────────────────┐
│ csv_service.import_stock_positions( │
│ csv_content, │
│ replace_existing │
│ ) │
│ │
│ • Parse CSV with csv.DictReader │
│ • Validate each row │
│ • Check data types │
└─────────────────┬───────────────────┘
│
5. POSITION MANAGER │
┌─────────────────▼───────────────────┐
│ For each valid row: │
│ position_manager.add_stock_position(│
│ symbol='AAPL', │
│ quantity=100, │
│ entry_price=150.50 │
│ ) │
│ │
│ • Create StockPosition object │
│ • Generate position_id │
│ • Add to self.stock_positions dict │
└─────────────────┬───────────────────┘
│
6. SAVE TO DISK │
┌─────────────────▼───────────────────┐
│ position_manager.save_positions() │
│ │
│ Write to: data/positions.json │
│ { │
│ "stocks": { │
│ "STK_AAPL_20251016...": { │
│ "symbol": "AAPL", │
│ "quantity": 100, │
│ "entry_price": 150.50 │
│ } │
│ } │
│ } │
└─────────────────┬───────────────────┘
│
7. ENRICHMENT │
┌─────────────────▼───────────────────┐
│ enrichment_service.enrich_all_ │
│ positions() │
│ │
│ For each position: │
│ • Fetch from yfinance │
│ • Calculate Greeks │
│ • Compute P&L │
│ • Update position object │
│ • Save back to positions.json │
└─────────────────┬───────────────────┘
│
8. API RESPONSE │
┌─────────────────▼───────────────────┐
│ return CSVImportResponse( │
│ success=3, │
│ failed=0, │
│ errors=[], │
│ position_ids=[ │
│ "STK_AAPL_...", │
│ "STK_NVDA_...", │
│ "STK_MSFT_..." │
│ ] │
│ ) │
└─────────────────┬───────────────────┘
│ HTTP 200 OK
│ application/json
9. FRONTEND RESPONSE │
┌─────────────────▼───────────────────┐
│ positionService.ts receives: │
│ { │
│ success: 3, │
│ failed: 0, │
│ errors: [], │
│ position_ids: [...] │
│ } │
└─────────────────┬───────────────────┘
│
10. UI UPDATE │
┌─────────────────▼───────────────────┐
│ PositionsPage.tsx │
│ • Show success snackbar │
│ • Reload positions list │
│ • Update portfolio summary │
└─────────────────────────────────────┘
src/api/main.py
├── imports: src/api/position_routes.py
│ ├── imports: src/data/position_manager.py
│ ├── imports: src/data/csv_position_service.py
│ │ └── imports: src/data/position_manager.py
│ └── imports: src/data/position_enrichment_service.py
│ ├── imports: src/data/position_manager.py
│ ├── imports: src/analytics/greeks_calculator.py
│ └── imports: src/analytics/black_scholes.py
│
└── imports: src/agents/position_context_service.py
├── imports: src/data/position_manager.py
└── imports: src/data/position_enrichment_service.py
frontend/src/pages/PositionsPage.tsx
└── imports: frontend/src/services/positionService.ts
# src/api/main.py
from .position_routes import router as position_router
app.include_router(position_router)
# src/api/position_routes.py
from ..data.position_manager import PositionManager
from ..data.csv_position_service import CSVPositionService
from ..data.position_enrichment_service import PositionEnrichmentService
# src/data/csv_position_service.py
from .position_manager import PositionManager, StockPosition, OptionPosition
# src/data/position_enrichment_service.py
from .position_manager import PositionManager, StockPosition, OptionPosition
from ..analytics.greeks_calculator import GreeksCalculator
from ..analytics import black_scholes
# src/agents/position_context_service.py
from ..data.position_manager import PositionManager
from ..data.position_enrichment_service import PositionEnrichmentServiceThe Position Management System is independent of Playwright. Playwright was used in the Chase integration attempt (which we moved away from), but the current system uses:
- yfinance for market data (not Playwright)
- FastAPI for API endpoints (not Playwright)
- React for frontend (not Playwright)
- CSV files for data import/export (not Playwright)
Playwright is NOT required for this system to work.
However, if you want to use Playwright for testing the frontend UI, here's how:
# test_ui_with_playwright.py
from playwright.sync_api import sync_playwright
def test_position_import_ui():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
# Navigate to positions page
page.goto("http://localhost:3000/positions")
# Click import button
page.click('button:has-text("Import CSV")')
# Upload file
page.set_input_files('input[type="file"]', 'stock_positions.csv')
# Click import
page.click('button:has-text("Import")')
# Wait for success message
page.wait_for_selector('text=Successfully imported')
browser.close()# Example: Multi-conversation tracking
# Session 1: User asks about AAPL
context_service.log_agent_interaction(
conversation_id="user_123_session_1",
user_query="What's my AAPL position?",
agent_response="You have 100 shares at $150.50, up 65%",
positions_accessed=["STK_AAPL_20251015120000"]
)
# Session 2: User asks follow-up (different day)
context = context_service.create_agent_context(
conversation_id="user_123_session_1", # Same conversation_id
include_summary=True
)
# Context includes previous conversation:
# """
# # Recent Conversation
# **User**: What's my AAPL position?
# **Assistant**: You have 100 shares at $150.50, up 65%
# **User**: Should I sell now?
# """
# Agent has full context from previous sessionWhere to find results:
- Architecture Diagram: See ASCII diagrams above
- Data Flow: See flow diagrams for CSV import, enrichment, AI integration
- Code Examples: See practical snippets for each integration point
- API Flow: See complete request/response trace
- Dependencies: See module import graph
- Playwright Note: System works without Playwright (uses yfinance instead)