From d3350cc2b33204505763b9bd3f352c3179c3f2be Mon Sep 17 00:00:00 2001 From: kosyloa Date: Wed, 10 Apr 2024 13:50:34 +0200 Subject: [PATCH] feat: order book model WIP --- DXFeedFramework.xcodeproj/project.pbxproj | 56 ++++++ .../Model/Market/ObservableListModel.swift | 17 ++ .../Model/Market/OrderBookModel.swift | 129 +++++++++++++ .../Model/Market/OrderBookModelFilter.swift | 18 ++ .../Model/Market/OrderBookModelListener.swift | 13 ++ .../Model/NativeObservableListModel.swift | 17 ++ .../Native/Model/NativeOrderBookModel.swift | 174 ++++++++++++++++++ .../Model/OrderBookModelFilter+Ext.swift | 51 +++++ .../DXOrderBookModelTest.swift | 26 +++ 9 files changed, 501 insertions(+) create mode 100644 DXFeedFramework/Model/Market/ObservableListModel.swift create mode 100644 DXFeedFramework/Model/Market/OrderBookModel.swift create mode 100644 DXFeedFramework/Model/Market/OrderBookModelFilter.swift create mode 100644 DXFeedFramework/Model/Market/OrderBookModelListener.swift create mode 100644 DXFeedFramework/Native/Model/NativeObservableListModel.swift create mode 100644 DXFeedFramework/Native/Model/NativeOrderBookModel.swift create mode 100644 DXFeedFramework/Native/Model/OrderBookModelFilter+Ext.swift create mode 100644 DXFeedFrameworkTests/DXOrderBookModelTest.swift diff --git a/DXFeedFramework.xcodeproj/project.pbxproj b/DXFeedFramework.xcodeproj/project.pbxproj index c35dbd88c..c1874cdd2 100644 --- a/DXFeedFramework.xcodeproj/project.pbxproj +++ b/DXFeedFramework.xcodeproj/project.pbxproj @@ -123,6 +123,14 @@ 6447A5F12A8FDD1B00739CCF /* BitUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6447A5F02A8FDD1B00739CCF /* BitUtil.swift */; }; 644B95E72BC542F600E95CB7 /* DXAttachTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644B95E62BC542F600E95CB7 /* DXAttachTest.swift */; }; 644B95E92BC54E4E00E95CB7 /* MarketEvent+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644B95E82BC54E4E00E95CB7 /* MarketEvent+Ext.swift */; }; + 644B95ED2BC5844400E95CB7 /* OrderBookModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644B95EC2BC5844400E95CB7 /* OrderBookModel.swift */; }; + 644B95F02BC5847000E95CB7 /* NativeOrderBookModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644B95EF2BC5847000E95CB7 /* NativeOrderBookModel.swift */; }; + 644B95F22BC586AD00E95CB7 /* DXOrderBookModelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644B95F12BC586AD00E95CB7 /* DXOrderBookModelTest.swift */; }; + 644B95F42BC5873500E95CB7 /* OrderBookModelFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644B95F32BC5873500E95CB7 /* OrderBookModelFilter.swift */; }; + 644B95F62BC58D3800E95CB7 /* OrderBookModelFilter+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644B95F52BC58D3800E95CB7 /* OrderBookModelFilter+Ext.swift */; }; + 644B95F82BC672E500E95CB7 /* ObservableListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644B95F72BC672E500E95CB7 /* ObservableListModel.swift */; }; + 644B95FA2BC673BC00E95CB7 /* OrderBookModelListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644B95F92BC673BC00E95CB7 /* OrderBookModelListener.swift */; }; + 644B95FC2BC67F6200E95CB7 /* NativeObservableListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644B95FB2BC67F6200E95CB7 /* NativeObservableListModel.swift */; }; 644BD75D2A44726F00A0BF99 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644BD75C2A44726F00A0BF99 /* AppDelegate.swift */; }; 644BD7612A44726F00A0BF99 /* ARQuoteTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644BD7602A44726F00A0BF99 /* ARQuoteTableViewController.swift */; }; 644BD7662A44727000A0BF99 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 644BD7652A44727000A0BF99 /* Assets.xcassets */; }; @@ -677,6 +685,14 @@ 6447A5F02A8FDD1B00739CCF /* BitUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitUtil.swift; sourceTree = ""; }; 644B95E62BC542F600E95CB7 /* DXAttachTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DXAttachTest.swift; sourceTree = ""; }; 644B95E82BC54E4E00E95CB7 /* MarketEvent+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MarketEvent+Ext.swift"; sourceTree = ""; }; + 644B95EC2BC5844400E95CB7 /* OrderBookModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderBookModel.swift; sourceTree = ""; }; + 644B95EF2BC5847000E95CB7 /* NativeOrderBookModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeOrderBookModel.swift; sourceTree = ""; }; + 644B95F12BC586AD00E95CB7 /* DXOrderBookModelTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DXOrderBookModelTest.swift; sourceTree = ""; }; + 644B95F32BC5873500E95CB7 /* OrderBookModelFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderBookModelFilter.swift; sourceTree = ""; }; + 644B95F52BC58D3800E95CB7 /* OrderBookModelFilter+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderBookModelFilter+Ext.swift"; sourceTree = ""; }; + 644B95F72BC672E500E95CB7 /* ObservableListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableListModel.swift; sourceTree = ""; }; + 644B95F92BC673BC00E95CB7 /* OrderBookModelListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderBookModelListener.swift; sourceTree = ""; }; + 644B95FB2BC67F6200E95CB7 /* NativeObservableListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeObservableListModel.swift; sourceTree = ""; }; 644BD75A2A44726F00A0BF99 /* DXARQuoteTableApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DXARQuoteTableApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 644BD75C2A44726F00A0BF99 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 644BD7602A44726F00A0BF99 /* ARQuoteTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARQuoteTableViewController.swift; sourceTree = ""; }; @@ -1179,6 +1195,35 @@ path = Ipf; sourceTree = ""; }; + 644B95EA2BC5842600E95CB7 /* Model */ = { + isa = PBXGroup; + children = ( + 644B95EB2BC5843200E95CB7 /* Market */, + ); + path = Model; + sourceTree = ""; + }; + 644B95EB2BC5843200E95CB7 /* Market */ = { + isa = PBXGroup; + children = ( + 644B95EC2BC5844400E95CB7 /* OrderBookModel.swift */, + 644B95F32BC5873500E95CB7 /* OrderBookModelFilter.swift */, + 644B95F72BC672E500E95CB7 /* ObservableListModel.swift */, + 644B95F92BC673BC00E95CB7 /* OrderBookModelListener.swift */, + ); + path = Market; + sourceTree = ""; + }; + 644B95EE2BC5845800E95CB7 /* Model */ = { + isa = PBXGroup; + children = ( + 644B95EF2BC5847000E95CB7 /* NativeOrderBookModel.swift */, + 644B95F52BC58D3800E95CB7 /* OrderBookModelFilter+Ext.swift */, + 644B95FB2BC67F6200E95CB7 /* NativeObservableListModel.swift */, + ); + path = Model; + sourceTree = ""; + }; 644BD75B2A44726F00A0BF99 /* ARQuoteTableApp */ = { isa = PBXGroup; children = ( @@ -1597,6 +1642,7 @@ 8088D77A29C5D8AD00F240CB /* Native */, 64656F552A1B6A07006A0B19 /* Utils */, 64ECD6802A9DD80000B36935 /* Ipf */, + 644B95EA2BC5842600E95CB7 /* Model */, 64F73B9D2B67863B0088EC37 /* Promise */, 64C771FD2A9504D7009868C2 /* Extra */, 6498E6B02AB1D40C0093A065 /* Schedule */, @@ -1637,6 +1683,7 @@ 64EAA1A52B838ED3005087BC /* DXSnapshotProcessorTest.swift */, 64820AAE2BB2E26100BDFD0B /* DXOtcMarketOrderTest.swift */, 644B95E62BC542F600E95CB7 /* DXAttachTest.swift */, + 644B95F12BC586AD00E95CB7 /* DXOrderBookModelTest.swift */, ); path = DXFeedFrameworkTests; sourceTree = ""; @@ -1662,6 +1709,7 @@ 64104FD12A277B1A00D1FC41 /* Interop */, 64104FCA2A2629C400D1FC41 /* Subscription */, 64437A902A9DF1C4005929B2 /* Ipf */, + 644B95EE2BC5845800E95CB7 /* Model */, 6498E6B32AB1D43A0093A065 /* Schedule */, 64F73BA22B67B2750088EC37 /* Promise */, 64ACBCE42A28AEE200032C53 /* SymbolMappers */, @@ -2411,6 +2459,7 @@ 64DA26BA2AA20AAA005B1757 /* DXInstrumentProfileConnectionState.swift in Sources */, 64278C6E2A602D2B0074B5AA /* Candle.swift in Sources */, 64ACBCE62A28BA7D00032C53 /* SymbolMapper.swift in Sources */, + 644B95ED2BC5844400E95CB7 /* OrderBookModel.swift in Sources */, 64ECD6822A9DDC2800B36935 /* DXInstrumentProfileReader.swift in Sources */, 6486B9712AD0445E00D8D5FA /* UnderlyingMapper.swift in Sources */, 64E3637B2AD83459002E2B0D /* SeriesMapper.swift in Sources */, @@ -2423,6 +2472,7 @@ 6486B9732AD045C800D8D5FA /* TheoPrice.swift in Sources */, 64ACBCDF2A2897EA00032C53 /* String+Symbol.swift in Sources */, 64C004762BA09FDC0009F7C9 /* OtcMarketsPriceType.swift in Sources */, + 644B95F62BC58D3800E95CB7 /* OrderBookModelFilter+Ext.swift in Sources */, 64B4363A2AB86D1A0003919E /* DayFilter.swift in Sources */, 64EAA1A42B7B4DA1005087BC /* IndexedEventSource+Ext.swift in Sources */, 64A42F3B2B07A7A3001C3ACC /* SymbolParser.swift in Sources */, @@ -2448,6 +2498,7 @@ 64B4363E2AB883350003919E /* SessionFilter.swift in Sources */, 6486B9612AD02D3900D8D5FA /* SummaryMapper.swift in Sources */, 64AF04412AE6A9E90091F6F1 /* TradeETHMapper.swift in Sources */, + 644B95F42BC5873500E95CB7 /* OrderBookModelFilter.swift in Sources */, 64B436402AB8857F0003919E /* SessionFilter+Ext.swift in Sources */, 646407592AA0CA8D006FF769 /* NativeProfileIterator.swift in Sources */, 6469F8C82A3B25C900846831 /* MarketEvent+Access.swift in Sources */, @@ -2462,8 +2513,10 @@ 6486B9582AD00BDC00D8D5FA /* Summary.swift in Sources */, 64104FCC2A2629D800D1FC41 /* NativeSubscription.swift in Sources */, 64C772012A975102009868C2 /* Reference+Util.swift in Sources */, + 644B95FA2BC673BC00E95CB7 /* OrderBookModelListener.swift in Sources */, 64656F6D2A1CFB10006A0B19 /* DXEndpointState.swift in Sources */, 64AF04432AE6AA230091F6F1 /* TradeETH+Ext.swift in Sources */, + 644B95F02BC5847000E95CB7 /* NativeOrderBookModel.swift in Sources */, 6464074D2A9E352E006FF769 /* InstrumentProfileField.swift in Sources */, 64ABC0FF2AD91E6500904D78 /* OptionSale.swift in Sources */, 64ABC1012AD9284900904D78 /* OptionSale+Ext.swift in Sources */, @@ -2538,6 +2591,7 @@ 640C3FCC2A616B6200555161 /* ArgumentException.swift in Sources */, 64B436442AB88EA40003919E /* NativeDay.swift in Sources */, 642BE4C42A2E1AFF0052340A /* Quote.swift in Sources */, + 644B95F82BC672E500E95CB7 /* ObservableListModel.swift in Sources */, 64656F602A1B9EC2006A0B19 /* EnumUtil.swift in Sources */, 64A42F492B0B9683001C3ACC /* NativeTimeFormat.swift in Sources */, 642BE4CA2A2E1C640052340A /* MarketEvent.swift in Sources */, @@ -2563,6 +2617,7 @@ 64656F5E2A1B97F2006A0B19 /* NativeFeed.swift in Sources */, 6486B97D2AD057F200D8D5FA /* OrderSource.swift in Sources */, 64656F6F2A1CFC12006A0B19 /* WeakBox.swift in Sources */, + 644B95FC2BC67F6200E95CB7 /* NativeObservableListModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2578,6 +2633,7 @@ 641C64B42B347C430023CFAD /* DXObservableSubscriptionTest.swift in Sources */, 64ACBCEC2A29FE2300032C53 /* XCTestCase+Utils.swift in Sources */, 6423E4692B457000006B208D /* DXTimeSeriesSubscriptionTest.swift in Sources */, + 644B95F22BC586AD00E95CB7 /* DXOrderBookModelTest.swift in Sources */, 64ECD67F2A9CF4CB00B36935 /* IPFTests.swift in Sources */, 64ACBCD52A2789EF00032C53 /* TestListener.swift in Sources */, 646064EA2B4D8973009201E2 /* DXLastEventTest.swift in Sources */, diff --git a/DXFeedFramework/Model/Market/ObservableListModel.swift b/DXFeedFramework/Model/Market/ObservableListModel.swift new file mode 100644 index 000000000..0d3f545dc --- /dev/null +++ b/DXFeedFramework/Model/Market/ObservableListModel.swift @@ -0,0 +1,17 @@ +// +// +// Copyright (C) 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation + +public class ObservableListModel { + let native: NativeObservableListModel + + init(native: NativeObservableListModel) { + self.native = native + } + +} diff --git a/DXFeedFramework/Model/Market/OrderBookModel.swift b/DXFeedFramework/Model/Market/OrderBookModel.swift new file mode 100644 index 000000000..7b55d609c --- /dev/null +++ b/DXFeedFramework/Model/Market/OrderBookModel.swift @@ -0,0 +1,129 @@ +// +// +// Copyright (C) 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation + +public class OrderBookModel { + let native: NativeOrderBookModel + + private let listeners = ConcurrentWeakHashTable() + + /// Creates new model. This model is not attached to any feed, not subscribed to any symbol, + /// and has filter set by default to {@link OrderBookModelFilter#ALL}. + public init() throws { + native = try NativeOrderBookModel() + } + + /// Attaches model to the specified feed. + /// - Parameters: + /// - feed: The feed to attach to. + public func attach(feed: DXFeed) throws { + try native.attach(feed: feed.nativeFeed) + } + + /// Detaches model from the specified feed. + /// - Parameters: + /// - feed: The feed to detach from. + public func detach(feed: DXFeed) throws { + try native.detach(feed: feed.nativeFeed) + } + + /// Closes this model and makes it permanently detached. + public func close() throws { + try native.close() + } + + /// Clears subscription symbol and, subsequently, all events in this model. + public func clear() throws { + try native.clear() + } + + /// Returns filter for the model. This filter specifies which order events are shown in the book. + /// - Returns: ``OrderBookModelFilter`` + public func getFilter() throws -> OrderBookModelFilter { + try native.getFilter() + } + + /// Sets the specified filter to the model. + /// - Parameters: + /// - filter: The model filter. + public func setFilter(_ filter: OrderBookModelFilter) throws { + try native.setFilter(filter) + } + + /// Returns order book symbol, or {@code null} for empty subscription. + /// - Returns: order book symbol + public func getSymbol() throws -> String? { + try native.getSymbol() + } + + /// Sets symbol for the order book to subscribe for. + /// - Parameters: + /// - symbol: order book symbol. Use null for to unsubscribe. + public func setSymbol(_ symbol: String?) throws { + try native.setSymbol(symbol) + } + + /// Returns lot size. Lot size is a multiplier applied to ``Scope/composite`` + /// - Returns: lot size. + public func getLotSize() throws -> Int32 { + try native.getLotSize() + } + + /// Sets the lot size. + /// - Parameters: + /// - size: lot size multiplier. + public func setLotSize(_ size: Int32) throws { + try native.setLotSize(size) + } + + /// Returns the view of bid side (buy orders) of the order book. + /// - Returns: ``ObservableListModel``. + public func getBuyOrders() throws -> ObservableListModel { + ObservableListModel(native: try native.getBuyOrders()) + } + + /// Returns the view of offer side (sell orders) of the order book. + /// - Returns: ``ObservableListModel``. + public func getSellOrders() throws -> ObservableListModel { + ObservableListModel(native: try native.getSellOrders()) + } + + /// Adds a listener to this order book model. + /// - Parameters: + /// - listener: ``OrderBookModelListener``. + public func add(listener: O) throws + where O: OrderBookModelListener, + O: Hashable { + try listeners.reader { [weak self] in + if $0.count == 0 { + try self?.native.addListener(self) + } + } + listeners.insert(listener) + } + + /// Removes a listener from this order book model. + /// - Parameters: + /// - listener: ``OrderBookModelListener``. + public func remove(listener: O) + where O: OrderBookModelListener, + O: Hashable { + listeners.remove(listener) + } +} + +extension OrderBookModel: OrderBookModelListener { + public func modelChanged() { + listeners.reader { items in + let enumerator = items.objectEnumerator() + while let listener = enumerator.nextObject() as? OrderBookModelListener { + listener.modelChanged() + } + } + } +} diff --git a/DXFeedFramework/Model/Market/OrderBookModelFilter.swift b/DXFeedFramework/Model/Market/OrderBookModelFilter.swift new file mode 100644 index 000000000..0731c2aa1 --- /dev/null +++ b/DXFeedFramework/Model/Market/OrderBookModelFilter.swift @@ -0,0 +1,18 @@ +// +// +// Copyright (C) 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation + +public enum OrderBookModelFilter: CaseIterable { + case composite + case regional + case aggregate + case order + case compositeRegional + case compositeRegionalAgregate + case all +} diff --git a/DXFeedFramework/Model/Market/OrderBookModelListener.swift b/DXFeedFramework/Model/Market/OrderBookModelListener.swift new file mode 100644 index 000000000..c743938fe --- /dev/null +++ b/DXFeedFramework/Model/Market/OrderBookModelListener.swift @@ -0,0 +1,13 @@ +// +// +// Copyright (C) 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation + +/// Callback interface that receives notifications about changes in ``OrderBookModel`` +public protocol OrderBookModelListener: AnyObject { + func modelChanged() +} diff --git a/DXFeedFramework/Native/Model/NativeObservableListModel.swift b/DXFeedFramework/Native/Model/NativeObservableListModel.swift new file mode 100644 index 000000000..a871cf6da --- /dev/null +++ b/DXFeedFramework/Native/Model/NativeObservableListModel.swift @@ -0,0 +1,17 @@ +// +// +// Copyright (C) 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation +@_implementationOnly import graal_api + +class NativeObservableListModel { + let native: UnsafeMutablePointer? + + init(native: UnsafeMutablePointer?) { + self.native = native + } +} diff --git a/DXFeedFramework/Native/Model/NativeOrderBookModel.swift b/DXFeedFramework/Native/Model/NativeOrderBookModel.swift new file mode 100644 index 000000000..fb67db88c --- /dev/null +++ b/DXFeedFramework/Native/Model/NativeOrderBookModel.swift @@ -0,0 +1,174 @@ +// +// +// Copyright (C) 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation +@_implementationOnly import graal_api + +class NativeOrderBookModel { + private class WeakModel: WeakBox { } + + let native: UnsafeMutablePointer? + var nativeListener: UnsafeMutablePointer? + + weak var listener: OrderBookModelListener? + + private static let listeners = ConcurrentArray() + + private static let finalizeCallback: dxfg_finalize_function = { _, context in + if let context = context { + let endpoint: AnyObject = bridge(ptr: context) + if let listener = endpoint as? WeakModel { + NativeOrderBookModel.listeners.removeAll(where: { + return $0 === listener + }) + } + } + } + + static let listenerCallback: dxfg_order_book_model_listener_function = {_, nativeEvents, context in + if let context = context { + var events = [MarketEvent]() + let listener: AnyObject = bridge(ptr: context) + if let listener = listener as? WeakModel { + guard let subscription = listener.value else { + return + } +// let count = Int(nativeEvents?.pointee.size ?? 0) +// for index in 0.. OrderBookModelFilter { + let thread = currentThread() + let filter = try ErrorCheck.nativeCall(thread, dxfg_OrderBookModel_getFilter(thread, native)) + return OrderBookModelFilter.fromNative(native: dxfg_order_book_model_filter_t(UInt32(filter))) + } + + func setFilter(_ filter: OrderBookModelFilter) throws { + let thread = currentThread() + try ErrorCheck.nativeCall(thread, + dxfg_OrderBookModel_setFilter(thread, + native, + filter.toNative())) + } + + func getSymbol() throws -> String? { + let thread = currentThread() + let symbol = try ErrorCheck.nativeCall(thread, dxfg_OrderBookModel_getSymbol(thread, native)) + return String(nullable: symbol) + } + + func setSymbol(_ symbol: String?) throws { + let thread = currentThread() + try ErrorCheck.nativeCall(thread, dxfg_OrderBookModel_setSymbol(thread, native, symbol?.toCStringRef())) + } + + func getLotSize() throws -> Int32 { + let thread = currentThread() + return try ErrorCheck.nativeCall(thread, dxfg_OrderBookModel_getLotSize(thread, native)) + } + + func setLotSize(_ size: Int32) throws { + let thread = currentThread() + try ErrorCheck.nativeCall(thread, dxfg_OrderBookModel_setLotSize(thread, native, size)) + } + + func getBuyOrders() throws -> NativeObservableListModel { + let thread = currentThread() + let model = try ErrorCheck.nativeCall(thread, dxfg_OrderBookModel_getBuyOrders(thread, native)) + return NativeObservableListModel(native: model) + } + + func getSellOrders() throws -> NativeObservableListModel { + let thread = currentThread() + let model = try ErrorCheck.nativeCall(thread, dxfg_OrderBookModel_getSellOrders(thread, native)) + return NativeObservableListModel(native: model) + } + + + fileprivate func clearNativeListeners() { + if let nativeListener = nativeListener { + let thread = currentThread() + _ = try? ErrorCheck.nativeCall(thread, + dxfg_OrderBookModel_removeListener(thread, + native, + nativeListener)) + _ = try? ErrorCheck.nativeCall(thread, + dxfg_JavaObjectHandler_release(thread, + &(nativeListener.pointee.handler))) + } + } + + func addListener(_ listener: OrderBookModelListener?) throws { + clearNativeListeners() + self.listener = listener + + let weakModel = WeakModel(value: self) + NativeOrderBookModel.listeners.append(newElement: weakModel) + let voidPtr = bridge(obj: weakModel) + let thread = currentThread() + let listener = try ErrorCheck.nativeCall(thread, + dxfg_OrderBookModelListener_new(thread, + NativeOrderBookModel.listenerCallback, + voidPtr)) + + try ErrorCheck.nativeCall(thread, dxfg_Object_finalize(thread, + &(listener.pointee.handler), + NativeOrderBookModel.finalizeCallback, + voidPtr)) + self.nativeListener = listener + _ = try ErrorCheck.nativeCall(thread, + dxfg_OrderBookModel_addListener(thread, + native, + self.nativeListener)) + } +} diff --git a/DXFeedFramework/Native/Model/OrderBookModelFilter+Ext.swift b/DXFeedFramework/Native/Model/OrderBookModelFilter+Ext.swift new file mode 100644 index 000000000..097c9ec0e --- /dev/null +++ b/DXFeedFramework/Native/Model/OrderBookModelFilter+Ext.swift @@ -0,0 +1,51 @@ +// +// +// Copyright (C) 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation +@_implementationOnly import graal_api + +extension OrderBookModelFilter { + func toNative() -> dxfg_order_book_model_filter_t { + switch self { + case .composite: + COMPOSITE + case .regional: + REGIONAL + case .aggregate: + AGGREGATE + case .order: + ORDER + case .compositeRegional: + COMPOSITE_REGIONAL + case .compositeRegionalAgregate: + COMPOSITE_REGIONAL_AGGREGATE + case .all: + ALL + } + } + + static func fromNative(native: dxfg_order_book_model_filter_t) -> OrderBookModelFilter { + switch native { + case COMPOSITE: + return .composite + case REGIONAL: + return .regional + case AGGREGATE: + return .aggregate + case ORDER: + return .order + case COMPOSITE_REGIONAL: + return .compositeRegional + case COMPOSITE_REGIONAL_AGGREGATE: + return .compositeRegionalAgregate + case ALL: + return .all + default: + fatalError("Try to convert OrderBookModelFilter with wrong value \(native)") + } + } +} diff --git a/DXFeedFrameworkTests/DXOrderBookModelTest.swift b/DXFeedFrameworkTests/DXOrderBookModelTest.swift new file mode 100644 index 000000000..6be1568fa --- /dev/null +++ b/DXFeedFrameworkTests/DXOrderBookModelTest.swift @@ -0,0 +1,26 @@ +// +// +// Copyright (C) 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import XCTest +@testable import DXFeedFramework + +final class DXOrderBookModelTest: XCTestCase { + + func testInit() throws { + let bookModel = try OrderBookModel() + } + + func testFilters() { + let allValues = OrderBookModelFilter.allCases + let allNative = allValues.map { filter in + filter.toNative() + } + XCTAssertEqual(allNative.map { filter in + OrderBookModelFilter.fromNative(native: filter) + }, allValues) + } +}