Speeding up this Loop? (1 Viewer)

hascons

Registered User.
Local time
Yesterday, 17:29
Joined
Apr 20, 2009
Messages
58
Hello guys

I have this program (attached) that I've been working on that does work.

It takes 1 Minute stock data and forms different time bars in this case 60 Minute time bars of the date, time, open,high,low,close.

The problem is that it takes 25 seconds for each days of data, which is 8 records per day. On a very large databaase of say 30 Years data that would equal approx 7500 days to create data for. That would take about 60 hours for each stock.

I was wondering if the code I've attached could be sped up or if i'm going about my approach all wrong.

I'm essentially creating a recordset based on a specific day and a specific Hourly time bar and then looping through this recordset to fill variables that will be written back to another table.

I've tried to fill the variables by querying each variable with a Select Max / Min but this was extremely slow. I've also tried a DMax / DMin approach but again this was much slower than looping through as I've done here.

Anyone have any suggestions as to how to speed this up? Maybe an array ?


Code:
Sub Create60MinuteDataBar()

'' This program takes 1 minute stock data bars and forms
'' 60 minute stock data bars
'
'  The program uses 2 tables
'  SPX_1  Where 1 minute data bars are stored
'  SPX_1  Fields (ID,BarOpen,BarHigh,BarLow,BarClose)
'
'  SPX_60 Where the calculated 60 Minute Bars are created and then stored
'  SPX_60  Fields (ID,BarOpen,BarHigh,BarLow,BarClose)
'
'  The Program first creates a recordset rst1 of Distinct Days of 1 Minute
'  bar data, which is used as a filter to gather that days records From
'  SPX_1 to create the 60 Minute Data Bar
'
'  The Program also uses a variable x as an additional filter to gather records
'  for the current created Hour Daa Bar from the table SPX_1
'
'  The program loops through the distinct days and then loops through and gathers
'  Data from SPX_1 to create a recordset of the current Hour Data Bar being formed
'
'  With The Recordset Now Formed The ( Open, High,Low,Close ) Can be Formed
'
'  4 Variables (CurOpen,CurHigh,CurLow,CurClose) are used to store required data
'  while the information is gathered for each
'
'  The 60 Minute Data Bar Open is simply the very first record BarOpen
'  The 60 Minute BarHigh, Bar Low are created by looping through the recordset
'  and comparing Each against the assigned variables
'  The 60 Minute BarClose is simply the very last record BarClose
'
'  With the Variables now filled the 60 Minute Data Bar can Now be Created
'  and added to the table SPX_60

Dim dbs As Database
Dim rst As DAO.Recordset, rst1 As DAO.Recordset, rst2 As DAO.Recordset
Dim curYear As Integer, x As Integer, y As Long
Dim sql As String, CurDay As Date, sTime As Integer, eTime As Integer
Dim CurOpen As Double, CurHigh As Double, CurLow As Double, CurClose As Double
Dim CurDirection As String

Set dbs = CurrentDb
Set rst2 = dbs.OpenRecordset("SPX_60")


    sql = "SELECT DISTINCT SPX_1.BarDate" & _
        " From SPX_1" & _
        " order by SPX_1.BarDate;"

