Weird: ForEach(transactionList)
Looping Twice

Weird behavior

I asked on iosdev.space Mastodon if anyone could spot my stupid mistake ...  seems that the ForEach(transactionList) { transaction in .... is runing once for the 10 items in the list and keeps on looping around and running another 10 time.  Doubling the number of items in the list.  But I don't know why?  Nor how to correct this behavior.

Many Mastodon Swift coders are looking at it... stumped ...

The console print outs at bottom show the element count of the list (10) and then the index count as ForEach loops up to 20.

A similar thread on Twitter

https://twitter.com/howtomakeanappp/status/1620619870667939840

A simpler example:

import SwiftUI


struct ContentView: View {

    func message(index: Int) -> some View {

        return HStack {

            Image(systemName: "globe")

                .imageScale(.large)

                .foregroundColor(.accentColor)

            Text("Hello, world! \(index)")

        }

    }

    

    var body: some View {

        VStack {

            let _ = Self._printChanges()  // debug the View refresh 

            ForEach(1...10, id: \.self ) { index in

                let _ = print("\(index) of ForEach()")

                message(index: index)

                let _ = Self._printChanges()  // debug the View refresh

            }

            

        }

        .padding()

    }

}


struct ContentView_Previews: PreviewProvider {

    static var previews: some View {

        ContentView()

    }

}

Maybe there is a reason...

When I extracted the message logic into a new View - so that the Self._printChanges() would be fired for the MessageView I noticed something... adding a bit more logging and I see that the first loop thru the ForEach does NOT actually call the MessageView. It is only upon the second loop of the 10 iterations that the MessageView is called.

It appears that the first loop thru the ForEach MessageView is created but NOT executed.  Then after the creation loop - an execution loop is run.

I wish we could get Apple SwiftUI team to comment and enlighten us.

Another coder related that the first loop is to do view layout... then once layout is computed... execute the body code.

Source Code for explaination.


import SwiftUI


struct ContentView: View {

    

    var body: some View {

        VStack {

            let _ = Self._printChanges()  // debug the View refresh

            ForEach(1...10, id: \.self ) { index in

                let _ = print("\(index) of ForEach()")

                MessageView(index: index)

                let _ = print("called MessageView(index: \(index))")

                let _ = Self._printChanges()  // debug the View refresh

            }

            

        }

        .padding()

    }

}


struct ContentView_Previews: PreviewProvider {

    static var previews: some View {

        ContentView()

    }

}


//---- MessageView

import SwiftUI


struct MessageView: View {

    

    var index: Int

    

    var body: some View {

        

        let _ = print("   message(\(index)) called")

        let _ = Self._printChanges()  // debug the View refresh

        return HStack {

            Image(systemName: "globe")

                .imageScale(.large)

                .foregroundColor(.accentColor)

            Text("Hello, world! \(index)")

        }

    }

    

}


struct MessageView_Previews: PreviewProvider {

    static var previews: some View {

        MessageView(index: 66)

    }

}



Above is a simple example - code to explore the ForEach double loop.


Below is the original project code that I discovered this weird behavior.

Had this idea last night... tried it this morning.  For all the people on Mastodon that think something is causing the Chart to be refreshed in the View structure.  Apple provided this debug print for refresh debugging.  Notice 20 "ChartView: unchanged." messages in the console log.

let _ = Self._printChanges()

Here is StockChartDetail_View  


struct StockChartDetail_View: View {

    

    @StateObject var chartVM: Chart_ViewModel

    @StateObject var quotesVM: QuotesViewModel


    @Environment(\.dismiss) private var dismiss

    

    @State var selectedFolio = FolioItem.defaultFolio   // these are STATIC vars - that's why it works here

    @State var folioList = FolioItem.preLoadedList      // STATIC var

    

    

    @EnvironmentObject var folioCKVM: FolioCKViewModel  // use folioCKVM.transactionList

    @State var transactionList: [Transaction]           // updateTransactionList() onAppear

    // cannot @State var transactionList: [Transaction] = folioCKVM.transactionList

    // so we use setTransactionsInList() in onTask or onAppear - is this good practice???

    

    

    @State var price: Double = 1.00

    @State var wlTicker: WLTicker

    @State private var showingBuySellStock = false

    

    var body: some View {

        VStack(alignment: .leading, spacing: 0) {

            headerView.padding(.horizontal)

            // Apple Inc | AAPL NASDAQ USD


            Divider()   // topmost divider line

                .padding(.vertical, 8)

                .padding(.horizontal)

            

            priceDiffRowView

                .frame(maxWidth: .infinity, alignment: .leading)

                .padding(.top, 8)

                .padding(.horizontal)

            

            Divider()   // 2nd divider line

                .padding(.vertical, 8)

                .padding(.horizontal)

            

            chartDurationPickerView

            

            Divider()   // 3rd divider line

                .padding(.vertical, 8)

                .padding(.horizontal)

            

            loadingStateOrChart      // contains the Chart scrolls horz.

            

            Divider()   // 4th divider line

                .padding(.vertical, 8)

                .padding(.horizontal)

            scrollingMarketDataView

            

        }

        .padding(.top)

        .background(Color(uiColor: .systemBackground))

        .task(id: chartVM.selectedRange.rawValue) {

            if quotesVM.quote == nil {

                await quotesVM.fetchOneQuote()

                wlTicker = quotesVM.ticker

                price = quotesVM.quote?.regularMarketPrice ?? 1.00

            }

            await chartVM.fetchData()       // Call API get chart data

            updateTransactionListOnTask()         // update TransactionList for this Symbol

           // setTransactionsInList()

        }

        .sheet(isPresented: $showingBuySellStock) {

            //Text("This sheet brought to you by Hacking with Swift")

            PurchaseStockView(isPresented: $showingBuySellStock,

                              symbol: wlTicker.symbol,

                              marketPrice: $price)

            .presentationDetents([.medium, .large])

        }

//        .onAppear() {

//            updateTransactionListOnAppear()  // in onAppear

//           // setTransactionsInList()

//        }

    }


    //MARK: - View Components -

    private var headerView: some View {

        HStack(alignment: .top) {

            // Company Name & Symbol

            VStack(alignment: .leading, spacing: 4) {

                if let shortName = quotesVM.ticker.shortname {

                    HStack {

                        Text(shortName)  // Apple Inc.

                            .font(.subheadline.weight(.semibold))

                            .foregroundColor(.text_Secondary)

                        

                        FolioPickerView(selection: $selectedFolio, list: $folioList)

                    }

                }

                

                // Ticker and Exchange

                HStack(alignment: .lastTextBaseline) {

                    

                    Text(quotesVM.ticker.symbol).font(.title.bold())

                    // AAPL - symbol

                    

                    HStack(spacing: 4) {    // Exchange and Currency

                        

                        if let exchange = quotesVM.ticker.exchange {    // exchDisp is now exchange

                            Text(exchange)

                            // NASDAQ

                        }

                        

                        if let currency = quotesVM.quote?.currency {

                            Text("·") // spacer dot

                            Text(currency)

                            // USD

                        }

                    } // hstack

                    .font(.caption)

                    .foregroundColor(.text_Secondary)

                } // hstack

                

            } // vstack

            Spacer()  // middle

                      // Action Buttons Buy/Sell

            VStack {

                buyPlaceholder

                sellPlaceholder

            }

            Spacer()  // middle

            closeButton

        } // hstack

    }

    private var buyPlaceholder: some View {

        Button {

            //onBuyButtonTapped()

            showingBuySellStock.toggle()

        } label: {

            Text("Buy")

                .font(.caption)

        }

        .buttonStyle(EnlargeButtonAnnimation())

        .frame(width: 40, height: 25)

    }

    private var sellPlaceholder: some View {

        Button {

            //onSellButtonTapped()

            showingBuySellStock.toggle()

        } label: {

            Text("Sell")

                .font(.caption)

        }

        .buttonStyle(EnlargeButtonAnnimation())

        .frame(width: 40, height: 25)

    }

    

    @ViewBuilder

    private var quoteDetailRowView: some View {

        

        switch quotesVM.phase {

            case .fetching: LoadingStateView()

            case .failure(let error): ErrorStateView(error: "Get Quote: \(error.localizedDescription)")

                    .padding(.horizontal)

            case .success(let quote):

                ScrollView(.horizontal) {

                    HStack(spacing: 16) {

                        ForEach(quote.columnItems) {

                            QuoteDetailRowColumnView(item: $0)

                        }

                    }

                    .padding(.horizontal)

                    .font(.caption.weight(.semibold))

                    .lineLimit(1)

                } // scrollview

                .scrollIndicators(.hidden)

                

                

            default: EmptyView()

        }

    }

    private var priceDiffRowView: some View {

        VStack(alignment: .leading, spacing: 8) {

            

            if let quote = quotesVM.quote {

                HStack {

                    // active trading now

                    if quote.isTrading,

                       let price = quote.regularPriceText,

                       let diff = quote.regularDiffText {

                        priceDiffStackView(price: price, diff: diff, caption: nil)

                        

                        Spacer()

                    } else {

                        // market is closed now

                        if let atCloseText = quote.regularPriceText,

                           let atCloseDiffText = quote.regularDiffText {

                            priceDiffStackView(price: atCloseText, diff: atCloseDiffText, caption: "At Close")

                        }

                        Spacer()

                        if let afterHourText = quote.postPriceText,

                           let afterHourDiffText = quote.postPriceDiffText {

                            priceDiffStackView(price: afterHourText, diff: afterHourDiffText, caption: "After Hours")

                        }

                    } // else - if quote.isTrading

                } // hstack

            } // if let quote =

        }

    }

    private func priceDiffStackView(price: AttributedString, diff: String, caption: String?) -> some View {

        VStack(alignment: .trailing) {

            

            // Latest Price Quote

            Text(price).font(.headline.bold())

            // the Delta of Price or other options

            Text(diff).font(.caption2.weight(.semibold))

                .foregroundColor(diff.hasPrefix("-") ? .Chart_red : .Chart_green)

            //FIXME: chart_red or text_red

            // }

            

            if let caption {

                Text(caption)

                    .font(.subheadline.weight(.semibold))

                    .foregroundColor(.text_Secondary)

            }

        } // vstack

    }

    

    //MARK: - Chart Components -

    private var chartDurationPickerView: some View {

        VStack {

            ScrollView(.horizontal, showsIndicators: false) {

                ZStack {

                    DateRangePickerView(selectedRange: $chartVM.selectedRange)

                        .opacity(chartVM.durationSelectionOpacity)

                    // displayed when touching selecting a date on graph

                    Text(chartVM.selectedDurationText)

                        .font(.headline)

                        .padding(.vertical, 4)

                        .padding(.horizontal)

                }

            }

            .scrollIndicators(.hidden)

            .frame(maxWidth: .infinity, alignment: .leading)

        }

    }

    //FIXME: is this var redundant -> move it to chartView

    private var loadingStateOrChart: some View {

        chartView

            .padding(.horizontal)

            .frame(maxWidth: .infinity, minHeight: 220)

    }

    @ViewBuilder

    private var chartView: some View {

        switch chartVM.phase {

            case .fetching: LoadingStateView()

            case .success(let data):

                ChartView(chartViewData: data, transactionList: transactionList, chartVM: chartVM)

            case .failure(let error):

                ErrorStateView(error: "Get Chart: \(error.localizedDescription)")

            default: EmptyView()

        }

    }


    //MARK: - View Components -

    private var scrollingMarketDataView: some View {

        ScrollView(.horizontal, showsIndicators: false) {

            // footer below graph

            // 3 rows high scrolling horiz view

            quoteDetailRowView

                .frame(maxWidth: .infinity, minHeight: 80)

        } // scrollview

        .scrollIndicators(.hidden)

        .frame(maxWidth: .infinity, alignment: .leading)

    }


    //MARK: - Page Icons

    private var closeButton: some View {

        Button {

            dismiss()

        } label: {

            Circle()

                .frame(width: 20, height: 20)

                .foregroundColor(.gray.opacity(0.1))

                .overlay {

                    Image(systemName: "multiply.circle")

                        .font(.system(size: 16).bold())

                        .foregroundColor(.text_Secondary)

                }

        }

        .buttonStyle(.plain)

    }


    //MARK: - Form functions -

    

    func setTransactionsInList() {

        self.transactionList = folioCKVM.transactionList

        Self.logger.trace("setTransactionsInList for \(wlTicker.symbol) \(transactionList.count) transactions")

    }

    

    func updateTransactionListOnAppear() {

        // refreshes the Published transactionList

        Self.logger.trace("updateTransactionListOnAppear for \(wlTicker.symbol) \(transactionList.count) transactions")

        

        folioCKVM.loadTransactionList(for: wlTicker.symbol)  // updates Published transactionList

        self.transactionList = folioCKVM.transactionList

        Self.logger.trace("set TransactionsList for \(wlTicker.symbol) \(transactionList.count) transactions")

        

     //   Self.logger.trace( "Found \(folioCKVM.transactionList.count) transactions in List.")

     //   Self.logger.trace( "\(folioCKVM.transactionList)")

    }

    

    func updateTransactionListOnTask() {

        // refreshes the Published transactionList

        Self.logger.trace("updateTransactionListOnTask for \(wlTicker.symbol) \(transactionList.count) transactions")

        

        folioCKVM.loadTransactionList(for: wlTicker.symbol)  // updates Published transactionList

        self.transactionList = folioCKVM.transactionList

        Self.logger.trace("set TransactionsList for \(wlTicker.symbol) \(transactionList.count) transactions")

        

     //   Self.logger.trace( "Found \(folioCKVM.transactionList.count) transactions in List.")

     //   Self.logger.trace( "\(folioCKVM.transactionList)")

    }

    

    private static let logger = Logger(

        subsystem: "App",

        category: String(describing: StockChartDetail_View.self)

    )

}


//MARK: - Previews -


#if DEBUG

struct StockTickerView_Previews: PreviewProvider {

    

    static var tradingStubsQuoteVM: QuotesViewModel = {

       var mockAPI = MockStocksAPI()

        mockAPI.stubbedFetchQuotesCallback = {

            [Quote.stub(isTrading: true)]

        }

        return QuotesViewModel(ticker: .stub, stocksAPI: mockAPI)

    }()

    

    static var closedStubsQuoteVM: QuotesViewModel = {

       var mockAPI = MockStocksAPI()

        mockAPI.stubbedFetchQuotesCallback = {

            [Quote.stub(isTrading: false)]

        }

        return QuotesViewModel(ticker: .stub, stocksAPI: mockAPI)

    }()

    

    

    static var loadingStubsQuoteVM: QuotesViewModel = {

       var mockAPI = MockStocksAPI()

        mockAPI.stubbedFetchQuotesCallback = {

            await withCheckedContinuation { _ in

                

            }

        }

        return QuotesViewModel(ticker: .stub, stocksAPI: mockAPI)

    }()

    

    

    static var errorStubsQuoteVM: QuotesViewModel = {

       var mockAPI = MockStocksAPI()

        mockAPI.stubbedFetchQuotesCallback = {

            throw NSError(domain: "error", code: 0, userInfo: [NSLocalizedDescriptionKey: "An error has occured."])

        }

        return QuotesViewModel(ticker: .stub, stocksAPI: mockAPI)

    }()

    

    static var chartVM: Chart_ViewModel {

        Chart_ViewModel(ticker: .stub, apiService: MockStocksAPI())

    }

    

    static var previews: some View {

        Group {


            StockChartDetail_View(chartVM: chartVM, quotesVM: tradingStubsQuoteVM,

                                  transactionList: [], wlTicker: tradingStubsQuoteVM.ticker)

                .previewDisplayName("Trading")

                .frame(height: 700)

            

            StockChartDetail_View(chartVM: chartVM, quotesVM: closedStubsQuoteVM,

                                  transactionList: [], wlTicker: tradingStubsQuoteVM.ticker)

                .previewDisplayName("Closed")

                .frame(height: 700)

            

            StockChartDetail_View(chartVM: chartVM, quotesVM: loadingStubsQuoteVM,

                                  transactionList: [], wlTicker: tradingStubsQuoteVM.ticker)

                .previewDisplayName("Loading Quote")

                .frame(height: 700)

            

            StockChartDetail_View(chartVM: chartVM, quotesVM: errorStubsQuoteVM,

                                  transactionList: [], wlTicker: tradingStubsQuoteVM.ticker)

                .previewDisplayName("Error Quote")

                .frame(height: 700)

            

        }.previewLayout(.sizeThatFits)

    }

}

#endif



Here's the Transaction - a transactionList is just an [Transaction] array.  I quit using plurals for arrays a while back and started the convention of prepending "List" to make the code WAY more obvious!  This structure is presisted in CloudKit so it contains a ckRecord and quite a few extensions for converting to/from CloudKit.


//

//  Transaction.swift

//  AssetsFolio

//

//  Created by David on 1/1/23.

//


import Foundation

import CloudKit


struct Transaction: CKPrecipitable, Hashable, Identifiable {

    var ckRecord: CKRecord?

    var id = UUID()

    

    public let symbol:              String

    public let shortname:           String?

    public let exchange:            String?

    

    public let transactionType:     String?

    public let shares:              Double?

    public let price:               Double?

    public let fee:                 Double?

    public let date:                Date?

    public let splitRatio:          String?       // must handle both 3:2 and 1:5

    public let transactionTotal:    Double?

    public var folioName:           String? = "WatchList"     // for CK - must be primitive not FolioItem


    // init("AAPL") all optional except the symbol - no ckRecord.

    public init(symbol: String, shortname: String?, exchange: String?,

                transactionType: String?,

                shares: Double?,

                price: Double?, fee: Double?, date: Date?,

                splitRatio: String?, transactionTotal: Double?,

                folioName: String?

    ) {

        self.symbol = symbol

        self.shortname = shortname

        self.exchange = exchange

        self.transactionType = transactionType

        self.shares = shares

        self.price = price

        self.fee = fee

        self.date = date

        self.splitRatio = splitRatio

        self.transactionTotal = transactionTotal

        self.folioName = folioName

        

        self.ckRecord = nil

    }

    // init("AAPL") all optional except the symbol - no ckRecord.

    public init(symbol: String, shortname: String?, exchange: String?,

                transactionType: String?,

                shares: Double?,

                price: Double?, fee: Double?, date: Date?,

                splitRatio: String?, transactionTotal: Double?,

                folioName: String?,

                record: CKRecord) {

        self.symbol = symbol

        self.shortname = shortname

        self.exchange = exchange

        self.transactionType = transactionType

        self.shares = shares

        self.price = price

        self.fee = fee

        self.date = date

        self.splitRatio = splitRatio

        self.transactionTotal = transactionTotal

        self.folioName = folioName

        

        self.ckRecord = record

    }

    // A FAILALBE init() - returns Optional Transaction? - Converts from CKRecord to Transaction

    init?(from ckRecord: CKRecord) {

        //        print("Transaction init from CKRecord: recordID is: \(ckRecord.recordID)")

        //        print("Transaction init from CKRecord: record   is: \(ckRecord)")

        guard

            let symbol = ckRecord[TransactionRecordKeys.symbol.rawValue] as? String,

            let shortname = ckRecord[TransactionRecordKeys.shortname.rawValue] as? String,

            let exchange = ckRecord[TransactionRecordKeys.exchange.rawValue] as? String,

            

            let transactionType = ckRecord[TransactionRecordKeys.transactionType.rawValue] as? String,

            let shares = ckRecord[TransactionRecordKeys.shares.rawValue] as? Double,

            let price = ckRecord[TransactionRecordKeys.price.rawValue] as? Double,

            let fee = ckRecord[TransactionRecordKeys.fee.rawValue] as? Double,

            let date = ckRecord[TransactionRecordKeys.date.rawValue] as? Date,

            let splitRatio = ckRecord[TransactionRecordKeys.splitRatio.rawValue] as? String,

            let transactionTotal = ckRecord[TransactionRecordKeys.transactionTotal.rawValue] as? Double,

            let folioName = ckRecord[TransactionRecordKeys.folioName.rawValue] as? String

                

        else {

            return nil

        }


        self = .init(symbol: symbol, shortname: shortname,

                     exchange: exchange,

                     transactionType: transactionType,

                     shares: shares, price: price, fee: fee, date: date,

                     splitRatio: splitRatio, transactionTotal: transactionTotal,

                     folioName: folioName,

                     record: ckRecord)

    }

    

    

    static let timestamp = Date(timeIntervalSince1970: 1671645600)

    static let preview = Transaction(symbol: "AAPL", shortname: "Apple Inc.", exchange: "NASDAQ",

                                     transactionType: "BUY", shares: 10.0, price: 122.34, fee: 0.0,

                                     date: timestamp, splitRatio: "1:1", transactionTotal: 1223.40, folioName: "Awesome")

}



// In the example below, you see the simple Transaction value type that I want to store on CloudKit.

// CloudKit provides us CKRecord type representing items in the CloudKit database.

// Usually, we need to implement a converter from/to CKRecord for our custom types.

// CKRecord is a Dictionary with some special Meta data fields such as RecordID


extension Transaction {

    /// A type converter from Transaction to CKRecord. Wraps the Transaction as a CKRecord-Dictionary using TransactionRecordKeys subscripts.

    var convertToRecord: CKRecord {

        let record : CKRecord

        if self.ckRecord != nil {

            // use existing record and update it.

            record = self.ckRecord!

            

            record[TransactionRecordKeys.symbol.rawValue]           = symbol

            record[TransactionRecordKeys.shortname.rawValue]        = shortname

            record[TransactionRecordKeys.exchange.rawValue]         = exchange

            

            record[TransactionRecordKeys.transactionType.rawValue]  = transactionType

            record[TransactionRecordKeys.shares.rawValue]           = shares

            record[TransactionRecordKeys.price.rawValue]            = price

            record[TransactionRecordKeys.fee.rawValue]              = fee

            record[TransactionRecordKeys.date.rawValue]             = date

            record[TransactionRecordKeys.splitRatio.rawValue]       = splitRatio

            record[TransactionRecordKeys.transactionTotal.rawValue] = transactionTotal

            record[TransactionRecordKeys.folioName.rawValue]        = folioName

            

        } else {

            record = CKRecord(recordType: TransactionRecordKeys.type.rawValue)

            

            record[TransactionRecordKeys.symbol.rawValue]           = symbol

            record[TransactionRecordKeys.shortname.rawValue]        = shortname

            record[TransactionRecordKeys.exchange.rawValue]         = exchange

            

            record[TransactionRecordKeys.transactionType.rawValue]  = transactionType

            record[TransactionRecordKeys.shares.rawValue]           = shares

            record[TransactionRecordKeys.price.rawValue]            = price

            record[TransactionRecordKeys.fee.rawValue]              = fee

            record[TransactionRecordKeys.date.rawValue]             = date

            record[TransactionRecordKeys.splitRatio.rawValue]       = splitRatio

            record[TransactionRecordKeys.transactionTotal.rawValue] = transactionTotal

            record[TransactionRecordKeys.folioName.rawValue]        = folioName

        }

        return record   // a CKRecord

    }

}



extension Transaction  : CustomStringConvertible {

    var description: String {

        

        if self.ckRecord != nil {

            return "( \(symbol) \(transactionType!) \(shares!) @ $\(price!) on \(date!) )"

        } else {

            return "( \(symbol) \(transactionType!) \(shares!) @ $\(price!) on \(date!)  ckRecord is nil )"

        }

    }

}



enum TransactionRecordKeys: String {

    case type = "Transaction"

    // no id

    case symbol         // AAPL

    case shortname      // Apple Inc.

    case exchange       // NASDAQ

    

    case transactionType    // BUY or SELL or SPLIT or ... Dividend, Fee, other

    case shares

    case price

    case fee

    case date

    case splitRatio

    case transactionTotal

    case folioName

}


// See https://www.rambo.codes/posts/2020-02-25-cloudkit-101 Improving the code with a custom subscript

// Usage:

//   record[.symbol] = "AAPL"

//   record[.shortname] = "Apple Inc."

/// Extend CKRecord with TransactionRecordKeys as subscripts.

extension CKRecord {

    subscript(key: TransactionRecordKeys) -> Any? {

        get {

            return self[key.rawValue]

        }

        set {

            self[key.rawValue] = newValue as? CKRecordValue

        }

    }

}



Output of a console trace...

let _ = print("\(count):Transaction date \(transaction.date!) for $\(transaction.price!)  Vertical RuleMark Buy at \(position) list count \(transactionList.count)")

Notice:  the list count is constant at 10 the whole time.  The loop executes 20 times (2X 10). Element 11 is same as Element 1....


1:Transaction date 2023-01-28 00:50:51 +0000 for $147.53  Vertical RuleMark Buy at 125.0 list count 10

2:Transaction date 2021-01-27 23:14:00 +0000 for $138.53  Vertical RuleMark Buy at -90.0 list count 10

3:Transaction date 2022-01-26 23:43:00 +0000 for $147.53  Vertical RuleMark Buy at -90.0 list count 10

4:Transaction date 2023-01-28 00:51:04 +0000 for $147.53  Vertical RuleMark Buy at 125.0 list count 10

5:Transaction date 2023-01-28 00:50:55 +0000 for $147.53  Vertical RuleMark Buy at 125.0 list count 10

6:Transaction date 2022-12-29 03:09:00 +0000 for $136.53  Vertical RuleMark Buy at 105.0 list count 10

7:Transaction date 2022-01-26 23:43:00 +0000 for $140.53  Vertical RuleMark Buy at -90.0 list count 10

8:Transaction date 2023-01-28 00:50:58 +0000 for $147.53  Vertical RuleMark Buy at 125.0 list count 10

9:Transaction date 2023-01-11 23:40:00 +0000 for $147.53  Vertical RuleMark Buy at 114.0 list count 10

10:Transaction date 2023-01-28 00:51:01 +0000 for $147.53  Vertical RuleMark Buy at 125.0 list count 10

11:Transaction date 2023-01-28 00:50:51 +0000 for $147.53  Vertical RuleMark Buy at 125.0 list count 10

12:Transaction date 2021-01-27 23:14:00 +0000 for $138.53  Vertical RuleMark Buy at -90.0 list count 10

13:Transaction date 2022-01-26 23:43:00 +0000 for $147.53  Vertical RuleMark Buy at -90.0 list count 10

14:Transaction date 2023-01-28 00:51:04 +0000 for $147.53  Vertical RuleMark Buy at 125.0 list count 10

15:Transaction date 2023-01-28 00:50:55 +0000 for $147.53  Vertical RuleMark Buy at 125.0 list count 10

16:Transaction date 2022-12-29 03:09:00 +0000 for $136.53  Vertical RuleMark Buy at 105.0 list count 10

17:Transaction date 2022-01-26 23:43:00 +0000 for $140.53  Vertical RuleMark Buy at -90.0 list count 10

18:Transaction date 2023-01-28 00:50:58 +0000 for $147.53  Vertical RuleMark Buy at 125.0 list count 10

19:Transaction date 2023-01-11 23:40:00 +0000 for $147.53  Vertical RuleMark Buy at 114.0 list count 10

20:Transaction date 2023-01-28 00:51:01 +0000 for $147.53  Vertical RuleMark Buy at 125.0 list count 10