From b0f2f6a8d976952fcb9c9265c10129a520c509b9 Mon Sep 17 00:00:00 2001 From: AKosylo Date: Tue, 5 Dec 2023 14:56:25 +0100 Subject: [PATCH] DXFeedLiveIpfSample to playground sample --- DXFeedFramework.xcodeproj/project.pbxproj | 6 +- README.md | 2 +- .../ConvertTapeFile.playground/Contents.swift | 2 +- .../Contents.swift | 69 +++++++++++ .../contents.xcplayground | 4 + .../Contents.swift | 2 +- .../Contents.swift | 2 +- Samples/Tools/LiveIpfSample.swift | 116 ------------------ Samples/Tools/main.swift | 2 - 9 files changed, 79 insertions(+), 126 deletions(-) create mode 100644 Samples/Playgrounds/DXFeedLiveIpfSample.playground/Contents.swift create mode 100644 Samples/Playgrounds/DXFeedLiveIpfSample.playground/contents.xcplayground delete mode 100644 Samples/Tools/LiveIpfSample.swift diff --git a/DXFeedFramework.xcodeproj/project.pbxproj b/DXFeedFramework.xcodeproj/project.pbxproj index 0a0705769..fc4a059d3 100644 --- a/DXFeedFramework.xcodeproj/project.pbxproj +++ b/DXFeedFramework.xcodeproj/project.pbxproj @@ -210,7 +210,6 @@ 6486B9792AD04F4000D8D5FA /* OrderBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6486B9782AD04F4000D8D5FA /* OrderBase.swift */; }; 6486B97B2AD0517A00D8D5FA /* OrderAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6486B97A2AD0517A00D8D5FA /* OrderAction.swift */; }; 6486B97D2AD057F200D8D5FA /* OrderSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6486B97C2AD057F200D8D5FA /* OrderSource.swift */; }; - 6486B97F2AD4167800D8D5FA /* LiveIpfSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6486B97E2AD4167800D8D5FA /* LiveIpfSample.swift */; }; 648BD5692AC450D6004A3A95 /* ConnectTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648BD5682AC450D6004A3A95 /* ConnectTool.swift */; }; 648BD56B2AC4576F004A3A95 /* HelpTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648BD56A2AC4576F004A3A95 /* HelpTool.swift */; }; 648BD56D2AC56A04004A3A95 /* SubscriptionUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648BD56C2AC56A04004A3A95 /* SubscriptionUtils.swift */; }; @@ -542,6 +541,7 @@ 6406F2562AD9820700B58C42 /* NativePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativePublisher.swift; sourceTree = ""; }; 6406F25A2AD987EB00B58C42 /* PublisherTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublisherTest.swift; sourceTree = ""; }; 640885C42B1F477A00E6CF88 /* DxFeedIpfConnect.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = DxFeedIpfConnect.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 640885C52B1F553C00E6CF88 /* DXFeedLiveIpfSample.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = DXFeedLiveIpfSample.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 6408BB712B1F2D14005D7797 /* WriteTapeFile.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = WriteTapeFile.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 64098F492ACD6AF20020D741 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 64098F4C2ACD73820020D741 /* PerfTestEventListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerfTestEventListener.swift; sourceTree = ""; }; @@ -690,7 +690,6 @@ 6486B9782AD04F4000D8D5FA /* OrderBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderBase.swift; sourceTree = ""; }; 6486B97A2AD0517A00D8D5FA /* OrderAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderAction.swift; sourceTree = ""; }; 6486B97C2AD057F200D8D5FA /* OrderSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderSource.swift; sourceTree = ""; }; - 6486B97E2AD4167800D8D5FA /* LiveIpfSample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveIpfSample.swift; sourceTree = ""; }; 648BD5682AC450D6004A3A95 /* ConnectTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectTool.swift; sourceTree = ""; }; 648BD56A2AC4576F004A3A95 /* HelpTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpTool.swift; sourceTree = ""; }; 648BD56C2AC56A04004A3A95 /* SubscriptionUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUtils.swift; sourceTree = ""; }; @@ -966,7 +965,6 @@ 641BDD572AC71CCE00236B78 /* LatencyTestTool.swift */, 645BE8512AC31E7C0028243D /* PerfTestTool.swift */, 648BD5682AC450D6004A3A95 /* ConnectTool.swift */, - 6486B97E2AD4167800D8D5FA /* LiveIpfSample.swift */, 64FFE59E2AD430E4003D3353 /* ScheduleSample.swift */, 645CD0032AE145E600F99FCF /* DumpTool.swift */, 648BD56A2AC4576F004A3A95 /* HelpTool.swift */, @@ -1009,6 +1007,7 @@ 6435EE3C2B1F1E9200E8496C /* PrintQuoteEvents.playground */, 6408BB712B1F2D14005D7797 /* WriteTapeFile.playground */, 640885C42B1F477A00E6CF88 /* DxFeedIpfConnect.playground */, + 640885C52B1F553C00E6CF88 /* DXFeedLiveIpfSample.playground */, ); path = Playgrounds; sourceTree = ""; @@ -2121,7 +2120,6 @@ 647426AF2ABC93900012F793 /* EventCode+String.swift in Sources */, 645BE8542AC3229D0028243D /* ToolsCommand.swift in Sources */, 644FE5D12AC1F34000580E3A /* LatencyMetricsPrinter.swift in Sources */, - 6486B97F2AD4167800D8D5FA /* LiveIpfSample.swift in Sources */, 645BE8522AC31E7C0028243D /* PerfTestTool.swift in Sources */, 645CD0042AE145E600F99FCF /* DumpTool.swift in Sources */, 641E45F92B1DE51700649363 /* EventsListener.swift in Sources */, diff --git a/README.md b/README.md index 83ee57687..f2748eaae 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,7 @@ sudo /usr/bin/xattr -r -d com.apple.quarantine - [x] [PrintQuoteEvents](https://github.com/dxFeed/dxfeed-graal-swift-api/blob/swift/Samples/Playgrounds/PrintQuoteEvents.playground/Contents.swift) is a simple demonstration of how to subscribe to the `Quote` event, using a `DxFeed` instance singleton and `dxfeed.properties` file - [x] [WriteTapeFile](https://github.com/dxFeed/dxfeed-graal-swift-api/blob/swift/Samples/Playgrounds/WriteTapeFile.playground/Contents.swift) is a simple demonstration of how to write events to a tape file - [x] [DxFeedIpfConnect](https://github.com/dxFeed/dxfeed-graal-swift-api/blob/swift/Samples/Playgrounds/DxFeedIpfConnect.playground/Contents.swift) is a simple demonstration of how to get Instrument Profiles -- [x] [DXFeedLiveIpfSample](https://github.com/dxFeed/dxfeed-graal-swift-api/blob/swift/Samples/Tools/LiveIpfSample.swift) +- [x] [DXFeedLiveIpfSample](https://github.com/dxFeed/dxfeed-graal-swift-api/blob/swift/Samples/Playgrounds/DXFeedLiveIpfSample.playground/Contents.swift) is a simple demonstration of how to get live updates for Instrument Profiles - [ ] DxFeedPublishProfiles is a simple demonstration of how to publish market events - [x] [ScheduleSample](https://github.com/dxFeed/dxfeed-graal-swift-api/blob/swift/Samples/Tools/ScheduleSample.swift) diff --git a/Samples/Playgrounds/ConvertTapeFile.playground/Contents.swift b/Samples/Playgrounds/ConvertTapeFile.playground/Contents.swift index 2a4d08722..7b9c5b228 100644 --- a/Samples/Playgrounds/ConvertTapeFile.playground/Contents.swift +++ b/Samples/Playgrounds/ConvertTapeFile.playground/Contents.swift @@ -1,7 +1,7 @@ import Foundation import DXFeedFramework -//Empty Listener with handler +// Empty Listener with handler class Listener: DXEventListener, Hashable { static func == (lhs: Listener, rhs: Listener) -> Bool { diff --git a/Samples/Playgrounds/DXFeedLiveIpfSample.playground/Contents.swift b/Samples/Playgrounds/DXFeedLiveIpfSample.playground/Contents.swift new file mode 100644 index 000000000..74885ca00 --- /dev/null +++ b/Samples/Playgrounds/DXFeedLiveIpfSample.playground/Contents.swift @@ -0,0 +1,69 @@ +import Cocoa +import PlaygroundSupport +import DXFeedFramework + +class Listener: DXInstrumentProfileUpdateListener, Hashable { + // Data model to keep all instrument profiles mapped by their ticker symbol + private var profiles = [String: InstrumentProfile]() + let collector: DXInstrumentProfileCollector + + init(_ collector: DXInstrumentProfileCollector) { + self.collector = collector + } + + + func instrumentProfilesUpdated(_ instruments: [DXFeedFramework.InstrumentProfile]) { + // We can observe REMOVED elements - need to add necessary filtering + // See javadoc for InstrumentProfileCollector for more details + + // (1) We can either process instrument profile updates manually + instruments.forEach { ipf in + if ipf.getIpfType() == .removed { + // Profile was removed - remove it from our data model + self.profiles.removeValue(forKey: ipf.symbol) + } else { + // Profile was updated - collector only notifies us if profile was changed + self.profiles[ipf.symbol] = ipf + } + } + print( +""" + +Instrument Profiles: +Total number of profiles (1): \(self.profiles.count) +Last modified: \( +(try? DXTimeFormat.defaultTimeFormat?.withMillis?.format(value: collector.getLastUpdateTime())) ?? "" +) +""" +) + } + + static func == (lhs: Listener, rhs: Listener) -> Bool { + return lhs === rhs + } + + func hash(into hasher: inout Hasher) { + hasher.combine("\(self):\(stringReference(self))") + } + +} + + +// An sample that demonstrates a subscription using InstrumentProfile. +let defaultIpfUrl = "https://demo:demo@tools.dxfeed.com/ipf" + +let collector = try DXInstrumentProfileCollector() +let connection = try DXInstrumentProfileConnection(defaultIpfUrl, collector) +// Update period can be used to re-read IPF files, not needed for services supporting IPF "live-update" +try connection.setUpdatePeriod(60000) +try connection.start() +// It is possible to add listener after connection is started - updates will not be missed in this case +let listener = Listener(collector) +try collector.add(listener: listener) + + +// infinity execution +PlaygroundPage.current.needsIndefiniteExecution = true + +// to finish execution run this line +PlaygroundPage.current.finishExecution() diff --git a/Samples/Playgrounds/DXFeedLiveIpfSample.playground/contents.xcplayground b/Samples/Playgrounds/DXFeedLiveIpfSample.playground/contents.xcplayground new file mode 100644 index 000000000..1c968e7d1 --- /dev/null +++ b/Samples/Playgrounds/DXFeedLiveIpfSample.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Samples/Playgrounds/DxFeedIpfConnect.playground/Contents.swift b/Samples/Playgrounds/DxFeedIpfConnect.playground/Contents.swift index 2bba91a78..842f47ec3 100644 --- a/Samples/Playgrounds/DxFeedIpfConnect.playground/Contents.swift +++ b/Samples/Playgrounds/DxFeedIpfConnect.playground/Contents.swift @@ -2,7 +2,7 @@ import Cocoa import PlaygroundSupport import DXFeedFramework -//Empty Listener with handler +// Empty Listener with handler class Listener: DXEventListener, Hashable { static func == (lhs: Listener, rhs: Listener) -> Bool { diff --git a/Samples/Playgrounds/PrintQuoteEvents.playground/Contents.swift b/Samples/Playgrounds/PrintQuoteEvents.playground/Contents.swift index 3d4508365..2c7778b9a 100644 --- a/Samples/Playgrounds/PrintQuoteEvents.playground/Contents.swift +++ b/Samples/Playgrounds/PrintQuoteEvents.playground/Contents.swift @@ -2,7 +2,7 @@ import Foundation import PlaygroundSupport import DXFeedFramework -//Empty Listener with handler +// Empty Listener with handler class Listener: DXEventListener, Hashable { static func == (lhs: Listener, rhs: Listener) -> Bool { diff --git a/Samples/Tools/LiveIpfSample.swift b/Samples/Tools/LiveIpfSample.swift deleted file mode 100644 index b9abb59f5..000000000 --- a/Samples/Tools/LiveIpfSample.swift +++ /dev/null @@ -1,116 +0,0 @@ -// -// LiveIpfSample.swift -// Tools -// -// Created by Aleksey Kosylo on 09.10.23. -// - -import Foundation -import DXFeedFramework - -class LiveIpfSample: ToolsCommand { - var isTools: Bool = false - lazy var name = { - stringReference(self) - }() - - private var ipfList = [InstrumentProfile]() - private var buffer = [String: InstrumentProfile]() - - static let defaultIpfUrl = "https://demo:demo@tools.dxfeed.com/ipf" - - var cmd = "DXFeedLiveIpfSample" - - var shortDescription = "An sample that demonstrates a subscription using InstrumentProfile." - - var fullDescription: String = - """ - An sample that demonstrates a subscription using InstrumentProfile. - - Usage: - usage: DXFeedLiveIpfSample [] - - Where: - - is URL for the instruments profiles. - Example of url: " + \(LiveIpfSample.defaultIpfUrl) - - """ - var collector: DXInstrumentProfileCollector? - var connection: DXInstrumentProfileConnection? - - private lazy var arguments: Arguments = { - do { - let arguments = try Arguments(ProcessInfo.processInfo.arguments, requiredNumberOfArguments: 1) - return arguments - } catch { - print(fullDescription) - exit(0) - } - }() - - func execute() { - do { - collector = try DXInstrumentProfileCollector() - connection = try DXInstrumentProfileConnection(arguments.count > 1 ? - arguments[1] : - LiveIpfSample.defaultIpfUrl, collector!) - // Update period can be used to re-read IPF files, not needed for services supporting IPF "live-update" - try connection?.setUpdatePeriod(60000) - connection?.add(listener: self) - try connection?.start() - // We can wait until we get first full snapshot of instrument profiles - connection?.waitUntilCompleted(10000) - // It is possible to add listener after connection is started - updates will not be missed in this case - try collector?.add(listener: self) - } catch { - print("Error: \(error)") - } - - _ = readLine() - } -} - -extension LiveIpfSample: DXInstrumentProfileConnectionListener { - func connectionDidChangeState(old: DXInstrumentProfileConnectionState, new: DXInstrumentProfileConnectionState) { - print("Connection state: \(new)") - } -} - -extension LiveIpfSample: DXInstrumentProfileUpdateListener { - func instrumentProfilesUpdated(_ instruments: [DXFeedFramework.InstrumentProfile]) { - instruments.forEach { ipf in - if ipf.getIpfType() == .removed { - self.buffer.removeValue(forKey: ipf.symbol) - } else { - self.buffer[ipf.symbol] = ipf - } - } - self.ipfList = self.buffer.map { _, value in - value - }.sorted(by: { ipf1, ipf2 in - ipf1.symbol < ipf2.symbol - }) - print( -""" - -Instrument Profiles: -Total number of profiles (1): \(self.ipfList.count) -Last modified: \( -(try? DXTimeFormat.defaultTimeFormat?.withMillis?.format(value: collector?.getLastUpdateTime() ?? 0)) ?? "" -) -""" -) - } -} - -extension LiveIpfSample: Hashable { - static func == (lhs: LiveIpfSample, rhs: LiveIpfSample) -> Bool { - return lhs === rhs || lhs.name == rhs.name - } - - func hash(into hasher: inout Hasher) { - hasher.combine(name) - } - -} diff --git a/Samples/Tools/main.swift b/Samples/Tools/main.swift index db55e4bc0..1f0a4ad43 100644 --- a/Samples/Tools/main.swift +++ b/Samples/Tools/main.swift @@ -10,9 +10,7 @@ import Foundation let commands: [ToolsCommand] = [PerfTestTool(), ConnectTool(), LatencyTestTool(), - LiveIpfSample(), ScheduleSample(), - IpfConnect(), DumpTool(), HelpTool()]