Set rst1 = dbs.OpenRecordset(sql)

    sql = ""
    
    
    rst1.Move 100
    
    'Do While Not rst1.EOF
    For y = 100 To 110
    
    
        Debug.Print (Now)  ' This is used to start timer
        
            For x = 900 To 1600 Step 100
             
                CurDay = rst1!BarDate
                sTime = x
                eTime = sTime + 60
         
                'Get Open Price
                
                sql = "SELECT SPX_1.BarDate,SPX_1.BarTime," & _
                    " SPX_1.BarOpen, SPX_1.BarHigh, SPX_1.BarLow,SPX_1.BarClose" & _
                    " FROM SPX_1" & _
                    " WHERE SPX_1.BarDate = #" & CurDay & "#" & _
                    " And SPX_1.BarTime >=" & sTime & _
                    " And SPX_1.BarTime < " & eTime & _
                    " ORDER BY SPX_1.BarTime;"

                Set rst = dbs.OpenRecordset(sql)
                
                If Not rst.EOF Then
                
                    'Get Open and Set High / Low variables
                    rst.MoveFirst
                    CurOpen = rst!BarOpen
                    CurHigh = rst!BarHigh
                    CurLow = rst!BarLow
                    
                    Do While Not rst.EOF
                    
                        If rst!BarHigh > CurHigh Then
                            CurHigh = rst!BarHigh
                        End If
                        
                        If rst!BarLow < CurLow Then
                            CurLow = rst!BarLow
                        End If
                    
                        
                        rst.MoveNext
                    
                    Loop
                    
                    'Get Low
                    rst.MoveLast
                    CurClose = rst!BarClose
                   
                    
                End If
                
                    '' Uncomment the next block of code when updating SPX_60 Table
                    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
                    
                    With rst2
                    
                        '.AddNew
                        '!BarDate = CurDay
                        '!BarTime = sTime
                        '!BarOpen = CurOpen
                        '!BarHigh = CurHigh
                        '!BarLow = CurLow
                        '!BarClose = CurClose
                        '.Update
                    
                    'End With
                    
                    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
                    
                    ' Used for testing
                    Debug.Print CurDay, sTime, CurOpen, CurHigh, CurLow, CurClose

                'Debug.Print y, CurDay, sTime ' Used to Monitor Program while its running
                
            Next x
        
        rst1.MoveNext
        
     Next y
    'Loop
    
    Debug.Print (Now)  ' Used for Timer
    Debug.Print "Done"  ' Used to inform that current iterations have ended
    
    
rst.Close
rst1.Close
rst2.Close
Set rst = Nothing
Set rst1 = Nothing
Set rst2 = Nothing
Set dbs = Nothing

End Sub
 

gemma-the-husky

Super Moderator
Staff member
Local time
Today, 01:29
Joined
Sep 12, 2006
Messages
15,653
t's hard to understand exactly what you doing in the loop, when you iterate every record?

if you want to find the highest/lowest values then a simple dmax or dmin will do it one line., although you say that was slower????

can you explain in simpler terms exactly what you are trying to accomplish?

eg - why control the loop with this?

For x = 900 To 1600 Step 100
 

hascons

Registered User.
Local time
Yesterday, 17:29
Joined
Apr 20, 2009
Messages
58
Dave,

I have a database of 1 Minute stock Data, Each record represents the (Date,Time,Open,High,Low,Close) for a 1 minute period going back 30 years. The databases for each stock has roughy 3.5 Million records of 1 minute data.

I would like to create different time frames such as 5 minute , 30 minute 60 minute etc.. for further analysis.

What i need to accomplish is create (60 minute in this example ) a 60 minute record consisting of the Open price, High price, Low Price, and Close price of a certain 60 minute period.

To find the Open / Close is just a matter of getting a recordset for a certain 60 minute period for a certain day. The 60 minute time frame start at the beginning of the hour (900, 1000,1100 etc..). I can then navigate to the first or last record and pull the information required.

Getting the high or low of any particular period requires finding the max or min of a certain 60 minute data block.

I tried using DMax and Dmin to do this. I may have done this improperly as i used the Dmax / Dmin on the 1minute data table filtering for a specific Day and 60 Minute block of time. To find these records seamed to take a very long time ( again probably because it is searching a very large recordset ). I'm not sure if i can narrow this down to just the recordset i create to get the first and last record of each 60 minute dataset.

I then tried what i posted here which essentially loops through a distinct daily list of dates and then creates a 60 record recordset by filtering the table based on the day and then on the 60 minute time block. ( For x = 900 to 1600 Skip 100).

This method had serious improvements in time 3 seconds per new record as compared with the DMax / Dmin approach which took 18 seconds per record.

I do believe there must be an even better approach to reduce the time required to process and create a new record.

Hope i explained this a little better
 

JHB

Have been here a while
Local time
Today, 02:29
Joined
Jun 17, 2012
Messages
7,732
How many records do you've to deal with in the below code, (I think you could do it without having to loop through each record)?
Code:
               If Not rst.EOF Then
                
                    'Get Open and Set High / Low variables
                    rst.MoveFirst
                    CurOpen = rst!BarOpen
                    CurHigh = rst!BarHigh
                    CurLow = rst!BarLow
                    
                    Do While Not rst.EOF
                    
                        If rst!BarHigh > CurHigh Then
                            CurHigh = rst!BarHigh
                        End If
                        
                        If rst!BarLow < CurLow Then
                            CurLow = rst!BarLow
                        End If
                    
                        
                        rst.MoveNext
                    
                    Loop
                    
                    'Get Low
                    rst.MoveLast
                    CurClose = rst!BarClose
                   
                    
                End If
 

