🤣

class ViewController: UIViewController {
    
        @IBOutlet weak var tickerTextField: UITextField!
        
        @IBOutlet weak var stochasticLabel: UILabel!
        @IBOutlet weak var ichimokuLabel: UILabel!
        @IBOutlet weak var bollingerBandsLabel: UILabel!
        @IBOutlet weak var movingAveragesLabel: UILabel!
        @IBOutlet weak var obvLabel: UILabel!
        @IBOutlet weak var fibonacciLabel: UILabel!
        @IBOutlet weak var rsiLabel: UILabel!
        @IBOutlet weak var adxLabel: UILabel!
        @IBOutlet weak var adlLabel: UILabel!
        @IBOutlet weak var macdLabel: UILabel!
           
        let errorMessageLabel = UILabel()
        let closeButton = UIButton(type: .system)
        let apiKey = "1RA8WZLKFJNH7B8H"
        var symbol = "AAPL" // Default to Apple, can be updated via search
        let baseUrl = "https://www.alphavantage.co/query"
        var isFetchingData = false
        var previousObv: Double? // To store the previous OBV value
    // Declare your arrays for price and volume data
     var closingPrices: [Double] = []
     var highPrices: [Double] = []
     var lowPrices: [Double] = []
     var volumes: [Double] = []

     // Moving Averages
     var shortTermMA: Double = 0.0
     var longTermMA: Double = 0.0

     // MACD-related data
     var macd: Double = 0.0
     var signalLine: Double = 0.0
     var macdBuySignal: Bool = false
     var macdSellSignal: Bool = false
     var macdHoldSignal: Bool = false

     // RSI-related data
     var rsi: Double = 0.0
     var rsiBuySignal: Bool = false
     var rsiSellSignal: Bool = false
     var rsiHoldSignal: Bool = false

     // OBV-related data
     var obv: Double = 0.0
     var obvBuySignal: Bool = false
     var obvSellSignal: Bool = false
     var obvHoldSignal: Bool = false

     // ADL-related data
     var adl: Double = 0.0
     var adlBuySignal: Bool = false
     var adlSellSignal: Bool = false
     var adlHoldSignal: Bool = false

     // Bollinger Bands-related data
     var upperBand: Double = 0.0
     var middleBand: Double = 0.0
     var lowerBand: Double = 0.0
     var bbBuySignal: Bool = false
     var bbSellSignal: Bool = false
     var bbHoldSignal: Bool = false

     // ADX-related data
     var adx: Double = 0.0
     var adxBuySignal: Bool = false
     var adxSellSignal: Bool = false
     var adxHoldSignal: Bool = false

     // Fibonacci Retracement-related data
     var fibBuySignal: Bool = false
     var fibSellSignal: Bool = false
     var fibHoldSignal: Bool = false

     // Ichimoku Cloud-related data
     var ichimokuBuySignal: Bool = false
     var ichimokuSellSignal: Bool = false
     var ichimokuHoldSignal: Bool = false

     // Stochastic-related data
     var stochasticK: Double = 0.0
     var stochasticD: Double = 0.0

        
        override func viewDidLoad() {
            super.viewDidLoad()
           
            setupErrorMessageLabel()
             setupCloseButton()
         }
         
         func setupErrorMessageLabel() {
             errorMessageLabel.translatesAutoresizingMaskIntoConstraints = false
                     errorMessageLabel.numberOfLines = 0
                     errorMessageLabel.lineBreakMode = .byWordWrapping
                     errorMessageLabel.textColor = .red
                     errorMessageLabel.textAlignment = .center
                     errorMessageLabel.isHidden = true
                     errorMessageLabel.backgroundColor = .white
                     errorMessageLabel.layer.cornerRadius = 10
                     errorMessageLabel.layer.masksToBounds = true
                     
                     view.addSubview(errorMessageLabel)
                     
                     // Set constraints for the error message label to be centered and square
                     NSLayoutConstraint.activate([
                         errorMessageLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                         errorMessageLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
                         errorMessageLabel.widthAnchor.constraint(equalToConstant: 300), // Set a fixed width (square size)
                         errorMessageLabel.heightAnchor.constraint(equalTo: errorMessageLabel.widthAnchor) // Make it square
                     ])
                 }
         
