Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

11Feature/canle charts #9

Merged
merged 52 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
b70db1d
feat: candle_charts WIP
kosyloa Apr 30, 2024
17da24d
feat: candle_charts WIP
kosyloa Apr 30, 2024
37c9702
feat: candle_charts WIP
kosyloa Apr 30, 2024
0e88a91
customize color to dxfeed color scheme
kosyloa May 2, 2024
d5b6f17
reformat code
kosyloa May 3, 2024
d8a5064
add separate target for mac candle chart sample
kosyloa May 3, 2024
bc796d8
integrate candle chart in quote table app
kosyloa May 3, 2024
9e8f139
fix warnings
kosyloa May 6, 2024
eea95c7
feat: MarketDepthModel WIP
kosyloa May 7, 2024
43875e2
fix hex representation for int
kosyloa May 8, 2024
89ba673
feat: market depth model
kosyloa May 8, 2024
5ac44b7
add available macro for OrderSet(just for test)
kosyloa May 8, 2024
8bd3555
feat: marketDepth(fix calutation error)
kosyloa May 9, 2024
148aa7c
add aggregation period for presentation
kosyloa May 9, 2024
f379731
swiftlint fixes
kosyloa May 9, 2024
643fbc5
refactoring for avoiding warning
kosyloa May 9, 2024
ccef8fc
add readwrite lock
kosyloa May 13, 2024
3de8b69
redesign market depth(two side)
kosyloa May 13, 2024
73904a2
change positon for sale/buy orders
kosyloa May 14, 2024
c9fc6a3
rename menu
kosyloa May 14, 2024
323ec88
redesign market depth: show without scroll
kosyloa May 14, 2024
027fe72
add min/max values
kosyloa May 15, 2024
98a3489
fix for ipad: required view/frame for action sheet
kosyloa May 15, 2024
3dcfcc4
add workaround for older version iOS
kosyloa May 16, 2024
589ccc3
candle chart redesign
kosyloa May 16, 2024
6fd4f1b
fix problem with not full loading of charts(need touch + scroll)
kosyloa May 16, 2024
fb2e026
fix warnings and small ui fixes
kosyloa May 20, 2024
1290880
remove hardcoded ipf address
kosyloa May 20, 2024
2bdf798
return test to working state
kosyloa May 20, 2024
858641d
fix run of candle ios app
kosyloa May 20, 2024
a6a5738
change x axis to string value(to avoid gaps)
kosyloa May 21, 2024
6cbb6e2
refactoring candles
kosyloa May 22, 2024
35c56b9
refactoring candle view
kosyloa May 22, 2024
9b01b16
add tests for marketdepth
kosyloa May 24, 2024
fe2b59c
OrderSource: publishable sources for dif orders
kosyloa May 24, 2024
e9e297e
marketdepth separate app(as a sample)
kosyloa May 24, 2024
d8bec96
fix warnings
kosyloa May 24, 2024
73896fd
feat: candle chart
kosyloa May 27, 2024
7408b8b
fix run on real device
kosyloa May 28, 2024
ae69423
warning: need fixes
kosyloa May 30, 2024
55c88ac
update dep
kosyloa May 30, 2024
6a3736c
save scroll position in case of insertion new candle
kosyloa May 30, 2024
405c030
remove blanks
kosyloa Jun 3, 2024
cf0def6
update build number
kosyloa Jun 6, 2024
91dd95a
change text of candle notice
kosyloa Jun 6, 2024
5b4c87a
test commit
kosyloa Jun 7, 2024
1734719
fix: leaks in schedule
kosyloa Jun 10, 2024
31eef52
decrease size of candles(to decrease time of loading)
kosyloa Jun 11, 2024
b27a8a3
update version of build
kosyloa Jun 12, 2024
1640326
fix warnings
kosyloa Jun 18, 2024
3cd5e54
fix warnings
kosyloa Jun 18, 2024
c484fc8
Merge branch 'swift' into feature/canle_charts
kosyloa Jun 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
653 changes: 636 additions & 17 deletions DXFeedFramework.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,16 @@ public class IndexedEventSubscriptionSymbol: Symbol {

/// Custom symbol has to return string representation.
public var stringValue: String {
return "\(symbol.stringValue)source=\(source.toString())"
return "\(symbol.stringValue){source=\(source.toString())}"
}
}

