From 34dec3c8a14f29754e1e7ff03b6481e2af1116ae Mon Sep 17 00:00:00 2001 From: AKosylo Date: Mon, 4 Dec 2023 17:11:59 +0100 Subject: [PATCH] add samples: DxFeedFileParser ConvertTapeFile --- DXFeedFramework.xcodeproj/project.pbxproj | 12 +++ DXFeedFramework/Events/EventCode.swift | 6 ++ README.md | 6 +- .../ConvertTapeFile.playground/Contents.swift | 96 +++++++++++++++++++ .../Resources/ConvertTapeFile.in | 23 +++++ .../contents.xcplayground | 4 + .../Contents.swift | 59 ++++++++++++ .../contents.xcplayground | 4 + Samples/Tools/ConnectTool.swift | 91 +++++++++++++++++- 9 files changed, 297 insertions(+), 4 deletions(-) create mode 100644 Samples/Playgrounds/ConvertTapeFile.playground/Contents.swift create mode 100644 Samples/Playgrounds/ConvertTapeFile.playground/Resources/ConvertTapeFile.in create mode 100644 Samples/Playgrounds/ConvertTapeFile.playground/contents.xcplayground create mode 100644 Samples/Playgrounds/DxFeedFileParser.playground/Contents.swift create mode 100644 Samples/Playgrounds/DxFeedFileParser.playground/contents.xcplayground diff --git a/DXFeedFramework.xcodeproj/project.pbxproj b/DXFeedFramework.xcodeproj/project.pbxproj index b152da2b9..debce8f01 100644 --- a/DXFeedFramework.xcodeproj/project.pbxproj +++ b/DXFeedFramework.xcodeproj/project.pbxproj @@ -630,6 +630,8 @@ 644BD7652A44727000A0BF99 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 644BD7682A44727000A0BF99 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 644BD7882A4475EF00A0BF99 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainAR.storyboard; sourceTree = ""; }; + 644C528D2B1E1CB8002C034C /* ConvertTapeFile.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = ConvertTapeFile.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 644C528F2B1E1D73002C034C /* DxFeedFileParser.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = DxFeedFileParser.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 644FE5D02AC1F34000580E3A /* LatencyMetricsPrinter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatencyMetricsPrinter.swift; sourceTree = ""; }; 645A34942A937C7200709F29 /* BinaryInteger+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BinaryInteger+Ext.swift"; sourceTree = ""; }; 645BE8512AC31E7C0028243D /* PerfTestTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerfTestTool.swift; sourceTree = ""; }; @@ -997,6 +999,15 @@ path = Market; sourceTree = ""; }; + 641E45FD2B1DF67E00649363 /* Playgrounds */ = { + isa = PBXGroup; + children = ( + 644C528F2B1E1D73002C034C /* DxFeedFileParser.playground */, + 644C528D2B1E1CB8002C034C /* ConvertTapeFile.playground */, + ); + path = Playgrounds; + sourceTree = ""; + }; 64262CCA2A4DA64700BA6BA3 /* VisionQuoteTableApp */ = { isa = PBXGroup; children = ( @@ -1390,6 +1401,7 @@ 64D8BB362A39B0DE0071BC88 /* Samples */ = { isa = PBXGroup; children = ( + 641E45FD2B1DF67E00649363 /* Playgrounds */, 6469F8D12A3B400100846831 /* Utils */, 64262CCA2A4DA64700BA6BA3 /* VisionQuoteTableApp */, 644BD75B2A44726F00A0BF99 /* ARQuoteTableApp */, diff --git a/DXFeedFramework/Events/EventCode.swift b/DXFeedFramework/Events/EventCode.swift index d7cb0348d..e2d6a0e9c 100644 --- a/DXFeedFramework/Events/EventCode.swift +++ b/DXFeedFramework/Events/EventCode.swift @@ -50,3 +50,9 @@ public enum EventCode: CaseIterable { /// See ``OptionSale`` case optionSale } + +public extension EventCode { + static func unsupported() -> [EventCode] { + return [.dailyCandle, .configuration, .message, .orderBase] + } +} diff --git a/README.md b/README.md index 199fa3dcb..88d162ebf 100644 --- a/README.md +++ b/README.md @@ -246,12 +246,12 @@ sudo /usr/bin/xattr -r -d com.apple.quarantine ## Samples -- [ ] ConvertTapeFile demonstrates how to convert one tape file to another tape file with optional intermediate processing or filtering -- [ ] DxFeedFileParser is a simple demonstration of how events are read form a tape file +- [x] ConvertTapeFile demonstrates how to convert one tape file to another tape file with optional intermediate processing or filtering +- [x] DxFeedFileParser is a simple demonstration of how events are read form a tape file - [ ] DxFeedSample is a simple demonstration of how to create multiple event listeners and subscribe to `Quote` and `Trade` events - [ ] PrintQuoteEvents is a simple demonstration of how to subscribe to the `Quote` event, using a `DxFeed` instance singleton and `dxfeed.properties` file - [ ] WriteTapeFile 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/Tools/IpfConnect.swift) is a simple demonstration of how to get Instrument Profiles +- [x] [DxFeedIpfConnect](https://github.com/dxFeed/dxfeed-graal-swift-api/blob/swift/Samples/Tools/IpfConnect.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) is a simple demonstration of how to get live updates for Instrument Profiles - [ ] DxFeedPublishProfiles is a simple demonstration of how to publish market events diff --git a/Samples/Playgrounds/ConvertTapeFile.playground/Contents.swift b/Samples/Playgrounds/ConvertTapeFile.playground/Contents.swift new file mode 100644 index 000000000..07ffeebd6 --- /dev/null +++ b/Samples/Playgrounds/ConvertTapeFile.playground/Contents.swift @@ -0,0 +1,96 @@ +import Foundation +import DXFeedFramework + +//Empty Listener with handler +class Listener: DXEventListener, Hashable { + + static func == (lhs: Listener, rhs: Listener) -> Bool { + lhs === rhs + } + + func hash(into hasher: inout Hasher) { + hasher.combine("\(self):\(stringReference(self))") + } + var callback: ([MarketEvent]) -> Void = { _ in } + + func receiveEvents(_ events: [MarketEvent]) { + self.callback(events) + } + + init(overrides: (Listener) -> Listener) { + _ = overrides(self) + } +} + + +let paths = Bundle.main.path(forResource: "ConvertTapeFile.in", ofType: nil) +let tempDirectory = NSTemporaryDirectory() +let fullURL = NSURL.fileURL(withPathComponents: [tempDirectory, "ConvertTapeFile.out"])?.path + +// Determine input and output tapes and specify appropriate configuration parameters. +let inputAddress = "file:\(paths ?? "")[readAs=stream_data,speed=max]" +let outputAddress = "tape:\(fullURL ?? "" )[saveAs=stream_data,format=text]" + +// Create input endpoint configured for tape reading. +let inputEndpoint = try DXEndpoint.builder() + .withRole(.streamFeed) // Prevents event conflation and loss due to buffer overflow. + .withProperty(DXEndpoint.Property.wildcardEnable.rawValue, "true") // Enables wildcard subscription. + .withProperty(DXEndpoint.Property.eventTime.rawValue, "true") // Use provided event times. + .build() + +// Create output endpoint configured for tape writing. +var outputEndpoint = try DXEndpoint.builder() + .withRole(.streamPublisher) // Prevents event conflation and loss due to buffer overflow. + .withProperty(DXEndpoint.Property.wildcardEnable.rawValue, "true") // Enables wildcard subscription. + .withProperty(DXEndpoint.Property.eventTime.rawValue, "true") // Use provided event times. + .build() + +// Create and link event processor for all types of events. +// Note: Set of processed event types could be limited if needed. +let eventTypes: [EventCode] = EventCode.allCases.compactMap { eventCode in + if EventCode.unsupported().contains(eventCode) { + return nil + } else { + return eventCode + } +} + +let feed = inputEndpoint.getFeed() +let subscription = try feed?.createSubscription(eventTypes) + +let listener = Listener { anonymCl in + anonymCl.callback = { events in + // Here event processing occurs. Events could be modified, removed, or new events added. + // For example, the below code adds 1 hour to event times: + // foreach (var e in events) + // { + // e.EventTime += 3600_000 + // } + + // Publish processed events + let publisher = outputEndpoint.getPublisher() + try? publisher?.publish(events: events) + } + return anonymCl +} + +try subscription?.add(listener: listener) + +// Subscribe to all symbols. +// Note: Set of processed symbols could be limited if needed. +try subscription?.addSymbols(WildcardSymbol.all) + +// Connect output endpoint and start output tape writing BEFORE starting input tape reading. +try outputEndpoint.connect(outputAddress) +// Connect input endpoint and start input tape reading AFTER starting output tape writing. +try inputEndpoint.connect(inputAddress) + +// Wait until all data is read and processed, and then gracefully close input endpoint. +try inputEndpoint.awaitNotConnected() +try inputEndpoint.closeAndAWaitTermination() + +// Wait until all data is processed and written, and then gracefully close output endpoint. +try outputEndpoint.awaitProcessed() +try outputEndpoint.closeAndAWaitTermination() + +print("ConvertTapeFile: \(inputAddress) has been successfully tapped to \(outputAddress)") diff --git a/Samples/Playgrounds/ConvertTapeFile.playground/Resources/ConvertTapeFile.in b/Samples/Playgrounds/ConvertTapeFile.playground/Resources/ConvertTapeFile.in new file mode 100644 index 000000000..784b4e67e --- /dev/null +++ b/Samples/Playgrounds/ConvertTapeFile.playground/Resources/ConvertTapeFile.in @@ -0,0 +1,23 @@ +==DXP3 type=tape version=QDS-3.315 time=field +STREAM_DATA +==STREAM_DATA +=Quote EventSymbol EventTime BidTime BidExchangeCode BidPrice BidSize AskTime AskExchangeCode AskPrice AskSize +Quote AAPL 20230216-180402.785+0300 20230216-180402+0300 Q 153.94 2 20230216-180402+0300 Q 153.95 1 +Quote AAPL 20230216-180402.805+0300 20230216-180402+0300 Q 153.94 2 20230216-180402+0300 Z 153.96 2 +Quote AAPL 20230216-180402.871+0300 20230216-180402+0300 Q 153.94 2 20230216-180402+0300 Q 153.95 1 +Quote AAPL 20230216-180402.878+0300 20230216-180402+0300 Q 153.94 2 20230216-180402+0300 Q 153.96 3 +Quote AAPL 20230216-180402.922+0300 20230216-180402+0300 P 153.94 2 20230216-180402+0300 Q 153.96 4 +Quote AAPL 20230216-180402.945+0300 20230216-180402+0300 P 153.94 2 20230216-180402+0300 Q 153.96 3 +Quote AAPL 20230216-180402.954+0300 20230216-180402+0300 P 153.94 2 20230216-180402+0300 Q 153.96 2 +Quote AAPL 20230216-180402.969+0300 20230216-180402+0300 P 153.94 2 20230216-180402+0300 Q 153.96 3 +Quote AAPL 20230216-180402.989+0300 20230216-180402+0300 P 153.94 2 20230216-180402+0300 Q 153.95 1 +Quote AAPL 20230216-180403.005+0300 20230216-180402+0300 P 153.94 2 20230216-180403+0300 Q 153.96 3 +Quote AAPL 20230216-180403.010+0300 20230216-180402+0300 P 153.94 2 20230216-180403+0300 Q 153.96 4 +Quote AAPL 20230216-180403.121+0300 20230216-180402+0300 P 153.94 2 20230216-180403+0300 Q 153.96 3 +Quote AAPL 20230216-180403.149+0300 20230216-180402+0300 P 153.94 2 20230216-180403+0300 Q 153.95 1 +Quote AAPL 20230216-180403.182+0300 20230216-180402+0300 P 153.94 2 20230216-180403+0300 Q 153.96 3 +Quote AAPL 20230216-180403.245+0300 20230216-180402+0300 P 153.94 2 20230216-180403+0300 Q 153.96 4 +Quote AAPL 20230216-180403.309+0300 20230216-180402+0300 P 153.94 2 20230216-180403+0300 Q 153.95 1 +Quote AAPL 20230216-180403.353+0300 20230216-180402+0300 P 153.94 2 20230216-180403+0300 Z 153.96 3 +Quote AAPL 20230216-180403.507+0300 20230216-180403+0300 Q 153.94 2 20230216-180403+0300 Q 153.96 3 +Quote AAPL 20230216-180403.516+0300 20230216-180403+0300 Q 153.95 1 20230216-180403+0300 Z 153.96 1 +Quote AAPL 20230216-180403.523+0300 20230216-180403+0300 Q 153.95 1 20230216-180403+0300 Q 153.96 1 diff --git a/Samples/Playgrounds/ConvertTapeFile.playground/contents.xcplayground b/Samples/Playgrounds/ConvertTapeFile.playground/contents.xcplayground new file mode 100644 index 000000000..1c968e7d1 --- /dev/null +++ b/Samples/Playgrounds/ConvertTapeFile.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Samples/Playgrounds/DxFeedFileParser.playground/Contents.swift b/Samples/Playgrounds/DxFeedFileParser.playground/Contents.swift new file mode 100644 index 000000000..4926c0e65 --- /dev/null +++ b/Samples/Playgrounds/DxFeedFileParser.playground/Contents.swift @@ -0,0 +1,59 @@ +import Foundation +import DXFeedFramework + +//Empty Listener with handler +class Listener: DXEventListener, Hashable { + + static func == (lhs: Listener, rhs: Listener) -> Bool { + lhs === rhs + } + + func hash(into hasher: inout Hasher) { + hasher.combine("\(self):\(stringReference(self))") + } + var callback: ([MarketEvent]) -> Void = { _ in } + + func receiveEvents(_ events: [MarketEvent]) { + self.callback(events) + } + + init(overrides: (Listener) -> Listener) { + _ = overrides(self) + } +} + + +var file: String = "" +var types: [EventCode] = [.quote] +var symbols = "AAPL" +var eventCounter = 0 + +// Create endpoint specifically for file parsing. +var endpoint = try DXEndpoint.create(.streamFeed) +var feed = endpoint.getFeed() + +// Subscribe to a specified event and symbol. +var sub = try feed?.createSubscription(types) +let listener = Listener { anonymCl in + anonymCl.callback = { events in + events.forEach { event in + eventCounter += 1 + print("\(eventCounter): \(event)") + } + } + return anonymCl +} +try sub?.add(listener: listener) + +// Add symbols. +try sub?.addSymbols(symbols) + +// Connect endpoint to a file. +try endpoint.connect("file:\(file)[speed=max]") + +// Wait until file is completely parsed. +try endpoint.awaitNotConnected() + +// Close endpoint when we're done. +// This method will gracefully close endpoint, waiting while data processing completes. +try endpoint.closeAndAWaitTermination() diff --git a/Samples/Playgrounds/DxFeedFileParser.playground/contents.xcplayground b/Samples/Playgrounds/DxFeedFileParser.playground/contents.xcplayground new file mode 100644 index 000000000..1c968e7d1 --- /dev/null +++ b/Samples/Playgrounds/DxFeedFileParser.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Samples/Tools/ConnectTool.swift b/Samples/Tools/ConnectTool.swift index 903c63731..f60efb501 100644 --- a/Samples/Tools/ConnectTool.swift +++ b/Samples/Tools/ConnectTool.swift @@ -61,7 +61,7 @@ Where: func execute() { isQuite = arguments.isQuite - + print(FileManager.default.currentDirectoryPath) arguments.properties.forEach { key, value in try? SystemProperty.setProperty(key, value) } @@ -104,4 +104,93 @@ Where: // Print till input new line _ = readLine() } + + func testConverTapeFile() throws { + class Listener: DXEventListener, Hashable { + + static func == (lhs: Listener, rhs: Listener) -> Bool { + lhs === rhs + } + + func hash(into hasher: inout Hasher) { + hasher.combine("\(self):\(stringReference(self))") + } + var callback: ([MarketEvent]) -> Void = { _ in } + + func receiveEvents(_ events: [MarketEvent]) { + self.callback(events) + } + + init(overrides: (Listener) -> Listener) { + _ = overrides(self) + } + } + + + // Determine input and output tapes and specify appropriate configuration parameters. + let inputAddress = "file:/Users/akosylo/Projects/tapeK2.tape[readAs=stream_data,speed=max]" + let outputAddress = "tape:/Users/akosylo/Projects/tapeK21.tape[saveAs=stream_data,format=text]" + + // Create input endpoint configured for tape reading. + let inputEndpoint = try DXEndpoint.builder() + .withRole(.streamFeed) // Prevents event conflation and loss due to buffer overflow. + .withProperty(DXEndpoint.Property.wildcardEnable.rawValue, "true") // Enables wildcard subscription. + .withProperty(DXEndpoint.Property.eventTime.rawValue, "true") // Use provided event times. + .build() + + // Create output endpoint configured for tape writing. + var outputEndpoint = try DXEndpoint.builder() + .withRole(.streamPublisher) // Prevents event conflation and loss due to buffer overflow. + .withProperty(DXEndpoint.Property.wildcardEnable.rawValue, "true") // Enables wildcard subscription. + .withProperty(DXEndpoint.Property.eventTime.rawValue, "true") // Use provided event times. + .build() + + // Create and link event processor for all types of events. + // Note: Set of processed event types could be limited if needed. + let eventTypes: [EventCode] = EventCode.allCases.compactMap { eventCode in + if EventCode.unsupported().contains(eventCode) { + return nil + } else { + return eventCode + } + } + + let feed = inputEndpoint.getFeed() + let subscription = try feed?.createSubscription(eventTypes) + + let listener = Listener { anonymCl in + anonymCl.callback = { events in + // Here event processing occurs. Events could be modified, removed, or new events added. + // For example, the below code adds 1 hour to event times: + // foreach (var e in events) + // { + // e.EventTime += 3600_000 + // } + + // Publish processed events +// let publisher = outputEndpoint.getPublisher() +// try? publisher?.publish(events: events) + } + return anonymCl + } + + try subscription?.add(listener: listener) + + // Subscribe to all symbols. + // Note: Set of processed symbols could be limited if needed. + try subscription?.addSymbols(WildcardSymbol.all) + + // Connect output endpoint and start output tape writing BEFORE starting input tape reading. + try outputEndpoint.connect(outputAddress) + // Connect input endpoint and start input tape reading AFTER starting output tape writing. + try inputEndpoint.connect(inputAddress) + + // Wait until all data is read and processed, and then gracefully close input endpoint. + try inputEndpoint.awaitNotConnected() + try inputEndpoint.closeAndAWaitTermination() + + // Wait until all data is processed and written, and then gracefully close output endpoint. + try outputEndpoint.awaitProcessed() + try outputEndpoint.closeAndAWaitTermination() + } }