         func setupCloseButton() {
             closeButton.translatesAutoresizingMaskIntoConstraints = false
             closeButton.setTitle("X", for: .normal)
             closeButton.addTarget(self, action: #selector(hideErrorMessage), for: .touchUpInside)
             closeButton.isHidden = true
             view.addSubview(closeButton)
             
             NSLayoutConstraint.activate([
                         closeButton.topAnchor.constraint(equalTo: errorMessageLabel.topAnchor, constant: 8),
                         closeButton.trailingAnchor.constraint(equalTo: errorMessageLabel.trailingAnchor, constant: -8)
             ])
         }
         
         func updateErrorMessage(_ message: String) {
             errorMessageLabel.text = message
             errorMessageLabel.isHidden = false
             closeButton.isHidden = false
             view.bringSubviewToFront(errorMessageLabel)
             view.bringSubviewToFront(closeButton)
         }
         
         @objc func hideErrorMessage() {
             errorMessageLabel.isHidden = true
             closeButton.isHidden = true
         }
        
        // Function triggered by search button tap
        @IBAction func searchButtonTapped(_ sender: UIButton) {
            guard let symbolText = tickerTextField.text, !symbolText.isEmpty else {
                showError(message: "Please enter a valid stock symbol.")
                return
            }
            
            symbol = symbolText.uppercased()
            
            if isFetchingData {
                return // Prevent multiple fetch requests while one is in progress
            }
            
            fetchStockData(ticker: newTicker) { [weak self] stockData, error in
                guard let self = self else { return }

                // Handle error
                if let error = error {
                    // Assuming `processError` is a method that displays errors in your app.
                    self.processError(message: error.localizedDescription) // Use your actual error handling method here
                } else if let stockData = stockData {
                    // Process the received stock data
                    self.processStockData(stockData) // Make sure `processStockData` is the correct method to process data
                }
            }
    // Function to fetch stock data from Alpha Vantage
    func fetchStockData(ticker: String, completion: @escaping ([String: DailyAdjustedData]?, Error?) -> Void) {
        let urlString = "\(baseUrl)?function=TIME_SERIES_DAILY_ADJUSTED&symbol=\(ticker)&apikey=\(apiKey)"
        
        // Print the URL string for debugging
        print("URL String: \(urlString)")
        
        guard let url = URL(string: urlString) else {
            completion(nil, NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]))
            return
        }
        
        isFetchingData = true // Ensure this is set to true to avoid multiple fetch requests

        URLSession.shared.dataTask(with: url) { data, response, error in
            defer { self.isFetchingData = false } // Ensure it's reset to false when the task finishes
            
            if let error = error {
                print("Network error: \(error)")
                completion(nil, error)
                return
            }
            
            // Check for HTTP response status code
            if let httpResponse = response as? HTTPURLResponse {
                print("HTTP Status Code: \(httpResponse.statusCode)")
                guard (200...299).contains(httpResponse.statusCode) else {
                    completion(nil, NSError(domain: "", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: "Server returned an error"]))
                    return
                }
            }
            
            guard let data = data else {
                completion(nil, NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "No data received."]))
                return
            }
            
            // Print the raw JSON response for debugging
            if let jsonString = String(data: data, encoding: .utf8) {
                print("Raw JSON Response: \(jsonString)")
            }
            
            let decoder = JSONDecoder()
            do {
                let response = try decoder.decode(TimeSeriesDailyAdjusted.self, from: data)
                
                if let errorMessage = response.errorMessage {
                    completion(nil, NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: errorMessage]))
                } else {
                    completion(response.timeSeries, nil)
                    self.cacheStockData(stockData: response.timeSeries)
                }
            } catch let decodingError as DecodingError {
                self.handleDecodingError(decodingError)
                completion(nil, NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to decode response."]))
            } catch {
                print("Unexpected error: \(error)")
                completion(nil, error)
            }
        }.resume()
    }

            func handleDecodingError(_ error: DecodingError) {
                switch error {
                case .dataCorrupted(let context):
                    print("Data corrupted: \(context.debugDescription)")
                case .keyNotFound(let key, let context):
                    print("Key \(key) not found: \(context.debugDescription)")
                case .typeMismatch(let type, let context):
                    print("Type mismatch for type \(type): \(context.debugDescription)")
                case .valueNotFound(let type, let context):
                    print("Value not found for type \(type): \(context.debugDescription)")
                @unknown default:
                    print("Unknown decoding error: \(error)")
                }
            }
        // Function to process fetched stock data and calculate indicators
    func processStockData(_ stockData: [String: DailyAdjustedData]) {
        // Ensure there is data to process
        guard !stockData.isEmpty else {
            print("No stock data available to process.")
            return
        }

        // Convert optional String? values to Double, defaulting to 0.0 if conversion fails
        let closingPrices = stockData.values.compactMap { Double($0.close) }
        let highPrices = stockData.values.compactMap { Double($0.high) }
        let lowPrices = stockData.values.compactMap { Double($0.low) }
        let volumes = stockData.values.compactMap { Double($0.volume) }
        _ = stockData.values.compactMap { Double($0.open) }
        let adjustedClosingPrices = stockData.values.compactMap { Double($0.adjustedClose) }

        // Check if closingPrices and adjustedClosingPrices are consistent
        guard closingPrices.count == adjustedClosingPrices.count else {
            print("Mismatch in closing prices and adjusted closing prices count.")
            return
        }

        // Sort data by date (if needed)
        _ = stockData.sorted { $0.key < $1.key }

        // Proceed with your calculations
        let shortTermMA = calculateMovingAverage(prices: closingPrices, period: 50)
        let longTermMA = calculateMovingAverage(prices: closingPrices, period: 200)
        updateMovingAveragesLabel(shortTermMA: shortTermMA, longTermMA: longTermMA)

        // MACD Calculation
        let (macd, signalLine, macdBuySignal, macdSellSignal, macdHoldSignal) = calculateMacd(prices: closingPrices)
        updateMacdLabel(macd: macd, signalLine: signalLine, buySignal: macdBuySignal, sellSignal: macdSellSignal, holdSignal: macdHoldSignal)

        // RSI Calculation
        let (rsi, rsiBuySignal, rsiSellSignal, rsiHoldSignal) = calculateRsi(prices: closingPrices)
        updateRsiLabel(rsi: rsi, buySignal: rsiBuySignal, sellSignal: rsiSellSignal, holdSignal: rsiHoldSignal)

        // OBV Calculation
        let (obv, _, _, _) = calculateObv(prices: closingPrices, volumes: volumes)
        let previousPrice = closingPrices.dropLast().last ?? 0.0
        updateObvLabel(obv: obv, price: closingPrices.last ?? 0.0, previousPrice: previousPrice)

        // ADL Calculation
        let (adl, _, _, _) = calculateAdl(prices: closingPrices, highPrices: highPrices, lowPrices: lowPrices, volumes: volumes)
        updateAdlLabel(adl: adl)

        // Bollinger Bands Calculation
        let (upperBand, _, lowerBand, _, _, _) = calculateBollingerBandsWithSignals(prices: closingPrices)
        updateBollingerBandsLabel(currentPrice: closingPrices.last ?? 0.0, upperBand: upperBand, lowerBand: lowerBand)

        // ADX Calculation
        let (adx, _, _, _) = calculateAdxWithSignals(prices: closingPrices, highPrices: highPrices, lowPrices: lowPrices)
        updateAdxLabel(adx: adx)

        // Fibonacci Retracement Calculation
        let (fibRetracementLevel, _, _, _) = calculateFibonacciRetracementWithSignals(prices: closingPrices)
        updateFibonacciLabel(price: closingPrices.last ?? 0.0, fibRetracementLevel: fibRetracementLevel)

        // Ichimoku Cloud Calculation
        let (_, _, _) = calculateIchimokuCloudWithSignals(highPrices: highPrices, lowPrices: lowPrices, closePrices: closingPrices)
        updateIchimokuLabel(currentPrice: closingPrices.last ?? 0.0, tenkanSen: 0.0, kijunSen: 0.0, priceAboveCloud: true) // Adjust accordingly

        // Stochastic Calculation
        let (stochasticK, stochasticD) = calculateStochastic(prices: closingPrices, highPrices: highPrices, lowPrices: lowPrices)
        updateStochasticLabel(stochasticK: stochasticK, stochasticD: stochasticD)
    }
        func showError(message: String) {
            DispatchQueue.main.async {
                self.errorMessageLabel.isHidden = false
                self.errorMessageLabel.text = message
            }
        }
    struct CachedStockData: Codable {
        let data: [String: DailyAdjustedData]
        let timestamp: Date
    }

    // Global variable to track when new data is fetched
    var isNewDataFetched = false

    func loadCachedStockData() -> [String: DailyAdjustedData]? {
        guard let savedData = UserDefaults.standard.object(forKey: "cachedStockData") as? Data else { return nil }
        let decoder = JSONDecoder()
        do {
            let loadedData = try decoder.decode([String: DailyAdjustedData].self, from: savedData)
            
            // Check if the market is open (or if a certain time has passed since last data fetch)
            if let lastUpdated = UserDefaults.standard.object(forKey: "lastUpdated") as? Date {
                let currentTime = Date()
                let timeDifference = currentTime.timeIntervalSince(lastUpdated)
                
                // If data was fetched more than X minutes ago, allow a re-fetch
                if timeDifference < 60 * 10 { // 10 minutes, adjust as needed
                    isNewDataFetched = false // No new data, don't trigger recalculations
                    return loadedData // Use cached data
                }
            }
            
            // Set flag when new data is fetched
            isNewDataFetched = true
            
            return loadedData // Use cached data if no recent update found
        } catch {
            print("Failed to decode stock data: \(error)")
            return nil
        }
    }

    func cacheStockData(stockData: [String: DailyAdjustedData]) {
        let encoder = JSONEncoder()
        do {
            // Include a timestamp to track when the data was cached
            let dataWithTimestamp = CachedStockData(data: stockData, timestamp: Date())
            let encoded = try encoder.encode(dataWithTimestamp)
            UserDefaults.standard.set(encoded, forKey: "cachedStockData")
            UserDefaults.standard.set(Date(), forKey: "lastUpdated") // Store the timestamp of the cache
            
            // Set flag to indicate new data was fetched and cached
            isNewDataFetched = true
        } catch {
            print("Failed to encode stock data: \(error)")
        }
    }
    
    // Function to check if recalculations are needed
    func shouldRecalculateIndicators() -> Bool {
        return isNewDataFetched // Only recalculate if new data was fetched
    }
    
    func resetData() {
        // Resetting price data
        closingPrices = []
        highPrices = []
        lowPrices = []
        volumes = []
        
        // Resetting moving averages
        shortTermMA = 0.0
        longTermMA = 0.0
        
        // Resetting MACD-related data
        macd = 0.0
        signalLine = 0.0
        macdBuySignal = false
        macdSellSignal = false
        macdHoldSignal = false
        
        // Resetting RSI-related data
        rsi = 0.0
        rsiBuySignal = false
        rsiSellSignal = false
        rsiHoldSignal = false
        
        // Resetting OBV-related data
        obv = 0.0
        obvBuySignal = false
        obvSellSignal = false
        obvHoldSignal = false
        
        // Resetting ADL-related data
        adl = 0.0
        adlBuySignal = false
        adlSellSignal = false
        adlHoldSignal = false
        
        // Resetting Bollinger Bands-related data
        upperBand = 0.0
        middleBand = 0.0
        lowerBand = 0.0
        bbBuySignal = false
        bbSellSignal = false
        bbHoldSignal = false
        
        // Resetting ADX-related data
        adx = 0.0
        adxBuySignal = false
        adxSellSignal = false
        adxHoldSignal = false
        
        // Resetting Fibonacci Retracement-related data
        fibBuySignal = false
        fibSellSignal = false
        fibHoldSignal = false
        
        // Resetting Ichimoku Cloud-related data
        ichimokuBuySignal = false
        ichimokuSellSignal = false
        ichimokuHoldSignal = false
        
        // Resetting Stochastic-related data
        stochasticK = 0.0
        stochasticD = 0.0
    }
        
    func handleTickerChange(newTicker: String, completion: @escaping ([String: DailyAdjustedData]?, Error?) -> Void) {
        resetData() // Reset any previous data
        
        fetchStockData(ticker: newTicker) { data, error in
            if let error = error {
                completion(nil, error)  // Pass error to completion handler
            } else if let data = data {
                completion(data, nil)  // Pass the fetched data to completion handler
            }
        }
    }
    
    @IBAction func tickerInputChanged(_ sender: UITextField) {
        guard let newTicker = sender.text else { return }
        
        handleTickerChange(newTicker: newTicker) { data, error in
            if let error = error {
                print("Error fetching data: \(error)")
                // Handle error (e.g., show alert)
            } else if let data = data {
                print("Fetched data: \(data)")
                // Process the fetched data (e.g., update UI)
            }
        }
    }

        // **Helper Functions for Stock Indicators**
    func calculateStochastic(prices: [Double], highPrices: [Double], lowPrices: [Double], period: Int = 14) -> (stochasticK: Double, stochasticD: Double) {
        
        guard shouldRecalculateIndicators() else { return (0.0, 0.0) }
        
        guard prices.count >= period, highPrices.count >= period, lowPrices.count >= period else {
            return (stochasticK: 0.0, stochasticD: 0.0) // Return 0 if there aren't enough data points
        }
        
        // Calculate the highest high and the lowest low over the period
        let recentHighs = highPrices.suffix(period)
        let recentLows = lowPrices.suffix(period)
        let recentCloses = prices.suffix(period)
        
        let highestHigh = recentHighs.max() ?? 0.0
        let lowestLow = recentLows.min() ?? 0.0
        let currentClose = recentCloses.last ?? 0.0
        
        // Calculate %K (the Fast Stochastic)
        let stochasticK = (highestHigh - lowestLow) != 0 ? (currentClose - lowestLow) / (highestHigh - lowestLow) * 100 : 0.0
        
        // Calculate %D (the Slow Stochastic) as the moving average of %K over the last 3 periods
        let kValues = recentCloses.enumerated().map { (i, close) -> Double in
            guard i < recentHighs.count, i < recentLows.count else { return 0.0 }
            let high = recentHighs[i]
            let low = recentLows[i]
            return (high - low) != 0 ? (close - low) / (high - low) * 100 : 0.0
        }
        
        // Ensure at least 3 values are available for calculating %D
        let dPeriod = min(3, kValues.count)
        let stochasticD = dPeriod > 0 ? kValues.suffix(dPeriod).reduce(0, +) / Double(dPeriod) : 0.0
        
        return (stochasticK, stochasticD)
    }

    func calculateMovingAverage(prices: [Double], period: Int) -> Double {
            
            guard shouldRecalculateIndicators() else { return 0.0 }
            
            guard prices.count >= period else { return 0.0 }
            let slice = prices.suffix(period)
            let sum = slice.reduce(0, +)
            return sum / Double(period)
        }

    func calculateMacd(prices: [Double], shortTermPeriod: Int = 12, longTermPeriod: Int = 26, signalPeriod: Int = 9) -> (macd: Double, signalLine: Double, buySignal: Bool, sellSignal: Bool, holdSignal: Bool) {
        
        guard shouldRecalculateIndicators() else { return (0.0, 0.0, false, false, true) }
        
        guard prices.count >= longTermPeriod else { return (0.0, 0.0, false, false, true) }
        
        // Calculate Exponential Moving Averages (EMAs)
        let shortTermEma = calculateEma(prices: prices, period: shortTermPeriod)
        let longTermEma = calculateEma(prices: prices, period: longTermPeriod)
        
        // Calculate MACD Line
        let macd = shortTermEma - longTermEma
        
        // Calculate Signal Line (9-period EMA of MACD)
        let signalLine = calculateEma(prices: [macd], period: signalPeriod)
        
        // Buy, Sell, or Hold signal based on MACD and Signal Line crossover
        let buySignal = macd > signalLine // Buy signal when MACD crosses above Signal Line
        let sellSignal = macd < signalLine // Sell signal when MACD crosses below Signal Line
        let holdSignal = macd == signalLine // Hold signal when MACD equals Signal Line (no crossover)
        
        return (macd, signalLine, buySignal, sellSignal, holdSignal)
    }

    func calculateEma(prices: [Double], period: Int) -> Double {
        guard prices.count >= period else { return 0.0 }
        
        // Calculate the smoothing factor for EMA
        let smoothingFactor = 2.0 / (Double(period) + 1)
        
        // Calculate the Exponential Moving Average (EMA)
        var ema = prices.prefix(period).reduce(0, +) / Double(period) // Initial EMA (SMA of the first period)
        
        for price in prices.dropFirst(period) {
            ema = (price - ema) * smoothingFactor + ema
        }
        
        return ema
    }

    func calculateRsi(prices: [Double], period: Int = 14) -> (rsi: Double, buySignal: Bool, sellSignal: Bool, holdSignal: Bool) {
        
        guard shouldRecalculateIndicators() else { return (0.0, false, false, true) }
        
        guard prices.count > period else { return (0.0, false, false, true) }
        
        var gains: [Double] = []
        var losses: [Double] = []
        
        // Calculate gains and losses
        for i in 1..<prices.count {
            let change = prices[i] - prices[i - 1]
            if change > 0 {
                gains.append(change)
                losses.append(0.0)
            } else if change < 0 {
                losses.append(-change)
                gains.append(0.0)
            } else {
                gains.append(0.0)
                losses.append(0.0)
            }
        }
        
        // Calculate average gain and loss
        let avgGain = gains.suffix(period).reduce(0, +) / Double(period)
        let avgLoss = losses.suffix(period).reduce(0, +) / Double(period)
        
        // Guard against division by zero
        guard avgLoss != 0 else { return (100.0, false, true, false) }
        
        // Calculate Relative Strength (RS) and RSI
        let rs = avgGain / avgLoss
        let rsi = 100 - (100 / (1 + rs))
        
        // Buy, Sell, or Hold signal based on RSI value
        let buySignal = rsi < 30 // Buy signal when RSI is under 30 (oversold)
        let sellSignal = rsi > 70 // Sell signal when RSI is above 70 (overbought)
        let holdSignal = rsi >= 30 && rsi <= 70 // Hold signal when RSI is between 30 and 70 (neutral)
        
        return (rsi, buySignal, sellSignal, holdSignal)
    }

    func calculateObv(prices: [Double], volumes: [Double]) -> (obv: Double, buySignal: Bool, sellSignal: Bool, holdSignal: Bool) {
        
        guard shouldRecalculateIndicators() else { return (0.0, false, false, true) }
        
        guard prices.count == volumes.count, prices.count > 1 else { return (0.0, false, false, true) }
        
        var obv = 0.0
        var previousObv = 0.0
        var buySignal = false
        var sellSignal = false
        var holdSignal = false
        
        // Calculate OBV
        for i in 1..<prices.count {
            if prices[i] > prices[i - 1] {
                obv += volumes[i]  // Add volume on up days
            } else if prices[i] < prices[i - 1] {
                obv -= volumes[i]  // Subtract volume on down days
            }
        }
        
        // Calculate Buy/Sell/Hold signals based on OBV trends
        if obv > previousObv && prices.last ?? 0.0 > prices[prices.count - 2] {
            buySignal = true  // OBV and price are both increasing (buy signal)
        } else if obv < previousObv && prices.last ?? 0.0 < prices[prices.count - 2] {
            sellSignal = true  // OBV and price are both decreasing (sell signal)
        } else {
            holdSignal = true  // No clear trend (hold signal)
        }
        
        // Update previous OBV for next iteration
        previousObv = obv
        
        return (obv, buySignal, sellSignal, holdSignal)
    }

    func calculateAdl(prices: [Double], highPrices: [Double], lowPrices: [Double], volumes: [Double]) -> (adl: Double, buySignal: Bool, sellSignal: Bool, holdSignal: Bool) {
        
        guard shouldRecalculateIndicators() else { return (0.0, false, false, true) }
        
        guard prices.count == highPrices.count, prices.count == lowPrices.count, prices.count == volumes.count else { return (0.0, false, false, true) }
        
        var adl = 0.0
        var previousAdl = 0.0
        var buySignal = false
        var sellSignal = false
        var holdSignal = false
        
        // Calculate ADL
        for i in 1..<prices.count {
            let moneyFlowMultiplier = ((prices[i] - lowPrices[i]) - (highPrices[i] - prices[i])) / (highPrices[i] - lowPrices[i])
            let moneyFlowVolume = moneyFlowMultiplier * volumes[i]
            adl += moneyFlowVolume
        }
        
        // Calculate Buy/Sell/Hold signals based on ADL trends
        if adl > previousAdl && prices.last ?? 0.0 > prices[prices.count - 2] {
            buySignal = true  // ADL and price are both increasing (buy signal)
        } else if adl < previousAdl && prices.last ?? 0.0 < prices[prices.count - 2] {
            sellSignal = true  // ADL and price are both decreasing (sell signal)
        } else {
            holdSignal = true  // No clear trend (hold signal)
        }
        
        // Update previous ADL for next iteration
        previousAdl = adl
        
        return (adl, buySignal, sellSignal, holdSignal)
    }

    func calculateBollingerBandsWithSignals(prices: [Double], period: Int = 20) -> (upperBand: Double, middleBand: Double, lowerBand: Double, buySignal: Bool, sellSignal: Bool, holdSignal: Bool) {
        guard shouldRecalculateIndicators() else { return (0.0, 0.0, 0.0, false, false, true) }
        
        // Ensure there are enough data points
        guard prices.count >= period else { return (0.0, 0.0, 0.0, false, false, true) }
        
        // Calculate the moving average (middle band)
        let movingAvg = calculateMovingAverage(prices: prices, period: period)
        
        // Calculate the standard deviation
        let squaredDiff = prices.map { pow($0 - movingAvg, 2) }
        let variance = squaredDiff.reduce(0, +) / Double(prices.count)
        let standardDeviation = sqrt(variance)
        
        // Calculate the upper and lower bands
        let upperBand = movingAvg + (standardDeviation * 2)
        let lowerBand = movingAvg - (standardDeviation * 2)
        
        // Determine the buy, sell, or hold signal
        var buySignal = false
        var sellSignal = false
        var holdSignal = false
        
        let lastPrice = prices.last ?? 0.0
        
        if lastPrice <= lowerBand {
            buySignal = true  // Price is at or below the lower band (possible oversold condition)
        } else if lastPrice >= upperBand {
            sellSignal = true  // Price is at or above the upper band (possible overbought condition)
        } else {
            holdSignal = true  // Price is within the bands (no clear trend)
        }
        
        return (upperBand, movingAvg, lowerBand, buySignal, sellSignal, holdSignal)
    }

    func calculateAdxWithSignals(prices: [Double], highPrices: [Double], lowPrices: [Double], period: Int = 14) -> (adx: Double, buySignal: Bool, sellSignal: Bool, holdSignal: Bool) {
        guard shouldRecalculateIndicators() else { return (0.0, false, false, true) }
        
        // Ensure there is enough data for the period
        guard prices.count > period, highPrices.count > period, lowPrices.count > period else {
            return (0.0, false, false, true) // Insufficient data
        }
        
        var trValues: [Double] = []
        var positiveDM: [Double] = []
        var negativeDM: [Double] = []
        
        for i in 1..<prices.count {
            let high = highPrices[i]
            let low = lowPrices[i]
            let prevClose = prices[i - 1]
            let prevHigh = highPrices[i - 1]
            let prevLow = lowPrices[i - 1]
            
            // True Range
            let tr = max(high - low, abs(high - prevClose), abs(low - prevClose))
            trValues.append(tr)
            
            // Positive Directional Movement
            let posDM = high - prevHigh > prevLow - low ? max(high - prevHigh, 0) : 0
            positiveDM.append(posDM)
            
            // Negative Directional Movement
            let negDM = prevLow - low > high - prevHigh ? max(prevLow - low, 0) : 0
            negativeDM.append(negDM)
        }
        
        // Smooth the True Range, +DM and -DM
        let smoothedTR = smoothArray(trValues, period: period)
        let smoothedPositiveDM = smoothArray(positiveDM, period: period)
        let smoothedNegativeDM = smoothArray(negativeDM, period: period)
        
        var plusDI: [Double] = []
        var minusDI: [Double] = []
        var dx: [Double] = []
        
        for i in 0..<smoothedTR.count {
            let smoothedTRValue = smoothedTR[i]
            let smoothedPlusDM = smoothedPositiveDM[i]
            let smoothedMinusDM = smoothedNegativeDM[i]
            
            let plusDIValue = (smoothedPlusDM / smoothedTRValue) * 100
            let minusDIValue = (smoothedMinusDM / smoothedTRValue) * 100
            plusDI.append(plusDIValue)
            minusDI.append(minusDIValue)
            
            let dxValue = abs(plusDIValue - minusDIValue) / (plusDIValue + minusDIValue) * 100
            dx.append(dxValue)
        }
        
        // Average DX over the period to get ADX
        let adx = dx.prefix(period).reduce(0, +) / Double(period)
        
        // Determine buy, sell, or hold signals
        var buySignal = false
        var sellSignal = false
        var holdSignal = false
        
        let lastPlusDI = plusDI.last ?? 0.0
        let lastMinusDI = minusDI.last ?? 0.0
        
        if adx >= 25 {
            // Strong trend
            if lastPlusDI > lastMinusDI {
                buySignal = true
            } else if lastMinusDI > lastPlusDI {
                sellSignal = true
            }
        } else if adx < 20 {
            // Weak or no trend
            holdSignal = true
        }
        
        return (adx, buySignal, sellSignal, holdSignal)
    }

    func smoothArray(_ array: [Double], period: Int) -> [Double] {
        var smoothedArray: [Double] = []
        var smoothedValue = array.prefix(period).reduce(0, +) / Double(period)
        smoothedArray.append(smoothedValue)
        
        for i in period..<array.count {
            smoothedValue = (smoothedValue * Double(period - 1) + array[i]) / Double(period)
            smoothedArray.append(smoothedValue)
        }
        
        return smoothedArray
    }
    
    func calculateFibonacciRetracementWithSignals(prices: [Double]) -> (fibRetracementLevel: Double, buySignal: Bool, sellSignal: Bool, holdSignal: Bool) {
        guard shouldRecalculateIndicators() else { return (0.0, false, false, true) }
        
        // Ensure we have enough data points for calculation
        guard let maxPrice = prices.max(), let minPrice = prices.min(), maxPrice != minPrice else {
            return (0.0, false, false, true) // Return no signals if there is insufficient data or the max and min are equal
        }
        
        // Calculate the Fibonacci retracement levels
        let difference = maxPrice - minPrice
        let level23_6 = maxPrice - (difference * 0.236)
        let level38_2 = maxPrice - (difference * 0.382)
        let level50 = maxPrice - (difference * 0.5)
        let level61_8 = maxPrice - (difference * 0.618)
        
        // Retrieve the last price to compare against the Fibonacci levels
        let lastPrice = prices.last ?? 0.0
        
        var buySignal = false
        var sellSignal = false
        var holdSignal = false
        var fibRetracementLevel = 0.0 // Set this to 0 initially
        
        // Buy Signal Logic: Price at a retracement level (typically 23.6%, 38.2%, or 50%) and shows signs of bouncing
        if lastPrice <= level38_2 && lastPrice > level50 {
            buySignal = true
            fibRetracementLevel = level38_2 // Use the retracement level that triggered the buy signal
        }
        
        // Sell Signal Logic: Price reaches 61.8% and shows signs of reversal downwards
        if lastPrice >= level61_8 {
            sellSignal = true
            fibRetracementLevel = level61_8 // Use the retracement level that triggered the sell signal
        }
        
        // Hold Signal Logic: Price is between levels with no clear signal
        if lastPrice > level23_6 && lastPrice < level61_8 {
            holdSignal = true
            fibRetracementLevel = level50 // Use level50 as the hold signal retracement level
        }
        
        // Return the retracement level and signals
        return (fibRetracementLevel, buySignal, sellSignal, holdSignal)
    }

    func calculateIchimokuCloudWithSignals(highPrices: [Double], lowPrices: [Double], closePrices: [Double]) -> (buySignal: Bool, sellSignal: Bool, holdSignal: Bool) {
        guard shouldRecalculateIndicators() else { return (false, false, true) }
        
        // Ensure sufficient data points
        guard highPrices.count >= 26, lowPrices.count >= 26, closePrices.count > 0 else {
            return (false, false, true) // Cannot compute with insufficient data
        }

        // Calculate Tenkan-sen (Conversion Line) and Kijun-sen (Base Line)
        let tenkanSen = calculateMovingAverage(prices: highPrices, period: 9)
        let kijunSen = calculateMovingAverage(prices: highPrices, period: 26)
        
        // Calculate Senkou Span A and Senkou Span B (Leading spans)
        let senkouSpanA = (tenkanSen + kijunSen) / 2
        let senkouSpanB = calculateMovingAverage(prices: highPrices, period: 52)
        
        // Calculate Chikou Span (Lagging Line)
        _ = closePrices.last ?? 0.0
        
        // Define Buy, Sell, and Hold conditions
        var buySignal = false
        var sellSignal = false
        var holdSignal = false

        // Check if Tenkan-sen crosses above Kijun-sen and price is above the cloud
        if tenkanSen > kijunSen && closePrices.last ?? 0.0 > senkouSpanA {
            buySignal = true
        }

        // Check if Tenkan-sen crosses below Kijun-sen and price is below the cloud
        if tenkanSen < kijunSen && closePrices.last ?? 0.0 < senkouSpanB {
            sellSignal = true
        }

        // If price is within the cloud or no crossovers, hold the position
        if closePrices.last ?? 0.0 >= senkouSpanB && closePrices.last ?? 0.0 <= senkouSpanA {
            holdSignal = true
        }
        
        // Return the signals for buy, sell, or hold
        return (buySignal, sellSignal, holdSignal)
    }
        
    func updateLabelColor(_ label: UILabel, status: String) {
        DispatchQueue.main.async {
            switch status.lowercased() {
            case "buy":
                label.textColor = .green
            case "sell":
                label.textColor = .red
            case "hold":
                label.textColor = .orange
            default:
                label.textColor = .white // Original color, assuming white text on a black background
            }
        }
    }

    func updateMovingAveragesLabel(shortTermMA: Double, longTermMA: Double) {
        let status = shortTermMA > longTermMA ? "buy" : (shortTermMA < longTermMA ? "sell" : "hold")
        updateLabelColor(movingAveragesLabel, status: status)
    }

    func updateMacdLabel(macd: Double, signalLine: Double, buySignal: Bool, sellSignal: Bool, holdSignal: Bool) {
        // Update the MACD and Signal Line values
        print("MACD: \(macd), Signal Line: \(signalLine)")
        
        // Handle Buy, Sell, or Hold signals
        if buySignal {
            print("Buy Signal")
        } else if sellSignal {
            print("Sell Signal")
        } else if holdSignal {
            print("Hold Signal")
        }
    }

    func updateRsiLabel(rsi: Double, buySignal: Bool, sellSignal: Bool, holdSignal: Bool) {
        // Update the RSI value
        print("RSI: \(rsi)")
        
        // Handle Buy, Sell, or Hold signals
        if buySignal {
            print("Buy Signal (RSI is below 30, oversold)")
        } else if sellSignal {
            print("Sell Signal (RSI is above 70, overbought)")
        } else if holdSignal {
            print("Hold Signal (RSI is between 30 and 70, neutral)")
        }
    }

    func updateObvLabel(obv: Double, price: Double, previousPrice: Double) {
        let status: String
        if let previousObv = previousObv {
            status = (obv > previousObv && price > previousPrice) ? "buy" : (obv < previousObv && price < previousPrice ? "sell" : "hold")
        } else {
            status = "hold"
        }
        updateLabelColor(obvLabel, status: status)
        previousObv = obv
    }

    func updateStochasticLabel(stochasticK: Double, stochasticD: Double) {
        let status = (stochasticK > 80) ? "sell" : (stochasticK < 20 ? "buy" : "hold")
        updateLabelColor(stochasticLabel, status: status)
    }

    func updateAdlLabel(adl: Double) {
        let status = (adl > 0) ? "buy" : (adl < 0 ? "sell" : "hold")
        updateLabelColor(adlLabel, status: status)
    }

    func updateBollingerBandsLabel(currentPrice: Double, upperBand: Double, lowerBand: Double) {
        let status = (currentPrice > upperBand) ? "sell" : (currentPrice < lowerBand ? "buy" : "hold")
        updateLabelColor(bollingerBandsLabel, status: status)
    }

    func updateAdxLabel(adx: Double) {
        let strongTrendThreshold = 25.0
        let status = (adx > strongTrendThreshold) ? "buy" : (adx < strongTrendThreshold ? "sell" : "hold")
        updateLabelColor(adxLabel, status: status)
    }

    func updateFibonacciLabel(price: Double, fibRetracementLevel: Double) {
        let status = (price > fibRetracementLevel) ? "buy" : (price < fibRetracementLevel ? "sell" : "hold")
        updateLabelColor(fibonacciLabel, status: status)
    }

    func updateIchimokuLabel(currentPrice: Double, tenkanSen: Double, kijunSen: Double, priceAboveCloud: Bool) {
        let status: String
        if priceAboveCloud {
            status = tenkanSen > kijunSen ? "buy" : "hold"
        } else {
            status = currentPrice < min(tenkanSen, kijunSen) ? "sell" : "hold"
        }
        updateLabelColor(ichimokuLabel, status: status)
    }

        // Decodable structs for handling Alpha Vantage API response
        struct TimeSeriesDailyAdjusted: Decodable {
            let timeSeries: [String: DailyAdjustedData]
            let errorMessage: String?
            
            enum CodingKeys: String, CodingKey {
                case timeSeries = "Time Series (Daily)"
                case errorMessage = "Error Message"
            }
        }
        
    struct DailyAdjustedData: Codable {
        let open: String
        let high: String
        let low: String
        let close: String
        let adjustedClose: String
        let volume: String
        let dividendAmount: String
        let splitCoefficient: String

        // Coding keys to match the JSON response if necessary
        enum CodingKeys: String, CodingKey {
            case open = "1. open"
            case high = "2. high"
            case low = "3. low"
            case close = "4. close"
            case adjustedClose = "5. adjusted close"
            case volume = "6. volume"
            case dividendAmount = "7. dividend amount"
            case splitCoefficient = "8. split coefficient"
        }
    }}

				
			
				
					🤖

class ViewController: UIViewController {
    
    // MARK: - Outlets
    @IBOutlet weak var tickerTextField: UITextField!
    @IBOutlet weak var macdLabel: UILabel!
    @IBOutlet weak var rsiLabel: UILabel!
    @IBOutlet weak var stochasticLabel: UILabel!
    @IBOutlet weak var ichimokuLabel: UILabel!
    @IBOutlet weak var bollingerBandsLabel: UILabel!
    @IBOutlet weak var movingAveragesLabel: UILabel!
    @IBOutlet weak var obvLabel: UILabel!
    @IBOutlet weak var fibonacciLabel: UILabel!
    @IBOutlet weak var adxLabel: UILabel!
    @IBOutlet weak var adlLabel: UILabel!
    
    // MARK: - Properties
    private let apiKey = "1RA8WZLKFJNH7B8H"
    private let baseUrl = "https://www.alphavantage.co/query"
    private var isFetchingData = false
    private var symbol = "AAPL" // Default to Apple, can be updated via search
    
    // Data arrays
    private var closingPrices: [Double] = []
    private var highPrices: [Double] = []
    private var lowPrices: [Double] = []
    private var volumes: [Double] = []
    
    // Indicator-related data
    private var macd: Double = 0.0
    private var signalLine: Double = 0.0
    private var rsi: Double = 0.0
    private var obv: Double = 0.0
    private var adl: Double = 0.0
    private var upperBand: Double = 0.0
    private var middleBand: Double = 0.0
    private var lowerBand: Double = 0.0
    private var adx: Double = 0.0
    private var stochasticK: Double = 0.0
    private var stochasticD: Double = 0.0
    