extension IndexedEventSubscriptionSymbol: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(self.symbol.stringValue)
hasher.combine(self.source.toString())
}
}
extension IndexedEventSubscriptionSymbol: Equatable {
public static func == (lhs: IndexedEventSubscriptionSymbol,
rhs: IndexedEventSubscriptionSymbol) -> Bool {
Expand Down
17 changes: 17 additions & 0 deletions DXFeedFramework/Events/Market/Extra/OrderBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,20 @@ extension OrderBase {
}
}
}

public extension OrderBase {
/// Returns true if this order has some size (sizeAsDouble is neither 0 nor NaN).
func hasSize() -> Bool {
return size != 0 && !size.isNaN
}
}

extension OrderBase: Hashable {
public static func == (lhs: OrderBase, rhs: OrderBase) -> Bool {
return lhs.index == rhs.index
}

public func hash(into hasher: inout Hasher) {
hasher.combine(self.index)
}
}
33 changes: 30 additions & 3 deletions DXFeedFramework/Events/Market/Extra/OrderSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class OrderSource: IndexedEventSource {
/// It is a synthetic source.
/// The subscription on composite ``Quote`` event is observed when this source is subscribed to.
public static let compsoiteBid =
try? OrderSource(1, "COMPOSITE_BID", pubOrder | pubAnalyticOrder | pubSpreadOrder | fullOrderBook)
try? OrderSource(1, "COMPOSITE_BID", 0)
/// Ask side of a composite ``Quote``.
/// It is a synthetic source.
/// The subscription on composite ``Quote`` event is observed when this source is subscribed to.
Expand Down Expand Up @@ -95,12 +95,18 @@ public class OrderSource: IndexedEventSource {
/// Direct-Edge EDGX Exchange.
public static let DEX = try? OrderSource("DEX", pubOrder)

/// Direct-Edge EDGX Exchange. Record for price level book.
public static let dex = try? OrderSource("dex", pubOrder)

/// Bats BYX Exchange.
public static let BYX = try? OrderSource("BYX", pubOrder)

/// Bats BZX Exchange.
public static let BZX = try? OrderSource("BZX", pubOrder)

/// Bats BZX Exchange. Record for price level book.
public static let bzx = try? OrderSource("bzx", pubOrder)

/// Bats Europe BXE Exchange.
public static let BATE = try? OrderSource("BATE", pubOrder)

Expand Down Expand Up @@ -168,6 +174,8 @@ public class OrderSource: IndexedEventSource {
/// Pink sheets are listings for stocks that trade over-the-counter (OTC).
public static let pink = try? OrderSource("pink", pubOrder | pubOtcMarketsOrder)

private static var publishableViews = [[OrderSource]](repeating: [OrderSource](), count: flagsSize)

/// Don't use it. Just for initialization all static variable.
/// static let - is always lazy initialized
fileprivate static let allValues = [OrderSource.defaultOrderSource,
Expand All @@ -186,8 +194,10 @@ public class OrderSource: IndexedEventSource {
OrderSource.ISE,
OrderSource.DEA,
OrderSource.DEX,
OrderSource.dex,
OrderSource.BYX,
OrderSource.BZX,
OrderSource.bzx,
OrderSource.BATE,
OrderSource.CHIX,
OrderSource.CEUX,
Expand All @@ -208,6 +218,7 @@ public class OrderSource: IndexedEventSource {
OrderSource.iex,
OrderSource.MEMX,
OrderSource.memx,
OrderSource.OCEA,
OrderSource.pink]

override init(_ identifier: Int, _ name: String) {
Expand Down Expand Up @@ -247,6 +258,10 @@ public class OrderSource: IndexedEventSource {
if !OrderSource.sourcesByName.tryInsert(key: name, value: self) {
throw ArgumentException.exception("duplicate name \(name)")
}

for index in 0..<OrderSource.flagsSize where (pubFlags & (1 << index)) != 0 {
OrderSource.publishableViews[index].append(self)
}
}

private static func isFullOrderBookFlag(_ pubFlags: Int) -> Bool {
Expand Down Expand Up @@ -304,17 +319,29 @@ public class OrderSource: IndexedEventSource {
}
return name
}

/// Gets a value indicating whether this source supports Full Order Book.
public func isFullOrderBook() -> Bool {
return (pubFlags & OrderSource.fullOrderBook) != 0
}

/// Returns a list of publishable order sources for a given event type.
///
/// - Parameters:
/// - eventype : Possible values ``Order``, ``AnalyticOrder``, ``SpreadOrder``, ``OtcMarketsOrder``
/// - Returns: a list of publishable order sources.
/// - Throws: ``ArgumentException/exception(_:)``
public static func publishable(eventType: AnyClass) throws -> [OrderSource] {
let index = Int32(try OrderSource.getEventTypeMask(eventType)).leadingZeroBitCount
return publishableViews[31 - index]
}

/// Gets a value indicating whether the given event type can be directly published with this source.
///
/// Subscription on such sources can be observed directly via ``DXPublisher``
/// Subscription on such sources is observed via instances of ``GenericIndexedEventSubscriptionSymbol``
/// - Parameters:
/// - eventype : Possible values ``Order``, ``AnalyticOrder``, ``SpreadOrder``
/// - eventype : Possible values ``Order``, ``AnalyticOrder``, ``SpreadOrder``, ``OtcMarketsOrder``
/// - Returns: true- events can be directly published with this source
/// - Throws: ``ArgumentException/exception(_:)``
public func isPublishable(eventType: AnyClass.Type) throws -> Bool {
Expand Down Expand Up @@ -345,7 +372,7 @@ public class OrderSource: IndexedEventSource {
/// Gets type mask by specified event type.
///
/// - Parameters:
/// - eventype : Possible values ``Order``, ``AnalyticOrder``, ``SpreadOrder``
/// - eventype : Possible values ``Order``, ``AnalyticOrder``, ``SpreadOrder``, ``OtcMarketsOrder``
/// - Returns: The mask for event class.
/// - Throws: ``ArgumentException/exception(_:)``
public static func getEventTypeMask(_ eventType: AnyClass) throws -> Int {
Expand Down
5 changes: 5 additions & 0 deletions DXFeedFramework/Events/Market/Extra/Scope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public enum Scope: Int, CaseIterable {

/// Represents individual order on the market.
case order

var code: Int {
return self.rawValue
}

}

/// Class extension for ``ScopeExt`` enum.
Expand Down
173 changes: 173 additions & 0 deletions DXFeedFramework/Extra/IndexedTxModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//
//
// 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

class IndexedTxModel {
class Changes {
let isSnapshot: Bool
let source: IndexedEventSource
let events: [Order]

init(isSnapshot: Bool,
source: IndexedEventSource,
events: [Order]) {
self.isSnapshot = isSnapshot
self.source = source
self.events = events
}
}
var mode: TxMode
fileprivate var sourceTxDict = [Int: SourceTx]()
weak var listener: TxModelListener?

init(mode: TxMode) {
self.mode = mode
}

func setListener(_ listener: TxModelListener) {
self.listener = listener
}
}

extension IndexedTxModel: TxModelListener {
func modelChanged(changes: Changes) {
self.listener?.modelChanged(changes: changes)
}
}

extension IndexedTxModel: DXEventListener {
func receiveEvents(_ events: [MarketEvent]) {
var sourceTx: SourceTx?
events.forEach { marketEvent in
guard let order = marketEvent as? Order else {
return
}
let sourceId = order.eventSource.identifier
if sourceTx == nil || sourceId != sourceTx?.source.identifier {
sourceTx = geTxProcessorForEvent(order)
}
sourceTx?.processEvent(order)
}
onBatchReceived()
}

private func geTxProcessorForEvent(_ event: Order) -> SourceTx {
let sourceId = event.eventSource.identifier
guard let sourceTx = sourceTxDict[sourceId] else {
let sourceTx = SourceTx(source: event.eventSource, mode: mode, listener: self)
sourceTxDict[sourceId] = sourceTx
return sourceTx
}
return sourceTx
}

private func onBatchReceived() {
if mode.isBatchProcessing() {
notifyListenerForAllSources()
}
}

private func notifyListenerForAllSources() {
sourceTxDict.values.forEach { sourceTx in
sourceTx.notifyListener()
}
}
}

extension IndexedTxModel: Hashable {
public static func == (lhs: IndexedTxModel, rhs: IndexedTxModel) -> Bool {
return lhs === rhs
}

public func hash(into hasher: inout Hasher) {
hasher.combine("\(self):\(stringReference(self))")
}
}

private class SourceTx {
private var isPartialSnapshot = false
private var isCompleteSnapshot = false

private weak var snapshotDelegate: SnapshotDelegate?
private var pendingEvents = [Order]()
private var processedEvents = [Order]()

let source: IndexedEventSource
let mode: TxMode

weak var listener: TxModelListener?

init(source: IndexedEventSource,
mode: TxMode,
listener: TxModelListener) {
self.source = source
self.mode = mode
self.listener = listener
}

func processEvent(_ event: Order) {
if event.snapshotBegin() {
isPartialSnapshot = true
isCompleteSnapshot = false
pendingEvents.removeAll()
}
if isPartialSnapshot && event.endOrSnap() {
isPartialSnapshot = false
isCompleteSnapshot = true
}
if event.pending() || isPartialSnapshot {
pendingEvents.append(event)
return
}
let isSnapshot = isCompleteSnapshot
if isCompleteSnapshot {
isCompleteSnapshot = false
processedEvents.removeAll()
}
if !pendingEvents.isEmpty {
processedEvents.append(contentsOf: pendingEvents)
pendingEvents.removeAll()
// if isSnapshot {
// pendingEvents.trimToSize()
// }
}
processedEvents.append(event)
onTransactionReceived(isSnapshot)
}
private func onTransactionReceived(_ isSnapshot: Bool) {
if mode.isBatchProcessing() {
if isSnapshot {
notifyListener(true)
}
} else {
notifyListener(isSnapshot)
}
}

public func notifyListener() {
notifyListener(false)
}

public func notifyListener(_ isSnapshot: Bool) {
if processedEvents.isEmpty {
return
}
defer {
processedEvents.removeAll()
// if (isSnapshot)
// pendingEvents.trimToSize();
}
if listener == nil {
return
}

listener?.modelChanged(changes: IndexedTxModel.Changes(isSnapshot: isSnapshot,
source: source,
events: processedEvents))
}
}
26 changes: 26 additions & 0 deletions DXFeedFramework/Extra/MarketDepthListener.swift
Original file line number Diff line number Diff line change
@@ -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 Foundation

public class OrderBook {
public let buyOrders: [Order]
public let sellOrders: [Order]
public let name = "ASDas"
init(buyOrders: [Order], sellOrders: [Order]) {
self.buyOrders = buyOrders
self.sellOrders = sellOrders
}

public convenience init() {
self.init(buyOrders: [Order](), sellOrders: [Order]())
}
}

public protocol MarketDepthListener: AnyObject {
func modelChanged(changes: OrderBook)
}
Loading