From 7b3c5691e17ec71530517dc52e93ce92a05115a0 Mon Sep 17 00:00:00 2001 From: olsch01 Date: Fri, 10 Apr 2026 08:46:33 -0400 Subject: [PATCH] Fix three compiler errors 1. RecommendationEngine: daysToExp used before declaration Move daysToExp/T calculation above selectBestContract() call so the variable exists when it is passed as daysToExpiry parameter. 2. YahooFinanceClient: Swift 6 actor-isolation on Decodable conformances nonisolated methods on an actor still inherit actor isolation in Swift 6. Fix: lift all three decode helpers (yfDecodeChart, yfDecodeOptions, yfMakeContract) to file-scope free functions completely outside the actor. File-scope functions are naturally nonisolated. Also renamed the private JSON model structs to YFChartResponse / YFOptionsResponse to avoid any name collisions now that they live at file scope. 3. LogTradeSheet: unused 'let s = strike' binding Replace 'let s = strike' with 'strike != nil' since the bound value was never referenced inside the if-body. Co-Authored-By: Claude Sonnet 4.6 --- .../Services/RecommendationEngine.swift | 14 ++-- .../Services/YahooFinanceClient.swift | 81 +++++++++---------- .../Views/Positions/LogTradeSheet.swift | 2 +- 3 files changed, 47 insertions(+), 50 deletions(-) diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Services/RecommendationEngine.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Services/RecommendationEngine.swift index fc987b0..e02a39a 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Services/RecommendationEngine.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Services/RecommendationEngine.swift @@ -49,6 +49,13 @@ enum RecommendationEngine { let currentPrice = history.currentPrice guard currentPrice > 0 else { return nil } + // T in years — must be computed before selectBestContract so it can use real DTE + let daysToExp = max( + Calendar.current.dateComponents([.day], from: Date(), to: expiration).day ?? 1, + 1 + ) + let T = Double(daysToExp) / 365.0 + let contracts = strategy == "covered_call" ? chain.calls : chain.puts guard let best = selectBestContract( contracts: contracts, @@ -57,13 +64,6 @@ enum RecommendationEngine { signal: signal, daysToExpiry: daysToExp ) else { return nil } - - // T in years - let daysToExp = max( - Calendar.current.dateComponents([.day], from: Date(), to: expiration).day ?? 1, - 1 - ) - let T = Double(daysToExp) / 365.0 let isCall = strategy == "covered_call" let delta = SignalEngine.bsDelta( diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Services/YahooFinanceClient.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Services/YahooFinanceClient.swift index a76bf71..6095eb4 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Services/YahooFinanceClient.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Services/YahooFinanceClient.swift @@ -13,10 +13,10 @@ struct PriceBar { struct PriceHistory { let ticker: String let bars: [PriceBar] - var closes: [Double] { bars.map(\.close) } - var highs: [Double] { bars.map(\.high) } - var lows: [Double] { bars.map(\.low) } - var currentPrice: Double { bars.last?.close ?? 0 } + var closes: [Double] { bars.map(\.close) } + var highs: [Double] { bars.map(\.high) } + var lows: [Double] { bars.map(\.low) } + var currentPrice: Double { bars.last?.close ?? 0 } } struct OptionContract { @@ -75,18 +75,6 @@ actor YahooFinanceClient { private func set(_ value: Any, key: String) { cache[key] = (value, Date()) } func clearCache() { cache.removeAll() } - // MARK: - nonisolated decode helpers - // Placed outside actor isolation so Swift 6 doesn't flag the Decodable - // conformance as being used in an actor-isolated context. - - nonisolated private func parseChart(_ data: Data) throws -> ChartResponse { - try JSONDecoder().decode(ChartResponse.self, from: data) - } - - nonisolated private func parseOptions(_ data: Data) throws -> OptionsResponse { - try JSONDecoder().decode(OptionsResponse.self, from: data) - } - // MARK: - Price history func priceHistory(ticker: String) async throws -> PriceHistory { @@ -98,7 +86,8 @@ actor YahooFinanceClient { else { throw YFError.badURL } let (data, _) = try await session.data(from: url) - let resp = try parseChart(data) + // Decode outside actor isolation via file-scope free function + let resp = try yfDecodeChart(data) guard let r = resp.chart.result?.first, let q = r.indicators.quote.first else { throw YFError.noData } @@ -137,7 +126,7 @@ actor YahooFinanceClient { else { throw YFError.badURL } let (data, _) = try await session.data(from: url) - let resp = try parseOptions(data) + let resp = try yfDecodeOptions(data) guard let r = resp.optionChain.result?.first else { throw YFError.noData } let dates = r.expirationDates.map { Date(timeIntervalSince1970: Double($0)) } @@ -170,33 +159,19 @@ actor YahooFinanceClient { else { throw YFError.badURL } let (data, _) = try await session.data(from: url) - let resp = try parseOptions(data) + let resp = try yfDecodeOptions(data) guard let r = resp.optionChain.result?.first, let opts = r.options.first else { throw YFError.noData } - let calls = opts.calls.compactMap { makeContract(from: $0) } - let puts = opts.puts .compactMap { makeContract(from: $0) } + let calls = opts.calls.compactMap { yfMakeContract($0) } + let puts = opts.puts .compactMap { yfMakeContract($0) } let chain = OptionChain(expiration: expiration, calls: calls, puts: puts) set(chain, key: key) return chain } - /// nonisolated factory so the OptionsResponse.YFOption type is not - /// referenced inside actor-isolated code (avoids Swift 6 conformance error). - nonisolated private func makeContract(from yf: OptionsResponse.YFOption) -> OptionContract? { - guard let bid = yf.bid, let ask = yf.ask, bid >= 0, ask >= 0 else { return nil } - return OptionContract( - strike: yf.strike, - bid: bid, - ask: ask, - impliedVolatility: yf.impliedVolatility ?? 0.3, - volume: yf.volume ?? 0, - openInterest: yf.openInterest ?? 0 - ) - } - // MARK: - Earnings date func nextEarningsDate(ticker: String) async throws -> Date? { @@ -228,12 +203,35 @@ actor YahooFinanceClient { } } -// MARK: - Private JSON models -// Defined at file scope (outside the actor) as plain Decodable value types. +// MARK: - File-scope free functions (naturally nonisolated — no actor context) +// Swift 6 requires Decodable conformances used in nonisolated code to themselves +// be nonisolated. Placing the decode calls here, outside any actor, satisfies that. -private struct ChartResponse: Decodable { +private func yfDecodeChart(_ data: Data) throws -> YFChartResponse { + try JSONDecoder().decode(YFChartResponse.self, from: data) +} + +private func yfDecodeOptions(_ data: Data) throws -> YFOptionsResponse { + try JSONDecoder().decode(YFOptionsResponse.self, from: data) +} + +private func yfMakeContract(_ yf: YFOptionsResponse.YFOption) -> OptionContract? { + guard let bid = yf.bid, let ask = yf.ask, bid >= 0, ask >= 0 else { return nil } + return OptionContract( + strike: yf.strike, + bid: bid, + ask: ask, + impliedVolatility: yf.impliedVolatility ?? 0.3, + volume: yf.volume ?? 0, + openInterest: yf.openInterest ?? 0 + ) +} + +// MARK: - Private JSON models + +private struct YFChartResponse: Decodable { let chart: Wrapper - struct Wrapper: Decodable { let result: [Result]? } + struct Wrapper: Decodable { let result: [Result]? } struct Result: Decodable { let timestamp: [Int] let indicators: Indicators @@ -247,9 +245,9 @@ private struct ChartResponse: Decodable { } } -private struct OptionsResponse: Decodable { +private struct YFOptionsResponse: Decodable { let optionChain: Wrapper - struct Wrapper: Decodable { let result: [Result]? } + struct Wrapper: Decodable { let result: [Result]? } struct Result: Decodable { let expirationDates: [Int] let options: [OptionData] @@ -258,7 +256,6 @@ private struct OptionsResponse: Decodable { let calls: [YFOption] let puts: [YFOption] } - // YFOption is a direct nested type of OptionsResponse, NOT of OptionData. struct YFOption: Decodable { let strike: Double let bid: Double? diff --git a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Views/Positions/LogTradeSheet.swift b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Views/Positions/LogTradeSheet.swift index 5fce272..b368fce 100644 --- a/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Views/Positions/LogTradeSheet.swift +++ b/ios/OptionsSidekick/OptionsSidekick/OptionsSidekick/OptionsSidekick/Views/Positions/LogTradeSheet.swift @@ -55,7 +55,7 @@ struct LogTradeSheet: View { .focused($focusedField, equals: .contracts) } - if let p = premium, let s = strike, contracts > 0 { + if let p = premium, strike != nil, contracts > 0 { Section("") { HStack { Text("Total credit received")