Skip to content

Instantly share code, notes, and snippets.

@jverkoey
Created August 5, 2024 13:25
Show Gist options
  • Save jverkoey/bb55bab7899ad55e6d7e48ecee3b732d to your computer and use it in GitHub Desktop.
Save jverkoey/bb55bab7899ad55e6d7e48ecee3b732d to your computer and use it in GitHub Desktop.
Stock graph
import Charts
import SwiftUI
let oneDay: TimeInterval = 60 * 60 * 24
struct ContentView: View {
var body: some View {
let history = loadHistory()
.filter { $0.id > Date.now.addingTimeInterval(-oneDay * 30) }
let analysis = TradingAnalysis(history: history)
VStack {
HStack {
Text("AAPL")
Text(formattedDollars(analysis.lastChange))
.foregroundStyle(analysis.lastChange > 0 ? .green : .red)
}
Chart(history, content: { day in
RuleMark(
x: .value("Date", day.id),
yStart: .value("Low", day.low),
yEnd: .value("High", day.high)
)
.lineStyle(.init(lineWidth: 1))
.foregroundStyle(.gray)
RuleMark(
x: .value("Date", day.id),
yStart: .value("Open", day.open),
yEnd: .value("Close", day.close)
)
.lineStyle(.init(lineWidth: 4))
.foregroundStyle(day.open < day.close ? .green : .red)
})
.chartXScale(domain: analysis.xRange)
.chartYScale(domain: analysis.yRange)
}
.padding()
}
}
struct TradingAnalysis {
let xRange: ClosedRange<Date>
let yRange: ClosedRange<Double>
let lastChange: Double
init(history: [TradingDay]) {
if let min = history.map({ $0.id }).min(),
let max = history.map({ $0.id }).max() {
self.xRange = min.addingTimeInterval(-oneDay)...max.addingTimeInterval(oneDay)
} else {
self.xRange = Date.now.addingTimeInterval(-oneDay)...Date.now
}
if let min = history.map({ $0.low }).min(),
let max = history.map({ $0.high }).max() {
let size = max - min
self.yRange = (min - size * 0.1)...(max + size * 0.1)
} else {
self.yRange = 0...100
}
if let last = history.last {
self.lastChange = last.close - last.open
} else {
self.lastChange = 0
}
}
}
struct TradingDay: Identifiable {
let id: Date
let low: Double
let high: Double
let open: Double
let close: Double
}
/// History loaded from CSV export of https://finance.yahoo.com/quote/AAPL/history/
func loadHistory() -> [TradingDay] {
guard let url = Bundle.main.url(forResource: "AAPL", withExtension: "csv"),
let csv = try? String(contentsOf: url, encoding: .utf8) else {
return []
}
let lines = csv.components(separatedBy: .newlines)
return lines
.map { $0.components(separatedBy: ",") }
.dropFirst()
.compactMap {
guard let date = parseDate($0[0]),
let high = Double($0[2]),
let low = Double($0[3]),
let open = Double($0[1]),
let close = Double($0[4]) else {
return nil
}
return TradingDay(id: date, low: low, high: high, open: open, close: close)
}
}
func parseDate(_ string: String) -> Date? {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter.date(from: string)
}
func formattedDollars(_ dollars: Double) -> String {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
formatter.positivePrefix = "+"
return formatter.string(from: dollars as NSNumber) ?? ""
}
#Preview {
ContentView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment