From 32411d17adbba0bfc19ba6b526abed3266007f7f Mon Sep 17 00:00:00 2001 From: Ph0enixKM Date: Sun, 1 Jan 2023 16:08:01 +0100 Subject: [PATCH] feat: improve UI and add candle pricing --- OandaChandler.xcodeproj/project.pbxproj | 54 ++++- .../UserInterfaceState.xcuserstate | Bin 55811 -> 75585 bytes OandaChandler/Candles.swift | 172 ---------------- OandaChandler/ContentView.swift | 36 +++- OandaChandler/CreatingPhase.swift | 153 --------------- OandaChandler/Data/AuthData.swift | 29 +++ OandaChandler/Data/Candles.swift | 54 +++++ OandaChandler/Data/Date.swift | 28 +++ OandaChandler/Granularity.swift | 50 +++++ OandaChandler/KeychainController.swift | 59 ++++++ OandaChandler/OandaChandlerApp.swift | 87 ++++++-- OandaChandler/Request.swift | 74 +++++++ OandaChandler/Views/Auth.swift | 55 ++++++ OandaChandler/Views/Creating.swift | 185 ++++++++++++++++++ 14 files changed, 684 insertions(+), 352 deletions(-) delete mode 100644 OandaChandler/Candles.swift delete mode 100644 OandaChandler/CreatingPhase.swift create mode 100644 OandaChandler/Data/AuthData.swift create mode 100644 OandaChandler/Data/Candles.swift create mode 100644 OandaChandler/Data/Date.swift create mode 100644 OandaChandler/Granularity.swift create mode 100644 OandaChandler/KeychainController.swift create mode 100644 OandaChandler/Request.swift create mode 100644 OandaChandler/Views/Auth.swift create mode 100644 OandaChandler/Views/Creating.swift diff --git a/OandaChandler.xcodeproj/project.pbxproj b/OandaChandler.xcodeproj/project.pbxproj index 52522c5..eeb60a3 100644 --- a/OandaChandler.xcodeproj/project.pbxproj +++ b/OandaChandler.xcodeproj/project.pbxproj @@ -7,15 +7,23 @@ objects = { /* Begin PBXBuildFile section */ + 3B5B5B20296199840084E90F /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5B5B1F296199840084E90F /* Request.swift */; }; + 3B5B5B222961B6500084E90F /* Granularity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5B5B212961B6500084E90F /* Granularity.swift */; }; 3B714143295B24BC00B5358A /* OandaChandlerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B714142295B24BC00B5358A /* OandaChandlerApp.swift */; }; 3B714145295B24BC00B5358A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B714144295B24BC00B5358A /* ContentView.swift */; }; 3B714147295B24BE00B5358A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3B714146295B24BE00B5358A /* Assets.xcassets */; }; - 3B71414A295B24BE00B5358A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3B714149295B24BE00B5358A /* Preview Assets.xcassets */; }; 3B714152295B254400B5358A /* Candles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B714151295B254400B5358A /* Candles.swift */; }; - 3B79971C295C6DB500AD242A /* CreatingPhase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B79971B295C6DB500AD242A /* CreatingPhase.swift */; }; + 3B79971C295C6DB500AD242A /* Creating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B79971B295C6DB500AD242A /* Creating.swift */; }; + 3B87BEC52961001800CCE50D /* AuthData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B87BEC42961001800CCE50D /* AuthData.swift */; }; + 3B87BEC62961019500CCE50D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3B714149295B24BE00B5358A /* Preview Assets.xcassets */; }; + 3BD8BCCA2960779800B7A908 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD8BCC92960779800B7A908 /* Date.swift */; }; + 3BD8BCCE29607E5600B7A908 /* Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD8BCCD29607E5600B7A908 /* Auth.swift */; }; + 3BD8BCD029608A3F00B7A908 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD8BCCF29608A3F00B7A908 /* KeychainController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 3B5B5B1F296199840084E90F /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; }; + 3B5B5B212961B6500084E90F /* Granularity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Granularity.swift; sourceTree = ""; }; 3B71413F295B24BC00B5358A /* OandaChandler.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OandaChandler.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3B714142295B24BC00B5358A /* OandaChandlerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OandaChandlerApp.swift; sourceTree = ""; }; 3B714144295B24BC00B5358A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -23,7 +31,11 @@ 3B714149295B24BE00B5358A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 3B71414B295B24BE00B5358A /* OandaChandler.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OandaChandler.entitlements; sourceTree = ""; }; 3B714151295B254400B5358A /* Candles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Candles.swift; sourceTree = ""; }; - 3B79971B295C6DB500AD242A /* CreatingPhase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatingPhase.swift; sourceTree = ""; }; + 3B79971B295C6DB500AD242A /* Creating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Creating.swift; sourceTree = ""; }; + 3B87BEC42961001800CCE50D /* AuthData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthData.swift; sourceTree = ""; }; + 3BD8BCC92960779800B7A908 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; + 3BD8BCCD29607E5600B7A908 /* Auth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Auth.swift; sourceTree = ""; }; + 3BD8BCCF29608A3F00B7A908 /* KeychainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,10 +68,13 @@ 3B714141295B24BC00B5358A /* OandaChandler */ = { isa = PBXGroup; children = ( + 3BD8BCCC2960781700B7A908 /* Data */, + 3BD8BCCB296077EE00B7A908 /* Views */, + 3B5B5B1F296199840084E90F /* Request.swift */, + 3BD8BCCF29608A3F00B7A908 /* KeychainController.swift */, 3B714142295B24BC00B5358A /* OandaChandlerApp.swift */, 3B714144295B24BC00B5358A /* ContentView.swift */, - 3B79971B295C6DB500AD242A /* CreatingPhase.swift */, - 3B714151295B254400B5358A /* Candles.swift */, + 3B5B5B212961B6500084E90F /* Granularity.swift */, 3B714146295B24BE00B5358A /* Assets.xcassets */, 3B71414B295B24BE00B5358A /* OandaChandler.entitlements */, 3B714148295B24BE00B5358A /* Preview Content */, @@ -75,6 +90,25 @@ path = "Preview Content"; sourceTree = ""; }; + 3BD8BCCB296077EE00B7A908 /* Views */ = { + isa = PBXGroup; + children = ( + 3B79971B295C6DB500AD242A /* Creating.swift */, + 3BD8BCCD29607E5600B7A908 /* Auth.swift */, + ); + path = Views; + sourceTree = ""; + }; + 3BD8BCCC2960781700B7A908 /* Data */ = { + isa = PBXGroup; + children = ( + 3BD8BCC92960779800B7A908 /* Date.swift */, + 3B714151295B254400B5358A /* Candles.swift */, + 3B87BEC42961001800CCE50D /* AuthData.swift */, + ); + path = Data; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -133,7 +167,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3B71414A295B24BE00B5358A /* Preview Assets.xcassets in Resources */, + 3B87BEC62961019500CCE50D /* Preview Assets.xcassets in Resources */, 3B714147295B24BE00B5358A /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -145,10 +179,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3B79971C295C6DB500AD242A /* CreatingPhase.swift in Sources */, + 3B79971C295C6DB500AD242A /* Creating.swift in Sources */, + 3BD8BCCA2960779800B7A908 /* Date.swift in Sources */, + 3B5B5B222961B6500084E90F /* Granularity.swift in Sources */, 3B714145295B24BC00B5358A /* ContentView.swift in Sources */, 3B714152295B254400B5358A /* Candles.swift in Sources */, + 3BD8BCD029608A3F00B7A908 /* KeychainController.swift in Sources */, 3B714143295B24BC00B5358A /* OandaChandlerApp.swift in Sources */, + 3BD8BCCE29607E5600B7A908 /* Auth.swift in Sources */, + 3B5B5B20296199840084E90F /* Request.swift in Sources */, + 3B87BEC52961001800CCE50D /* AuthData.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/OandaChandler.xcodeproj/project.xcworkspace/xcuserdata/pawelkaras.xcuserdatad/UserInterfaceState.xcuserstate b/OandaChandler.xcodeproj/project.xcworkspace/xcuserdata/pawelkaras.xcuserdatad/UserInterfaceState.xcuserstate index fe14ac56bdc1557b2686b334e2ced33331aa6c3b..7252536fe0adc0745528c2ad5be5ad4b6d2bbd17 100644 GIT binary patch literal 75585 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 A^8f$< diff --git a/OandaChandler/Candles.swift b/OandaChandler/Candles.swift deleted file mode 100644 index e8e1aad..0000000 --- a/OandaChandler/Candles.swift +++ /dev/null @@ -1,172 +0,0 @@ -import SwiftUI -import Combine -import Foundation -import Dispatch - -struct CandleData: Decodable { - let candles: [Candle] -} - -struct Mid: Decodable { - let o: String - let h: String - let l: String - let c: String -} - -struct Candle: Decodable { - let complete: Bool - let volume: Int - let time: String - let mid: Mid -} - -extension Date { - static func ISOStringFromDate(date: Date) -> String { - let dateFormatter = DateFormatter() - dateFormatter.locale = Locale(identifier: "en_US_POSIX") - dateFormatter.timeZone = TimeZone(abbreviation: "GMT") - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS" - - return dateFormatter.string(from: date).appending("Z") - } - - static func dateFromISOString(string: String) -> Date? { - let dateFormatter = DateFormatter() - dateFormatter.locale = Locale(identifier: "en_US_POSIX") - dateFormatter.timeZone = TimeZone.autoupdatingCurrent - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" - - return dateFormatter.date(from: string) - } -} - -class Candles { - var candles: [Candle]? = nil - var error: String? = nil - var granularity = "" - var instrument = "" - var api_key = "" - var mode = OandaMode.FXTrader - - func getRequest(from: Date, to: Date) -> URLRequest { - let fromString = Date.ISOStringFromDate(date: from) - let toString = Date.ISOStringFromDate(date: to) - var mode = ""; - switch self.mode { - case .FXTrader: - mode = "fxtrade" - case .FXPractice: - mode = "fxpractice" - } - let uri = [ - "https://api-\(mode).oanda.com/v3/instruments/\(self.instrument)/candles?", - "granularity=\(self.granularity)&", - "from=\(fromString)&", - "to=\(toString)" - ].joined(separator: "") - let url = URL(string: uri)! - var req = URLRequest(url: url) - req.addValue("Bearer \(api_key)", forHTTPHeaderField: "Authorization") - return req - } - - func hour() -> TimeInterval { - return 60 * 60 - } - - // TODO: Move granularity and instrument to it's own UI Elements - func fetchCandles(from: Date, to: Date, granularity: String, instrument: String, api_key: String, mode: OandaMode, cb: (Double) -> Void) async -> Bool { - let fromDate = Calendar.current.date(bySettingHour: 1, minute: 0, second: 0, of: from)! - let toDate = Calendar.current.date(bySettingHour: 1, minute: 0, second: 0, of: to)! - self.granularity = granularity - self.instrument = instrument - self.api_key = api_key - self.mode = mode - var currentDate = fromDate - var nextDate = fromDate + self.hour() - var index = 0 - let interval = toDate.timeIntervalSince(fromDate) - let hours = interval / 3600 - var result: [(Int, [Candle])] = [] - while (nextDate <= to) { - let batch = await withTaskGroup(of: (Int, [Candle])?.self) { group -> [(Int, [Candle])] in - for _ in 0...25 { - if (currentDate >= to) { - break - } - let request = self.getRequest(from: currentDate, to: nextDate) - let idx = index - index += 1 - group.addTask { - do { - let (data, _) = try await URLSession.shared.data(for: request) - let decoded = String(decoding: data, as: UTF8.self) - // TODO: Do this in a reasonable way - if decoded.starts(with: "{\"errorMessage") { - self.error = decoded - } -// print(String(decoding: data, as: UTF8.self)) - let candleData = try JSONDecoder().decode(CandleData.self, from: data) - return (idx, candleData.candles) - } catch { - return nil - } - } - currentDate = nextDate - nextDate += self.hour() - } - var result: [(Int, [Candle])] = [] - for await value in group { - if let candles = value { - result.append(candles) - } - } - return result - } - cb(Double(index) / hours) - result += batch - } -// print(result.sorted(by: { (a, b) in a.0 < b.0 }).map { ($0.0, $0.1.first?.time, $0.1.last?.time, $0.1.count) }) -// print(result.map { $0.1.map { $0.time } }) -// print(result.map { $0.count }) -// print(result) - self.candles = result - .sorted(by: { (a, b) in a.0 < b.0 }) - .map { $0.1 } - .reduce([], +) - - print(self.candles?.first) - return self.candles?.isEmpty == false - } - - func formatDateString(_ dateString: String) -> String { - var res = dateString.replacingOccurrences(of: "-", with: "."); - res = dateString.replacingOccurrences(of: ":", with: "."); - res = dateString.replacingOccurrences(of: "T", with: ","); - return String(res[.. Void) throws { - // Reset the contents of the target file -// try "".data(using: .utf8)!.write(to: url); -// let fileHandle = try FileHandle(forWritingTo: url) - var row = "" - for (index, candle) in self.candles!.enumerated() { - row += [ - self.formatDateString(candle.time), - candle.mid.o, - candle.mid.h, - candle.mid.l, - candle.mid.c, - String(candle.volume) - ].joined(separator: ",") + "\n" - if index % 100 == 0 { - callback(Double(index) / Double(self.candles!.count)) - } - } - try row.data(using: .utf8)!.write(to: url) - } - -} - diff --git a/OandaChandler/ContentView.swift b/OandaChandler/ContentView.swift index 4a567e9..0d9519c 100644 --- a/OandaChandler/ContentView.swift +++ b/OandaChandler/ContentView.swift @@ -39,13 +39,37 @@ struct ContentView: View { @State var downloadProgress = 0.0 @State var saveProgress = 0.0 @State var isSavingFile = false - @State var candles = Candles() + @State var candles: [Candle] = [] + @State var error: String? = nil + + func getPricing(candle: Candle) -> CandlePricing { + return (candle.mid ?? candle.bid ?? candle.ask)! + } + + func generateCSV(_ url: URL, callback: (Double) -> Void) throws { + var row = "" + for (index, candle) in self.candles.enumerated() { + let pricing = getPricing(candle: candle) + row += [ + formatDateString(candle.time), + pricing.o, + pricing.h, + pricing.l, + pricing.c, + String(candle.volume) + ].joined(separator: ",") + "\n" + if index % 100 == 0 { + callback(Double(index) / Double(self.candles.count)) + } + } + try row.data(using: .utf8)!.write(to: url) + } var body: some View { ZStack { switch state { case .Creating: - CreatingPhase(state: $state, candles: $candles, progress: $downloadProgress) + CreatingPhase(state: $state, candles: $candles, error: $error, progress: $downloadProgress) case .Fetching: ProgressView(value: downloadProgress, label: { Text("Downloading...") @@ -65,7 +89,7 @@ struct ContentView: View { isSavingFile = true backgroundQueue.async { do { - try self.candles.generateCSV(save) { value in + try self.generateCSV(save) { value in DispatchQueue.main.async { self.saveProgress = value } @@ -101,7 +125,7 @@ struct ContentView: View { } Button(action: { self.state = .Creating - self.candles.candles = nil + self.candles = [] }) { Text("Back") }.padding().disabled(isSavingFile) } } @@ -113,10 +137,10 @@ struct ContentView: View { .frame(width: 50, height: 50) .padding() Text("Could not download the data") - Text(self.candles.error ?? "[unknown reason]").monospaced().padding() + Text(self.error ?? "[unknown reason]").monospaced().padding() Button(action: { self.state = .Creating - self.candles.candles = nil + self.candles = [] }) { Text("Back") }.padding() } } diff --git a/OandaChandler/CreatingPhase.swift b/OandaChandler/CreatingPhase.swift deleted file mode 100644 index 4f102a1..0000000 --- a/OandaChandler/CreatingPhase.swift +++ /dev/null @@ -1,153 +0,0 @@ -// -// CreatingPhase.swift -// TradingChronical -// -// Created by Paweł Karaś on 28/12/2022. -// - -import Foundation -import SwiftUI -import Security - -import Foundation -import Security - -let keychainService = Bundle.main.bundleIdentifier // replace with your app's unique identifier - -// Store data in the keychain -func storeDataInKeychain(_ key: String, data: String) { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: keychainService!, - kSecAttrAccount as String: key, - kSecValueData as String: data.data(using: .utf8)! - ] - let status = SecItemAdd(query as CFDictionary, nil) - if status != errSecSuccess { - print("Error storing data in keychain: \(status)") - } -} - -// Retrieve data from the keychain -func getDataFromKeychain(_ key: String) -> String? { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: keychainService!, - kSecAttrAccount as String: key, - kSecReturnData as String: true, - kSecMatchLimit as String: kSecMatchLimitOne - ] - var result: AnyObject? - let status = SecItemCopyMatching(query as CFDictionary, &result) - if status == errSecSuccess { - if let res = result as? Data { - return String(decoding: res, as: UTF8.self) - } else { - return nil - } - } else if status == errSecItemNotFound { - return nil - } else { - print("Error retrieving data from keychain: \(status)") - return nil - } -} - -// Test the functions -//let testData = "This is a test".data(using: .utf8)! -//storeDataInKeychain(data: testData) -//if let retrievedData = getDataFromKeychain() { -// print(String(data: retrievedData, encoding: .utf8)!) -//} else { -// print("Data not found in keychain") -//} - -enum OandaMode { - case FXTrader - case FXPractice -} - -func modeToValue(_ mode: OandaMode) -> String { - switch mode { - case .FXTrader: - return "fxt" - case .FXPractice: - return "fxp" - } -} - -func valueToMode(_ value: String) -> OandaMode { - switch value { - case "fxt": - return OandaMode.FXTrader - default: - return OandaMode.FXPractice - } -} - -struct CreatingPhase: View { - @Binding var state: AppState - @Binding var candles: Candles - @Binding var progress: Double - @State var from = Date() - @State var to = Date() - @State var granularity = "M1" - @State var instrument = "GBP_USD" - @State var token = getDataFromKeychain("token") ?? "" - @State var mode = valueToMode(getDataFromKeychain("mode") ?? "fxt") - - var body: some View { - VStack { - Text("Specify the configuration").font(.largeTitle).padding() - HStack { - SecureField("Your API Token", text: $token).frame(width: 200) - Picker("Choose mode", selection: $mode) { - Text("FX Trade").tag(OandaMode.FXTrader) - Text("FX Practice").tag(OandaMode.FXPractice) - }.frame(width: 200) - } - HStack { - TextField("Granularity", text: $granularity).frame(width: 50) - TextField("Instrument", text: $instrument).frame(width: 100) - } - HStack{ - DatePicker( - "From", - selection: $from, - displayedComponents: [.date] - ).datePickerStyle(.graphical).frame(width: 200) - DatePicker( - "To", - selection: $to, - displayedComponents: [.date] - ).datePickerStyle(.graphical).frame(width: 200) - }.padding() - Button(action: { - storeDataInKeychain("token", data: self.token) - storeDataInKeychain("mode", data: modeToValue(self.mode)) - self.state = .Fetching - Task { - let result = await self.candles.fetchCandles( - from: self.from, - to: self.to, - granularity: self.granularity, - instrument: self.instrument, - api_key: self.token, - mode: self.mode, - cb: { value in - DispatchQueue.main.async { - self.progress = value - } - } - ) - if result { - self.state = .Success - } else { - print(self.candles.candles!) - self.state = .Error - } - } - }) { Text("Submit") } - }.padding() - } -} diff --git a/OandaChandler/Data/AuthData.swift b/OandaChandler/Data/AuthData.swift new file mode 100644 index 0000000..8c4612c --- /dev/null +++ b/OandaChandler/Data/AuthData.swift @@ -0,0 +1,29 @@ +// +// Auth.swift +// OandaChandler +// +// Created by Paweł Karaś on 01/01/2023. +// + +import Foundation + +class AuthData: ObservableObject { + @Published var token = getDataFromKeychain("token") ?? ""; + @Published var mode = valueToMode(getDataFromKeychain("mode") ?? "fxt") + @Published var account_id: String? = nil + + func updateAccountId() { + Task { + self.account_id = try await Request(self).getAccount() + } + } + + func modeToUrl() -> String { + switch mode { + case .FXTrade: + return "fxtrade" + case .FXPractice: + return "fxpractice" + } + } +} diff --git a/OandaChandler/Data/Candles.swift b/OandaChandler/Data/Candles.swift new file mode 100644 index 0000000..2ec85a6 --- /dev/null +++ b/OandaChandler/Data/Candles.swift @@ -0,0 +1,54 @@ +import SwiftUI +import Combine +import Foundation +import Dispatch + +struct CandleData: Decodable { + let candles: [Candle] +} + +struct CandlePricing: Decodable { + let o: String + let h: String + let l: String + let c: String +} + +struct Candle: Decodable { + let complete: Bool + let volume: Int + let time: String + let mid: CandlePricing? + let bid: CandlePricing? + let ask: CandlePricing? +} + +enum CandlePricingType: String, CaseIterable, Identifiable { + var id: String { rawValue } + + case mid + case bid + case ask + + func getName() -> String { + switch self { + case .mid: + return "Mid" + case .bid: + return "Bid" + case .ask: + return "Ask" + } + } + + func getUrl() -> String { + switch self { + case .mid: + return "M" + case .bid: + return "B" + case .ask: + return "A" + } + } +} diff --git a/OandaChandler/Data/Date.swift b/OandaChandler/Data/Date.swift new file mode 100644 index 0000000..70d4cc2 --- /dev/null +++ b/OandaChandler/Data/Date.swift @@ -0,0 +1,28 @@ +import Foundation + +extension Date { + static func ISOStringFromDate(date: Date) -> String { + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "en_US_POSIX") + dateFormatter.timeZone = TimeZone(abbreviation: "GMT") + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS" + + return dateFormatter.string(from: date).appending("Z") + } + + static func dateFromISOString(string: String) -> Date? { + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "en_US_POSIX") + dateFormatter.timeZone = TimeZone.autoupdatingCurrent + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" + + return dateFormatter.date(from: string) + } +} + +func formatDateString(_ dateString: String) -> String { + var res = dateString.replacingOccurrences(of: "-", with: "."); + res = dateString.replacingOccurrences(of: ":", with: "."); + res = dateString.replacingOccurrences(of: "T", with: ","); + return String(res[.. String { + switch self { + case .m1: + return "1 Minute" + case .m2: + return "2 Minute" + case .m5: + return "5 Minute" + case .m10: + return "10 Minute" + case .m15: + return "15 Minute" + case .m30: + return "30 Minute" + case .h1: + return "1 Hour" + case .h2: + return "2 Hour" + case .h4: + return "4 Hour" + } + } + + func getUrl() -> String { + return self.rawValue + } +} + + diff --git a/OandaChandler/KeychainController.swift b/OandaChandler/KeychainController.swift new file mode 100644 index 0000000..0a3ebcd --- /dev/null +++ b/OandaChandler/KeychainController.swift @@ -0,0 +1,59 @@ +// +// KeychainController.swift +// OandaChandler +// +// Created by Paweł Karaś on 31/12/2022. +// + +import Foundation +import Security + +let keychainService = Bundle.main.bundleIdentifier // replace with your app's unique identifier + +// Store data in the keychain +func storeDataInKeychain(_ key: String, data: String) { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: keychainService!, + kSecAttrAccount as String: key, + kSecValueData as String: data.data(using: .utf8)! + ] + Task { + let status = SecItemAdd(query as CFDictionary, nil) + if status != errSecSuccess { + let attributes: [String: Any] = [ + kSecAttrAccount as String: key, + kSecValueData as String: data.data(using: .utf8)! + ] + let result = SecItemUpdate(query as CFDictionary, attributes as CFDictionary) + if result != errSecSuccess { + print("Error storing data in keychain: \(status)") + } + } + } +} + +// Retrieve data from the keychain +func getDataFromKeychain(_ key: String) -> String? { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: keychainService!, + kSecAttrAccount as String: key, + kSecReturnData as String: true, + kSecMatchLimit as String: kSecMatchLimitOne + ] + var result: AnyObject? + let status = SecItemCopyMatching(query as CFDictionary, &result) + if status == errSecSuccess { + if let res = result as? Data { + return String(decoding: res, as: UTF8.self) + } else { + return nil + } + } else if status == errSecItemNotFound { + return nil + } else { + print("Error retrieving data from keychain: \(status)") + return nil + } +} diff --git a/OandaChandler/OandaChandlerApp.swift b/OandaChandler/OandaChandlerApp.swift index 63e35f2..7b2f931 100644 --- a/OandaChandler/OandaChandlerApp.swift +++ b/OandaChandler/OandaChandlerApp.swift @@ -1,23 +1,82 @@ -// -// TradingCronicalApp.swift -// TradingCronical -// -// Created by Paweł Karaś on 27/12/2022. -// - import SwiftUI +enum SideBarItem: String, Identifiable, CaseIterable { + var id: String { rawValue } + + case export = "Export" + case auth = "Authenticate" +} + +func sideBarIcon(_ item: SideBarItem) -> String { + switch item { + case .export: + return "square.and.arrow.down" + case .auth: + return "key" + } +} + + @main struct TradingCronicalApp: App { + @State var sideBarVisibility: NavigationSplitViewVisibility = .doubleColumn + @State var selected = getDataFromKeychain("token") != nil ? SideBarItem.export : SideBarItem.auth + @StateObject var authData = AuthData(); + @State var granuality = Granularity.m1 + @State var instruments = [] + var body: some Scene { WindowGroup { ZStack { - LinearGradient(colors: [ - Color(red: 95/255, green: 42/255, blue: 0), - Color(red: 40/255, green: 17/255, blue: 0) - ], startPoint: .topLeading, endPoint: .bottom) - ContentView() - }.edgesIgnoringSafeArea(.all) - }.windowStyle(HiddenTitleBarWindowStyle()) + // Background Gradient +// LinearGradient(colors: [ +// Color(red: 95/255, green: 42/255, blue: 0), +// Color(red: 40/255, green: 17/255, blue: 0) +// ], startPoint: .topLeading, endPoint: .bottom) + NavigationSplitView(columnVisibility: $sideBarVisibility) { + List(SideBarItem.allCases, selection: $selected) { item in + NavigationLink(value: item) { + Image(systemName: sideBarIcon(item)).foregroundColor(.accentColor) + Text(item.rawValue.localizedCapitalized) + }.disabled(self.authData.account_id == nil && item == .export) + } + } detail: { + switch selected { + case .auth: + Auth() + .environmentObject(authData) + .frame(maxHeight: .infinity, alignment: .top) + case .export: + ContentView() + .environmentObject(authData) + } + }.onChange(of: authData.account_id) { auth in + if auth == nil { + selected = SideBarItem.auth + } + }.onAppear { + if authData.account_id == nil { + selected = SideBarItem.auth + } + } + } + .edgesIgnoringSafeArea(.all) + .toolbar { + ToolbarItemGroup(placement: .navigation, content: { + Button(action: { + self.authData.updateAccountId() + }, label: { + if self.authData.account_id != nil { + Image(systemName: "wifi") + } else { + Image(systemName: "wifi.slash") + } + }).help("Reconnect") + .animation(.default, value: self.authData.account_id) + .onAppear(perform: self.authData.updateAccountId) + }) + } + }.windowStyle(.hiddenTitleBar) + .windowToolbarStyle(.unified) } } diff --git a/OandaChandler/Request.swift b/OandaChandler/Request.swift new file mode 100644 index 0000000..64134a9 --- /dev/null +++ b/OandaChandler/Request.swift @@ -0,0 +1,74 @@ +// +// Request.swift +// OandaChandler +// +// Created by Paweł Karaś on 01/01/2023. +// + +import Foundation +import SwiftUI + +struct RequestError: Error { + var value: String +} + +class Request: ObservableObject { + var authData: AuthData + + init(_ authData: AuthData) { + self.authData = authData + } + + private func request(_ url: [String], query rawQuery: [String:String] = [:]) async throws -> (Data, URLResponse) { + let query = rawQuery.map { (key, value) in "\(key)=\(value)" } + var uri = (["https://api-\(authData.modeToUrl()).oanda.com/v3/"] + url).joined(separator: "/") + if !query.isEmpty { uri += "?\(query.joined(separator: "&"))" } + let url = URL(string: uri)! + var req = URLRequest(url: url) + req.addValue("Bearer \(authData.token)", forHTTPHeaderField: "Authorization") + return try await URLSession.shared.data(for: req) + } + + func getAccount() async throws -> String? { + let (data, _) = try await self.request(["accounts"]) + if let data = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any], + let array = data["accounts"] as? [[String: Any]], + let id = array[0]["id"] as? String { + return id + } + return nil + } + + func getInstruments() async throws -> [String]? { + guard let account_id = authData.account_id else { + return nil + } + var result: [String] = [] + let (data, _) = try await self.request(["accounts", account_id, "instruments"]) + if let data = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any], + let instruments = data["instruments"] as? [[String: Any]] { + for instrument in instruments { + if let name = instrument["name"] as? String { + result.append(name) + } + } + } + return result + } + + func getCandles(from: Date, to: Date, gran: Granularity, instrument: String, pricing: CandlePricingType) async throws -> Result { + let fromString = Date.ISOStringFromDate(date: from) + let toString = Date.ISOStringFromDate(date: to) + let (data, _) = try await self.request(["instruments", instrument, "candles"], query: [ + "granularity": gran.getUrl(), + "from": fromString, + "to": toString, + "price": pricing.getUrl() + ]) + do { + return .success(try JSONDecoder().decode(CandleData.self, from: data)) + } catch { + return .failure(RequestError(value: String(decoding: data, as: UTF8.self))) + } + } +} diff --git a/OandaChandler/Views/Auth.swift b/OandaChandler/Views/Auth.swift new file mode 100644 index 0000000..607eda1 --- /dev/null +++ b/OandaChandler/Views/Auth.swift @@ -0,0 +1,55 @@ +// +// Auth.swift +// OandaChandler +// +// Created by Paweł Karaś on 31/12/2022. +// + +import Foundation +import SwiftUI + +struct Auth: View { + @EnvironmentObject var authData: AuthData + @FocusState var isTokenFocused: Bool + + func getAccountId() { + Task { + authData.account_id = try await Request(authData).getAccount() + } + } + + var body: some View { + VStack { + Image(systemName: "key.viewfinder") + .resizable() + .frame(width: 50, height: 50) + .foregroundColor(.accentColor) + .padding() + Text("Oanda API Token").font(.largeTitle) + .padding(.bottom) + VStack { + HStack { + SecureField("Insert here your Oanda API Token", text: $authData.token) + .focused($isTokenFocused).onChange(of: authData.token) { item in + storeDataInKeychain("token", data: authData.token) + getAccountId() + }.frame(width: 200) + Picker("", selection: $authData.mode) { + Text("FX Trade").tag(OandaMode.FXTrade) + Text("FX Practice").tag(OandaMode.FXPractice) + } .onChange(of: authData.mode) { mode in + storeDataInKeychain("mode", data: modeToValue(authData.mode)) + getAccountId() + }.frame(width: 200) + } + Text("The api token will be stored securely in your keychain.").font(.footnote) + } + } + } +} + +struct Auth_Previews: PreviewProvider { + static var previews: some View { + Auth() + } +} diff --git a/OandaChandler/Views/Creating.swift b/OandaChandler/Views/Creating.swift new file mode 100644 index 0000000..4f5c2ec --- /dev/null +++ b/OandaChandler/Views/Creating.swift @@ -0,0 +1,185 @@ +// +// CreatingPhase.swift +// TradingChronical +// +// Created by Paweł Karaś on 28/12/2022. +// + +import Foundation +import SwiftUI + +enum OandaMode { + case FXTrade + case FXPractice +} + +func modeToValue(_ mode: OandaMode) -> String { + switch mode { + case .FXTrade: + return "fxt" + case .FXPractice: + return "fxp" + } +} + +func valueToMode(_ value: String) -> OandaMode { + switch value { + case "fxt": + return OandaMode.FXTrade + default: + return OandaMode.FXPractice + } +} + +func hour(_ amount: Double) -> TimeInterval { + return 60 * 60 * amount +} + +struct CreatingPhase: View { + @EnvironmentObject var authData: AuthData + @Binding var state: AppState + @Binding var candles: [Candle] + @Binding var error: String? + @Binding var progress: Double + @State var from = Date() + @State var to = Date() + @State var granularity = Granularity.m1 + @State var instruments: [String] = [] + @State var instrument = "GBP_USD" + @State var candlePricing = CandlePricingType.bid + + func fetchCandles(cb: (Double) -> Void) async { + let request = Request(authData) + let hour_count: Double = 8 + let fromDate = Calendar.current.date(bySettingHour: 1, minute: 0, second: 0, of: from)! + let toDate = Calendar.current.date(bySettingHour: 1, minute: 0, second: 0, of: to)! + // Get the total amount of time units to calculate + let interval = toDate.timeIntervalSince(fromDate) + let hours = interval / hour(hour_count) as Double + // Currently processed time unit + var index = 0 + // Variables used to iterate over time + var currentDate = fromDate + var nextDate = fromDate + hour(hour_count) + // Cannot calculate the range of just today + if self.from == self.to { + self.error = "Please select a bigger timestamp" + return + } + // Cannot download candles from the future + if self.to > Date() { + self.error = "Cannot download candles from the future (You wish... 💸)" + return + } + // Result of this function containing all fetched candles + var result: [(Int, [Candle])] = [] + while (nextDate < to) { + let batch = await withTaskGroup(of: Result<(Int, [Candle]), RequestError>?.self) { group -> [(Int, [Candle])] in + // Create eight parallel workers that fetch the candles + for _ in 0...8 { + if (nextDate >= to) { + break + } + let from = currentDate + let to = nextDate + let idx = index + index += 1 + group.addTask { + do { + return try await request + .getCandles(from: from, to: to, gran: self.granularity, instrument: self.instrument, pricing: self.candlePricing) + .map { (idx, $0.candles) } + } catch { + return nil + } + } + currentDate = nextDate + nextDate += hour(hour_count) + } + var result: [(Int, [Candle])] = [] + // Aggregate the results + for await value in group { + if let resultVal = value { + switch resultVal { + case .success(let candles): + result.append(candles) + case .failure(let error): + self.error = error.value + } + } + } + return result + } + cb(Double(index) / hours) + result += batch + } + // Sort the concurrently fetched candles + self.candles = result + .sorted(by: { (a, b) in a.0 < b.0 }) + .map { $0.1 } + .reduce([], +) + } + + var body: some View { + VStack { + Text("Specify the configuration").font(.largeTitle).padding() + HStack { + Picker("", selection: $granularity) { + ForEach(Granularity.allCases) { gran in + Text(gran.getName()).tag(gran) + } + }.frame(width: 100) + if !self.instruments.isEmpty { + Picker("", selection: $instrument) { + ForEach(instruments, id: \.self) { instr in + Text(instr).tag(instr) + } + }.frame(width: 100) + } else { + ProgressView().onAppear { + Task { + self.authData.account_id = try await Request(authData).getAccount() + if let instruments = try await Request(authData).getInstruments() { + self.instruments = instruments + } + } + }.padding() + } + Picker("", selection: $candlePricing) { + ForEach(CandlePricingType.allCases) { pricing in + Text(pricing.getName()).tag(pricing) + } + }.frame(width: 100) + } + HStack{ + DatePicker( + "From", + selection: $from, + displayedComponents: [.date] + ).datePickerStyle(.graphical).frame(width: 200) + DatePicker( + "To", + selection: $to, + displayedComponents: [.date] + ).datePickerStyle(.graphical).frame(width: 200) + }.padding() + Button(action: { + storeDataInKeychain("token", data: self.authData.token) + storeDataInKeychain("mode", data: modeToValue(self.authData.mode)) + self.state = .Fetching + Task { + await self.fetchCandles { value in + DispatchQueue.main.async { + self.progress = value + } + } + if !self.candles.isEmpty { + self.state = .Success + } else { + self.state = .Error + } + } + }) { Text("Submit") } + }.padding().frame(maxHeight: .infinity, alignment: .top) + } +}