Files
Options-SideKick/ios/OptionsSidekick/OptionsSidekick/ViewModels/PositionsViewModel.swift
olsch01 b7d4e900cc Initial implementation of Options Sidekick
Full-stack iOS options trading assistant:
- Python FastAPI backend with SQLite, APScheduler (15-min position monitor),
  APNs push notifications, and yfinance market data integration
- Signal engine: IV Rank (rolling HV proxy), SMA-50/200, swing-based
  support/resistance, earnings detection, signal strength scoring and
  noise-resistant SHA hash for change detection
- Recommendation engine: covered call and cash-secured put strike/expiry
  selection across 0DTE, 1DTE, weekly, and monthly horizons
- REST API: /devices, /portfolio, /recommendations, /positions, /signals, /alerts
- iOS SwiftUI app (iOS 17+): dashboard, recommendations, trades, portfolio,
  and alerts tabs with push notification deep-linking
- Unit + integration tests for signal engine and API layer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:38:25 -04:00

77 lines
2.4 KiB
Swift

import Foundation
@MainActor
final class PositionsViewModel: ObservableObject {
@Published var positions: [OptionPosition] = []
@Published var isLoading = false
@Published var error: String? = nil
var openPositions: [OptionPosition] { positions.filter { $0.status == "open" } }
var closedPositions: [OptionPosition] { positions.filter { $0.status != "open" } }
func load() async {
isLoading = true
error = nil
do {
positions = try await APIClient.shared.request(
.getPositions(status: nil),
body: Optional<String>.none
)
} catch {
self.error = error.localizedDescription
}
isLoading = false
}
func log(create: OptionPositionCreate) async -> Bool {
isLoading = true
error = nil
do {
let new: OptionPosition = try await APIClient.shared.request(.logPosition, body: create)
positions.insert(new, at: 0)
isLoading = false
return true
} catch {
self.error = error.localizedDescription
isLoading = false
return false
}
}
func close(position: OptionPosition, reason: String) async {
isLoading = true
error = nil
let body = OptionPositionClose(status: "closed", closeReason: reason)
do {
let updated: OptionPosition = try await APIClient.shared.request(
.closePosition(position.id),
body: body
)
if let idx = positions.firstIndex(where: { $0.id == position.id }) {
positions[idx] = updated
}
} catch {
self.error = error.localizedDescription
}
isLoading = false
}
func roll(position: OptionPosition) async {
let body = OptionPositionClose(status: "rolled", closeReason: "rolled")
isLoading = true
error = nil
do {
let updated: OptionPosition = try await APIClient.shared.request(
.closePosition(position.id),
body: body
)
if let idx = positions.firstIndex(where: { $0.id == position.id }) {
positions[idx] = updated
}
} catch {
self.error = error.localizedDescription
}
isLoading = false
}
}