    // MARK: - Lifecycle Methods
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTickerInputListener()
    }
    
    // MARK: - Setup Methods
    private func setupTickerInputListener() {
        tickerTextField.addTarget(self, action: #selector(tickerInputChanged), for: .editingChanged)
    }
    
    // MARK: - Event Handlers
    @objc private func tickerInputChanged() {
        guard let newTicker = tickerTextField.text, !newTicker.isEmpty else { return }
        symbol = newTicker.uppercased()
        fetchData(for: symbol)
    }
    
    // MARK: - Data Fetching
    private func fetchData(for symbol: String) {
        guard !isFetchingData else { return }
        isFetchingData = true
        
        let group = DispatchGroup()
        
        group.enter()
        fetchTimeSeriesData(for: symbol) { [weak self] success in
            if !success {
                self?.displayError("Failed to fetch time series data.")
            }
            group.leave()
        }
        
        group.enter()
        fetchTechnicalIndicator(function: "MACD", symbol: symbol) { [weak self] data in
            if let data = data {
                self?.processMACDData(data)
            } else {
                self?.displayError("Failed to fetch MACD data.")
            }
            group.leave()
        }
        
        group.enter()
        fetchTechnicalIndicator(function: "RSI", symbol: symbol) { [weak self] data in
            if let data = data {
                self?.processRSIData(data)
            } else {
                self?.displayError("Failed to fetch RSI data.")
            }
            group.leave()
        }
        
        // Fetch other indicators similarly...
        
        group.notify(queue: .main) { [weak self] in
            self?.updateUI()
            self?.isFetchingData = false
        }
    }
    
    private func fetchTimeSeriesData(for symbol: String, completion: @escaping (Bool) -> Void) {
        let urlString = "\(baseUrl)?function=TIME_SERIES_DAILY_ADJUSTED&symbol=\(symbol)&apikey=\(apiKey)"
        guard let url = URL(string: urlString) else {
            completion(false)
            return
        }
        
        URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard let self = self, let data = data, error == nil else {
                completion(false)
                return
            }
            
            do {
                let decoder = JSONDecoder()
                let response = try decoder.decode(TimeSeriesResponse.self, from: data)
                self.processTimeSeriesData(response.timeSeries)
                completion(true)
            } catch {
                completion(false)
            }
        }.resume()
    }
    
    private func fetchTechnicalIndicator(function: String, symbol: String, completion: @escaping ([String: String]?) -> Void) {
        let urlString = "\(baseUrl)?function=\(function)&symbol=\(symbol)&interval=daily&apikey=\(apiKey)"
        guard let url = URL(string: urlString) else {
            completion(nil)
            return
        }
        
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else {
                completion(nil)
                return
            }
            
            do {
                let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
                if let technicalData = json?["Technical Analysis: \(function)"] as? [String: [String: String]] {
                    completion(technicalData.first?.value)
                } else {
                    completion(nil)
                }
            } catch {
                completion(nil)
            }
        }.resume()
    }
    
    // MARK: - Data Processing
    private func processTimeSeriesData(_ timeSeries: [String: DailyAdjustedData]) {
        closingPrices = timeSeries.values.compactMap { Double($0.close) }
        highPrices = timeSeries.values.compactMap { Double($0.high) }
        lowPrices = timeSeries.values.compactMap { Double($0.low) }
        volumes = timeSeries.values.compactMap { Double($0.volume) }
    }
    
    private func processMACDData(_ data: [String: String]) {
        if let macdValue = data["MACD"], let macd = Double(macdValue) {
            self.macd = macd
        }
        if let signalValue = data["MACD_Signal"], let signal = Double(signalValue) {
            self.signalLine = signal
        }
    }
    
    private func processRSIData(_ data: [String: String]) {
        if let rsiValue = data["RSI"], let rsi = Double(rsiValue) {
            self.rsi = rsi
        }
    }
    
    // Process other indicators similarly...
    
     // MARK: - UI Updates
    private func updateUI() {
        updateLabel(macdLabel, with: macd, signalValue: signalLine)
        updateLabel(rsiLabel, with: rsi)
        updateLabel(stochasticLabel, with: stochasticK)
        updateLabel(ichimokuLabel, with: adl) // Example: Adjust based on your data mappings
        updateLabel(bollingerBandsLabel, with: upperBand)
        updateLabel(movingAveragesLabel, with: adx)
        updateLabel(obvLabel, with: obv)
        updateLabel(fibonacciLabel, with: lowerBand)
        updateLabel(adxLabel, with: adx)
        updateLabel(adlLabel, with: adl)
    }
    
    private func updateLabel(_ label: UILabel, with value: Double, signalValue: Double? = nil) {
        label.textColor = determineColor(for: value, signalValue: signalValue)
    }
    
    private func determineColor(for value: Double, signalValue: Double? = nil) -> UIColor {
        if let signalValue = signalValue {
            if value > signalValue {
                return .green // Buy
            } else if value < signalValue {
                return .red // Sell
            }
        } else {
            if value > 70 {
                return .red // Overbought/Sell
            } else if value < 30 {
                return .green // Oversold/Buy
            }
        }
        return .yellow // Hold/Neutral
    }
    
    // MARK: - Error Handling
    private func displayError(_ message: String) {
        DispatchQueue.main.async {
            let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default))
            self.present(alert, animated: true)
        }
    }
    
    // MARK: - Helper Functions
    private func resetData() {
        macd = 0.0
        signalLine = 0.0
        rsi = 0.0
        stochasticK = 0.0
        adl = 0.0
        upperBand = 0.0
        middleBand = 0.0
        lowerBand = 0.0
        adx = 0.0
        obv = 0.0
        
        closingPrices.removeAll()
        highPrices.removeAll()
        lowPrices.removeAll()
        volumes.removeAll()
    }
}

// MARK: - Data Models
struct TimeSeriesResponse: Codable {
    let timeSeries: [String: DailyAdjustedData]
    
    enum CodingKeys: String, CodingKey {
        case timeSeries = "Time Series (Daily)"
    }
}

struct DailyAdjustedData: Codable {
    let open: String
    let high: String
    let low: String
    let close: String
    let volume: String
    
    enum CodingKeys: String, CodingKey {
        case open = "1. open"
        case high = "2. high"
        case low = "3. low"
        case close = "4. close"
        case volume = "5. volume"
    }
}