hascons

Registered User.
Local time
Yesterday, 17:29
Joined
Apr 20, 2009
Messages
58
JHB

In this particular example there would be a max of 60 records ( 1 for each minute of Data ).
 

brokkel

Registered User.
Local time
Today, 02:29
Joined
Aug 13, 2008
Messages
10
I think I should know a good way of speeding up, let me test something
 

hascons

Registered User.
Local time
Yesterday, 17:29
Joined
Apr 20, 2009
Messages
58
Brokkel

Thanks, I look forward to seeing what you've come up with. I've been testing other methods but can seam to better the 3 seconds per record with the code I've posted above.
 

arnelgp

..forever waiting... waiting for jellybean!
Local time
Today, 08:29
Joined
May 7, 2009
Messages
19,237
you can use snapshot recordset in rs2.
use can use transaction on your workspace.
if it is fe/be try to have an open instance of your be.
 

Solo712

Registered User.
Local time
Yesterday, 20:29
Joined
Oct 19, 2012
Messages
828
This is confusing as hell!

1) it is not clear how many reads there are on the one-minute "DataBar". If it is a single read as the text suggests (by saying there are 60 reads for the hour data bar), then the High, Low, Open, Close reads on the minute data bar are silly. One reading value is all there should be.

2) It makes no sense to create a separate "time" field for either bar. Use date/time variable (cast as a double) in the recordset and the hour and minute of recording is passed to the record at the time of the recording. If then you need a min/max value within a given period of hours and days, you do it through SELECT of the BETWEEN date/time values by converting the day to the whole number and the post decimal. 1 Hour = .04167 in the remainder. I absolutely refuse to believe that this would be slower than the looping presented above.

Best,
Jiri
 

Cronk

Registered User.
Local time
Today, 10:29
Joined
Jul 4, 2013
Messages
2,772
First thing I'd be checking is that there are table indexes on appropriate fields.

Instead of Domain functions and looping through recordsets, have you tried queries? ie select top 1 .... order xxx desc to get the maximum

Also, record times for the individual processes, not just the whole program, to give a hint where optimization can be achieved.
 

gemma-the-husky

Super Moderator
Staff member
Local time
Today, 01:29
Joined
Sep 12, 2006
Messages
15,653
on reflection ...

you mean you monitor stock real time at one minute intervals?
why? how many stock lines do you have?

surely you can reconstruct the stock at any point in time from basic transaction records (in/out records)?
 

hascons

Registered User.
Local time
Yesterday, 17:29
Joined
Apr 20, 2009
Messages
58
Jiri

I wasn't sure what you were saying.


I've posted some sample data which I hope explains better what I'm trying to do.


