From d951c246f09108f17c7ea44b5b879292329a7990 Mon Sep 17 00:00:00 2001 From: AKosylo Date: Wed, 10 Jan 2024 17:33:59 +0100 Subject: [PATCH] Add fetching GetLastEvent(s) --- DXFeedFramework.xcodeproj/project.pbxproj | 4 + DXFeedFramework/Api/DXFeed.swift | 10 ++ .../Extensions/MarketEvent+Access.swift | 25 +++ DXFeedFramework/Native/Feed/NativeFeed.swift | 74 +++++++++ DXFeedFrameworkTests/DXLastEventTest.swift | 153 ++++++++++++++++++ DXFeedFrameworkTests/EndpointTest.swift | 1 - .../DXFeedconnect.playground/Contents.swift | 4 +- 7 files changed, 268 insertions(+), 3 deletions(-) create mode 100644 DXFeedFrameworkTests/DXLastEventTest.swift diff --git a/DXFeedFramework.xcodeproj/project.pbxproj b/DXFeedFramework.xcodeproj/project.pbxproj index 80fe9b284..9a95ef0ea 100644 --- a/DXFeedFramework.xcodeproj/project.pbxproj +++ b/DXFeedFramework.xcodeproj/project.pbxproj @@ -157,6 +157,7 @@ 645BE8522AC31E7C0028243D /* PerfTestTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 645BE8512AC31E7C0028243D /* PerfTestTool.swift */; }; 645BE8542AC3229D0028243D /* ToolsCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 645BE8532AC3229D0028243D /* ToolsCommand.swift */; }; 645CD0042AE145E600F99FCF /* DumpTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 645CD0032AE145E600F99FCF /* DumpTool.swift */; }; + 646064EA2B4D8973009201E2 /* DXLastEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646064E92B4D8973009201E2 /* DXLastEventTest.swift */; }; 646228512A376B0A0029DC97 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6462284D2A376B0A0029DC97 /* Main.storyboard */; }; 646228522A376B0A0029DC97 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6462284F2A376B0A0029DC97 /* LaunchScreen.storyboard */; }; 646407492A9DF984006FF769 /* InstrumentProfileMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646407482A9DF984006FF769 /* InstrumentProfileMapper.swift */; }; @@ -673,6 +674,7 @@ 645BE8512AC31E7C0028243D /* PerfTestTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerfTestTool.swift; sourceTree = ""; }; 645BE8532AC3229D0028243D /* ToolsCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolsCommand.swift; sourceTree = ""; }; 645CD0032AE145E600F99FCF /* DumpTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DumpTool.swift; sourceTree = ""; }; + 646064E92B4D8973009201E2 /* DXLastEventTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DXLastEventTest.swift; sourceTree = ""; }; 6462284E2A376B0A0029DC97 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 646228502A376B0A0029DC97 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 646407482A9DF984006FF769 /* InstrumentProfileMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstrumentProfileMapper.swift; sourceTree = ""; }; @@ -1561,6 +1563,7 @@ 648C72482B19CA5A00E2FEF3 /* DXExceptionTest.swift */, 641C64B32B347C430023CFAD /* DXObservableSubscriptionTest.swift */, 6423E4682B457000006B208D /* DXTimeSeriesSubscriptionTest.swift */, + 646064E92B4D8973009201E2 /* DXLastEventTest.swift */, ); path = DXFeedFrameworkTests; sourceTree = ""; @@ -2485,6 +2488,7 @@ 6423E4692B457000006B208D /* DXTimeSeriesSubscriptionTest.swift in Sources */, 64ECD67F2A9CF4CB00B36935 /* IPFTests.swift in Sources */, 64ACBCD52A2789EF00032C53 /* TestListener.swift in Sources */, + 646064EA2B4D8973009201E2 /* DXLastEventTest.swift in Sources */, 64098F672ACEB6F70020D741 /* DXConnectionStateTests.swift in Sources */, 648BD56F2AC582AB004A3A95 /* DateTimeParserTest.swift in Sources */, 64ACBCEA2A28DDDA00032C53 /* TestEventListener.swift in Sources */, diff --git a/DXFeedFramework/Api/DXFeed.swift b/DXFeedFramework/Api/DXFeed.swift index 68920f48e..d54e555ae 100644 --- a/DXFeedFramework/Api/DXFeed.swift +++ b/DXFeedFramework/Api/DXFeed.swift @@ -73,3 +73,13 @@ public class DXFeed { types: [type]) } } + +public extension DXFeed { + func getLastEvent(type: MarketEvent) throws -> ILastingEvent? { + return try native.getLastEvent(type: type) + } + + func getLastEvents(types: [MarketEvent]) throws -> [ILastingEvent] { + return try native.getLastEvents(types: types) + } +} diff --git a/DXFeedFramework/Events/Market/Extensions/MarketEvent+Access.swift b/DXFeedFramework/Events/Market/Extensions/MarketEvent+Access.swift index 1d04405e7..14d2a3493 100644 --- a/DXFeedFramework/Events/Market/Extensions/MarketEvent+Access.swift +++ b/DXFeedFramework/Events/Market/Extensions/MarketEvent+Access.swift @@ -73,4 +73,29 @@ extension MarketEvent { public var optionSale: OptionSale { return (self as? OptionSale)! } + /// Use only for event.type which supported ``ILastingEvent`` + public var lastingEvent: ILastingEvent? { + switch self.type { + case .quote: + return self.quote as ILastingEvent + case .profile: + return self.profile as ILastingEvent + case .summary: + return self.summary as ILastingEvent + case .greeks: + return self.greeks as ILastingEvent + case .candle: + return self.candle as ILastingEvent + case .underlying: + return self.underlying as ILastingEvent + case .theoPrice: + return self.theoPrice as ILastingEvent + case .trade: + return self.trade as ILastingEvent + case .tradeETH: + return self.tradeETH as ILastingEvent + default: + return nil + } + } } diff --git a/DXFeedFramework/Native/Feed/NativeFeed.swift b/DXFeedFramework/Native/Feed/NativeFeed.swift index b92d90a99..d66d32aed 100644 --- a/DXFeedFramework/Native/Feed/NativeFeed.swift +++ b/DXFeedFramework/Native/Feed/NativeFeed.swift @@ -11,6 +11,8 @@ import Foundation /// The location of the imported functions is in the header files "dxfg_feed.h". class NativeFeed { let feed: UnsafeMutablePointer? + private let mapper = EventMapper() + deinit { if let feed = feed { let thread = currentThread() @@ -74,4 +76,76 @@ class NativeFeed { event.type.nativeCode())) return NativeTimeSeriesSubscription(native: subscription) } + + func getLastEvent(type: MarketEvent) throws -> ILastingEvent? { + let thread = currentThread() + let inputEvent = try ErrorCheck.nativeCall(thread, + dxfg_EventType_new( + thread, + type.eventSymbol, + type.type.nativeCode()) + ) + defer { + _ = try? ErrorCheck.nativeCall(thread, + dxfg_EventType_release( + thread, + inputEvent)) + } + _ = try ErrorCheck.nativeCall(thread, + dxfg_DXFeed_getLastEvent( + thread, + self.feed, + inputEvent)) + + let event = try mapper.fromNative(native: inputEvent) + return event?.lastingEvent + } + + func getLastEvents(types: [MarketEvent]) throws -> [ILastingEvent] { + let listPointer = UnsafeMutablePointer.allocate(capacity: 1) + listPointer.pointee.size = Int32(types.count) + let classes = UnsafeMutablePointer?> + .allocate(capacity: types.count) + var iterator = classes + let thread = currentThread() + + types.forEach { event in + if let inputEvent = try? ErrorCheck.nativeCall(thread, dxfg_EventType_new( + thread, + event.eventSymbol, + event.type.nativeCode())) { + iterator.initialize(to: inputEvent) + iterator = iterator.successor() + } + } + listPointer.pointee.elements = classes + + defer { + for index in 0.. [MarketEvent] { + + let testQuote = Quote(symbol) + testQuote.bidSize = Double.random(in: 1000.0..<2000.0) + testQuote.askPrice = Double.random(in: 1000.0..<2000.0) + let testTrade = Trade(symbol) + testTrade.price = Double.random(in: 1000.0..<2000.0) + let testTradeEth = TradeETH(symbol) + testTradeEth.price = Double.random(in: 1000.0..<2000.0) + let testProfile = Profile(symbol) + testProfile.highLimitPrice = Double.random(in: 1000.0..<2000.0) + + let summary = Summary(symbol) + summary.dayId = 10 + var result = [testQuote, testTrade, testTradeEth, testProfile, summary] + if let candleSymbol = try? CandleSymbol.valueOf(symbol) { + let candle = Candle(candleSymbol) + result.append(candle) + } + let greeks = Greeks(symbol) + greeks.price = Double.random(in: 1000.0..<2000.0) + result.append(greeks) + + let underlying = Underlying(symbol) + underlying.volatility = Double.random(in: 1000.0..<2000.0) + result.append(underlying) + + let theoPrice = TheoPrice(symbol) + theoPrice.delta = Double.random(in: 1000.0..<2000.0) + result.append(theoPrice) + + return result + } + + private func checkEvent(_ event: ILastingEvent, in events: [MarketEvent]) { + func getEvent(type: EventCode) -> MarketEvent { + events.first { mEvent in + mEvent.type == type + }! + } + + switch event { + case let last as Quote: + let test = getEvent(type: last.type).quote + XCTAssert((last.askPrice ~== test.askPrice) && + (last.bidSize ~== test.bidSize)) + case let last as Trade: + let test = getEvent(type: last.type).trade + XCTAssert(test.price ~== last.price) + case let last as TradeETH: + let test = getEvent(type: last.type).tradeETH + XCTAssert(test.price ~== last.price) + case let last as Profile: + let test = getEvent(type: last.type).profile + XCTAssert(last.highLimitPrice ~== test.highLimitPrice) + case let last as Candle: + let test = getEvent(type: last.type).candle + XCTAssert(last.eventSymbol == test.eventSymbol) + case let last as Summary: + let test = getEvent(type: last.type).summary + XCTAssert(last.dayId == test.dayId) + case let last as Greeks: + let test = getEvent(type: last.type).greeks + XCTAssert(last.price ~== test.price) + case let last as Underlying: + let test = getEvent(type: last.type).underlying + XCTAssert(last.volatility ~== test.volatility) + case let last as TheoPrice: + let test = getEvent(type: last.type).theoPrice + XCTAssert(last.delta ~== test.delta) + default: + XCTAssert(false, "Unhandled last event \(event)") + } + } + + func testGetLastEvents() { + let symbol = "AAPL_TEST" + + let events = testEvents(symbol) + getLastEvents(symbol: symbol, events: events) { feed in + let result = try? feed?.getLastEvents(types: events) + result?.forEach({ event in + checkEvent(event, in: events) + }) + + events.forEach { event in + if let lastEvent = try? feed?.getLastEvent(type: event) { + checkEvent(lastEvent, in: events) + } else { + XCTAssert(false, "LastEvent returns nil") + } + } + } + } + + func getLastEvents(symbol: String, events: [MarketEvent], fetching: ((DXFeed?) -> Void)) { + let port = Int.random(in: 7500..<7600) + do { + let endpoint: DXEndpoint? = try DXEndpoint.create(.publisher) + try endpoint?.connect(":\(port)") + let publisher = endpoint?.getPublisher() + let connectedExpectation = expectation(description: "Connected") + let stateListener: TestEndpoointStateListener? = TestEndpoointStateListener { listener in + listener.callback = { state in + if state == .connected { + DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.3) { + print(Thread.current.threadName) + try? publisher?.publish(events: events) + connectedExpectation.fulfill() + } + } + } + return listener + } + let feedEndpoint = try DXEndpoint.create(.feed) + feedEndpoint.add(listener: stateListener!) + let allTypes = [Candle.self, + Trade.self, + TradeETH.self, + Quote.self, + TimeAndSale.self, + Profile.self, + Summary.self, + Greeks.self, + Underlying.self, + TheoPrice.self, + Order.self, + AnalyticOrder.self, + SpreadOrder.self, + Series.self, + OptionSale.self] + let subscription = try feedEndpoint.getFeed()?.createSubscription(allTypes) + try feedEndpoint.connect("localhost:\(port)") + try subscription?.addSymbols(symbol) + wait(for: [connectedExpectation], timeout: 1) + fetching(feedEndpoint.getFeed()) + } catch { + XCTAssert(false, "\(error)") + } + } + +} diff --git a/DXFeedFrameworkTests/EndpointTest.swift b/DXFeedFrameworkTests/EndpointTest.swift index 58cbce042..e763d95e2 100644 --- a/DXFeedFrameworkTests/EndpointTest.swift +++ b/DXFeedFrameworkTests/EndpointTest.swift @@ -76,7 +76,6 @@ final class EndpointTest: XCTestCase { testGetInstance(role: .publisher, count: 150) } - func testGetEventTypes() throws { let endpoint = try DXEndpoint.create().connect("demo.dxfeed.com:7300") let feed = endpoint.getFeed() diff --git a/Samples/Playgrounds/DXFeedconnect.playground/Contents.swift b/Samples/Playgrounds/DXFeedconnect.playground/Contents.swift index b03ba483c..c070e556d 100644 --- a/Samples/Playgrounds/DXFeedconnect.playground/Contents.swift +++ b/Samples/Playgrounds/DXFeedconnect.playground/Contents.swift @@ -40,11 +40,11 @@ let allTypes = [Candle.self, OptionSale.self] let argSymbols = ["ETH/USD:GDAX"] -let argTime: String? = nil +let argTime: String? = nil // To avoid release inside internal {} scope let feed = try DXEndpoint.getInstance().connect("demo.dxfeed.com:7300").getFeed() -var feedSubscription: DXFeedSubscription? = nil +var feedSubscription: DXFeedSubscription? let listener = Listener { listener in listener.callback = { events in events.forEach { event in