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>
77 lines
2.4 KiB
Swift
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
|
|
}
|
|
}
|