ID BarDate BarTime BarOpen BarHigh BarLow BarClose
342 1/3/1986 1000 210.19 210.19 210.19 210.19
343 1/3/1986 1001 210.2 210.2 210.2 210.2
344 1/3/1986 1002 210.18 210.18 210.18 210.18
345 1/3/1986 1003 210.2 210.2 210.2 210.2
346 1/3/1986 1004 210.19 210.19 210.19 210.19
347 1/3/1986 1005 210.2 210.2 210.2 210.2
348 1/3/1986 1006 210.16 210.16 210.16 210.16
349 1/3/1986 1007 210.17 210.17 210.17 210.17
350 1/3/1986 1008 210.2 210.2 210.2 210.2
351 1/3/1986 1009 210.21 210.21 210.21 210.21
352 1/3/1986 1010 210.2 210.2 210.2 210.2
353 1/3/1986 1011 210.24 210.24 210.24 210.24
354 1/3/1986 1012 210.31 210.31 210.31 210.31
355 1/3/1986 1013 210.36 210.36 210.36 210.36
356 1/3/1986 1014 210.46 210.46 210.46 210.46
357 1/3/1986 1015 210.51 210.51 210.51 210.51
358 1/3/1986 1016 210.62 210.62 210.62 210.62
359 1/3/1986 1017 210.65 210.65 210.65 210.65
360 1/3/1986 1018 210.69 210.69 210.69 210.69
361 1/3/1986 1019 210.74 210.74 210.74 210.74
362 1/3/1986 1020 210.78 210.78 210.78 210.78
363 1/3/1986 1021 210.76 210.76 210.76 210.76
364 1/3/1986 1022 210.75 210.75 210.75 210.75
365 1/3/1986 1023 210.78 210.78 210.78 210.78
366 1/3/1986 1024 210.69 210.69 210.69 210.69
367 1/3/1986 1026 210.65 210.65 210.65 210.65
368 1/3/1986 1027 210.66 210.66 210.66 210.66
369 1/3/1986 1028 210.64 210.64 210.64 210.64
370 1/3/1986 1031 210.62 210.62 210.62 210.62
371 1/3/1986 1035 210.6 210.6 210.6 210.6
372 1/3/1986 1036 210.58 210.58 210.58 210.58
373 1/3/1986 1037 210.55 210.55 210.55 210.55
374 1/3/1986 1039 210.58 210.58 210.58 210.58
375 1/3/1986 1040 210.56 210.56 210.56 210.56
376 1/3/1986 1041 210.53 210.53 210.53 210.53
377 1/3/1986 1042 210.52 210.52 210.52 210.52
378 1/3/1986 1044 210.5 210.5 210.5 210.5
379 1/3/1986 1045 210.52 210.52 210.52 210.52
380 1/3/1986 1047 210.53 210.53 210.53 210.53
381 1/3/1986 1048 210.5 210.5 210.5 210.5
382 1/3/1986 1049 210.52 210.52 210.52 210.52
383 1/3/1986 1050 210.49 210.49 210.49 210.49
384 1/3/1986 1051 210.47 210.47 210.47 210.47
385 1/3/1986 1052 210.48 210.48 210.48 210.48
386 1/3/1986 1053 210.5 210.5 210.5 210.5
387 1/3/1986 1054 210.48 210.48 210.48 210.48
388 1/3/1986 1055 210.49 210.49 210.49 210.49
389 1/3/1986 1056 210.44 210.44 210.44 210.44
390 1/3/1986 1057 210.45 210.45 210.45 210.45
391 1/3/1986 1058 210.42 210.42 210.42 210.42
392 1/3/1986 1059 210.4 210.4 210.4 210.4


This is 1 60 Minute Bar created from record above.

ID Date Time Open High Low Close
1 1/3/1986 1000 210.19 210.78 210.16 210.4

I'm trying to create a 1 record from 60 records

Open = BarOpen on 1st Record (ID 342)
High = BarHigh On Record (362)
Low = BarLow On Record (348)
Close = BarClose Last Record (392)

---------------------------------------------------------------------
Cronk

I have tried select queries to get the variables. It does take much longer that way.

I am probably wrong but i had to create 3 different recordsets
1 for open close
1 with a max for the high
1 with a min for the low

I set timers for each time I created a Select query. Each query seamed to run for about 5 seconds for each. To create the 1 record above took between 16 and 18 seconds.
 

arnelgp

..forever waiting... waiting for jellybean!
Local time
Today, 08:29
Joined
May 7, 2009
Messages
19,237
create another recordset rsFiltered.
open rst once, without filter on the top of your code, together with rs2 this.

on the inner loop filter rst:
rst.Filter = "BarTime >=#" & sTime & "# And SPX_1.BarTime < #" & eTime & "#"

now open rsFiltered based on rst:
set rsFiltered = rs.Openrecordset(dbopendynaset)

now loop on this new recordset, updating if you like.
since rst is already in memory, we do not need to fetch new data.
we only get new filtered data (rsFiltered) from memory (rst).
 

WayneRyan

AWF VIP
Local time
Today, 01:29
Joined
Nov 19, 2002
Messages
7,122
Hascons,

This is from an IPhone so I'll be brief.

Have a look at the Partition function. If you Partition your day then you
Can Group by and get the Min/Max prices for each partition. you can also Min/max the time of each partition so that your NEXT query can get the
Tougher Opening/Closing prices.

If you take the time to set it up properly, it will be WAY faster than the
Record set approach.

HTH,
Wayne
 

Galaxiom

Super Moderator
Staff member
Local time
Today, 10:29
Joined
Jan 20, 2009
Messages
12,852
since rst is already in memory, we do not need to fetch new data.
we only get new filtered data (rsFiltered) from memory (rst).

In my experience it is much faster to pull multiple small recordsets with the specific data required than apply a Filter to a large recordset.
 

Galaxiom

Super Moderator
Staff member
Local time
Today, 10:29
Joined
Jan 20, 2009
Messages
12,852
Have a look at the Partition function. If you Partition your day then you can Group by and get the Min/Max prices for each partition. you can also Min/max the time of each partition so that your NEXT query can get the Tougher Opening/Closing prices.

If you take the time to set it up properly, it will be WAY faster than the
Record set approach.

I would also recommend this technique.
 

hascons

Registered User.
Local time
Yesterday, 17:29
Joined
Apr 20, 2009
Messages
58
Arnelgp

I did originally try this method, leaving the rst recordset open and filtering the data as needed, but this method is actually almost 2 seconds slower per record then using the where clauses in the SQL statement.

--------------------------------------------------------------------------------

WayneRyan

I'm not sure what the partition function is but i will investigate.
 

WayneRyan

AWF VIP
Local time
Today, 01:29
Joined
Nov 19, 2002
Messages
7,122
Hascons,

This is a rough idea. The first query breaks your data into partitions AND
gets the Opening/Closing date (for your next query).

Code:
Select Stock, 				Your Stock ID
       BarDate, 			Just the Date part of BarDate
       Partition(MinutesSinceMidnight,  You calculate this value with DateDiff
                 0,			Day starts at minute 0
                 1440,			Minutes in a day
                 60),			width of each partition
       Min(BarDate),			Your NEXT query will use to get OPENING
       Max(BarDate),			Your NEXT query will use to get CLOSING
       Min(SellingPrice),		This will just carry over
       Max(SellingPrice)		This will just carry over
From   YourTable
Group By Stock, BarDate, Partition(...)

The second query will just join back to the original table and pick
up the opening/closing prices.

This should be WAY faster than your recordset approach.

For refinements, the 1440 and 60 values can be programmatically altered to let
you have partitions of a size different than 1 hour.

hth,
Wayne
 

hascons

Registered User.
Local time
Yesterday, 17:29
Joined
Apr 20, 2009
Messages
58
WayneRyan

Thank You so much for sharing this function with me.

Your example was a great starting point for What I was trying to accomplish. My BarTime is actually stored as a long integer as opposed to actual time, so it was fairly simple to adjust your sample code to produce the desired results.

I would like to take a moment and thank everyone of you experts at this forum, you provide an extremely valuable tool for the rest of us novices. Without your generous help many of us would possibly give up trying to solve some of the technical problems that come up.

I have modified the sql statement and posted it below for anybody else who would be interested in it.


Code:
sql = "SELECT SPX_1.BarDate, Partition(BarTime,900,1600,100) AS Bar_Period," & _
      " First(SPX_1.BarOpen) AS Bar_Open, Max(SPX_1.BarHigh) AS Bar_High," & _
      " Min(SPX_1.BarLow) AS Bar_Low, Last(SPX_1.BarClose) AS Bar_Close" & _
      " FROM SPX_1" & _
      " GROUP BY SPX_1.BarDate, Partition(BarTime,900,1600,100);"


This is a sample of the results

BarDate Bar_Period Bar_Open Bar_High Bar_Low Bar_Close
1/2/1986 900: 999 211.24 211.24 209.14 209.15
1/2/1986 1000:1099 209.21 209.61 209.18 209.47
1/2/1986 1100:1199 209.46 210.34 209.46 210.33
1/2/1986 1200:1299 210.35 210.39 209.66 209.91
1/2/1986 1300:1399 209.93 209.93 209.66 209.75
1/2/1986 1400:1499 209.73 209.91 209.73 209.82
1/2/1986 1500:1599 209.84 209.84 208.93 209.59
1/3/1986 900: 999 209.6 210.21 209.51 210.18
1/3/1986 1000:1099 210.19 210.78 210.16 210.4
1/3/1986 1100:1199 210.41 210.41 210.17 210.31
1/3/1986 1200:1299 210.29 210.3 210.14 210.3
1/3/1986 1300:1399 210.27 210.67 210.27 210.64
1/3/1986 1400:1499 210.65 210.65 210.23 210.25
1/3/1986 1500:1599 210.26 210.88 210.09 210.88
 

hascons

Registered User.
Local time
Yesterday, 17:29
Joined
Apr 20, 2009
Messages
58
WayneRyan

I should have mentioned this in the last post as this was the main purpose for this thread.

To separate the data as shown above takes approx 25 seconds on a datafile with close to 4,000,000 records.

That is absolutely amazing!

Again Thanks for sharing this function with us!
 

Users who are viewing this thread

Top Bottom