Did I say “fun”? Yes, I did. Though figuring this out initially wasn’t very fun at first. When it comes to price data, you have to know a few things when collating a bunch of historical prices. Namely the duration from one date to the next, or from a point in the past to the present.
My intent with this was to only pull the minimum amount of data from Tradingview, so I didn’t abuse their websocket and cause some admin somewhere to curse at my IP when he viewed the server logs. This requires some time functions, so I set about to make one.
Like any task, sometimes I think “Hey, this is only going to be a few steps.” Then later, when I look up at the clock and its 2am, I realize that I have plumbed a very deep rabbit-hole of specialized knowledge that I only want the barest nuance of.
For instance for my application I want a whole day’s prior data. Turns out there’s 6 bars in a day using a 4-hour period per bar. (6 x 4 = 24, so that is cool.) But, at any given time I start the main polling process, I won’t be precisely 24 hours out, I’ll be 24 + some random interval of time. So I have to calculate the proper offset to get everything up to now.
Okay – no problem, right? Just take a date like March 29th, and subtract one day and start there, right? — well, sure if its the middle or end of the month, what happens when you do it after crossing over to the 1st of the next month?
That means you have to know how many days in a month there are, when they change, and god help you — if its a leap year because Feb will have 29 days instead of 28.
So guess what I did?
I made a function that could determine those transitions – and get this – even CENTURY leap years, which is funny since I’m not going to live long enough to see another one, but hell, I guess I wanted to cover it anyway.
So that is how things sort of expand and become a bit more complex when determining what to do in a program. Here’s what I came up with for basic timestamp stuff:
import datetime import pytz timeZone = pytz.timezone('US/Central') # GMT -6 hrs currDate = datetime.datetime.now(timeZone) priorNaive = datetime.datetime(2022,3,27,00,00,00) priorDate = priorNaive.astimezone(timeZone) # Make timezone aware secElapsed = (currDate-priorDate).total_seconds() print("Prior Date: " + str(priorDate)) print("Current Date: " + str(currDate)) print("Elapsed seconds: " + str(secElapsed) + "\n")
Which results in two timezone-aware timestamps calculating elapsed seconds:
Prior Date: 2022-03-27 00:00:00-05:00 Current Date: 2022-03-28 21:56:48.813351-05:00 Elapsed seconds: 165408.813351
And just for anyone who needed something that does leap year/century in Python:
import re import datetime import pytz import math timeZone = pytz.timezone('US/Central') # GMT -5 hrs during CDT, -6 CST #yearTest = ["2024"] #dateTest = ["07","04"] ## Month, days - (leap year changes Feb): ## Jan (31), Feb (28 or 29), Mar (31), Apr (30), May (31), Jun (30), Jul (31), Aug (31), Sep (30), Oct (31), Nov (30), Dec (31) ## To be a leap year, the year number must be divisible by 4 except for end-of-century leap years, which must be divisible by 400. ## So the year 2000 was a end-of-century leap year, although 1900 was not. 2024 and 2028 are upcoming leap years ## The below returns YYYY-MM-DD def dateCalcOffset(myDateList, myYearList): # Checks for leap year/century and month boundary transitions months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"] # Months for debug, etc.. numDays = [31,28,31,30,31,30,31,31,30,31,30,31] # Feb can be 29 days if its a leap year endOfCentury = "false" endOfCentLeap = "false" leapYear = "false" days = 0 leapDays = 0 priorDay = 0 # Cast these as ints for calcs myMonth = int(myDateList) myDay = int(myDateList) myYear = int(myYearList) # Leap year - Is it a leap year? # First check if its end of century using modulo centuryRemainder = myYear % 100 if centuryRemainder == 0: endOfCentury = "true" if endOfCentury == "true": centuryRemainder = myYear % 400 # If true, check if century leap year if centuryRemainder == 0: endOfCentLeap = "true" #print("Year: " + myYearList + " is an end of century leap year.") # Debug # Add extra day if month is February if myMonth == 2: leapDays = 29 print("Feb has: " + str(leapDays) + " days.") # Debug else: #print("Year: " + myYearList + " is not a end of century leap year.") # Debug days = numDays[myMonth-1] # or just return days per month index #print(months[myMonth-1] + " has : " + str(days) + " days.") # Debug else: #print("Year: " + myYearList + " is not the end of a century.") # Debug nothing = 0 # Does nothing, but python wants something here if above is commented out lol # Now check for leap year - only if century check isn't positive if endOfCentury == "false" and endOfCentLeap == "false": leapRemainder = myYear % 4 if leapRemainder == 0: # No remainder? Its a leap year #print("Year: " + myYearList + " is a leap year.") # Debug leapYear = "true" # Add extra day if month is February if myMonth == 2: leapDays = 29 #print("Feb has: " + str(leapDays) + " days.") # Debug else: days = numDays[myMonth-1] # or just return days per month index #print(months[myMonth-1] + " has: " + str(days) + " days.") # Debug else: #print("Year: " + myYearList + " is not a leap year.") # Debug days = numDays[myMonth-1] # or just return days per month index #print(months[myMonth-1] + " has: " + str(days) + " days.") # Debug # Input check - if day is greater than number of days for that month - throw error if myDay > days: # Shouldn't happen, but catch it in case... print("Input day greater than maximum allowed for month/leap calcs.") return else: # Do calculations accounting for being first of month from prior month if myDay == 1 and endOfCentLeap == "true" or leapYear == "true": # Is it leap year/century? if myMonth == 2: # February 1st? myMonth = myMonth-1 # Decrement month priorDay = numDays[myMonth-1] # Get last day of prior month else: # Not the 1st of the month, and not Feb if myDay > 1 and myDay <= numDays[myMonth-1]: priorDay = myDay-1 else: # Not a leap year/century if myDay == 1: # First of month? myMonth = myMonth-1 # Decrement month priorDay = numDays[myMonth-1] # Get last day of prior month else: if myDay > 1 and myDay <= numDays[myMonth-1]: # Not first of month - first week or later? priorDay = myDay-1 return myYear, myMonth, priorDay def getCurrentStamp(myTimeZone): # Takes pytz assigned timezone for calcs myCurrentStamp = datetime.datetime.now(myTimeZone) return myCurrentStamp def getOffsetStamp(myCurrent, myTimeZone): dateRegex = '-(\d\d)' # Splits out stuff after '-' character yearRegex = '^(\d\d\d\d)' # Splits out year myDateParsed = re.findall(dateRegex, str(myCurrent)) # returns list, element is month, element is day myYearParsed = re.findall(yearRegex, str(myCurrent)) # returns list, element is year offsetDate = dateCalcOffset(myDateParsed, myYearParsed) myPriorNaive = datetime.datetime(offsetDate,offsetDate,offsetDate,00,00,00) ## Year,Month,Day,Hr,Min,Sec myPriorStamp = myPriorNaive.astimezone(myTimeZone) # Make timezone aware return myPriorStamp def elapsedSeconds(myCurrent, myPrior): # Gets elapsed from two time-zone aware datestamps ttlSeconds = (myCurrent-myPrior).total_seconds() return ttlSeconds def numOfBars(mySeconds, barInterval): # Takes total seconds, Bar interval in minutes barSeconds = barInterval * 60 ttlBarNum = math.trunc(mySeconds / barSeconds) # Drop decimals, can't get a fraction of a bar return ttlBarNum #--------------- Testing Functions #today = getCurrentStamp(timeZone) #offset = getOffsetStamp(today, timeZone) #secondsBetween = elapsedSeconds(today, offset) #barResolution = 240 # Minutes #howManyBars = numOfBars(secondsBetween, barResolution) #print("Todays timestamp: " + str(today)) #print("Prior timestamp: " + str(offset)) #print("Elapsed seconds: " + str(secondsBetween)) #print("Elapsed minutes: " + str(math.trunc(secondsBetween/60))) #print("Elapsed hours: " + str(math.trunc((secondsBetween/60)/60))) #print("Retrieve " + str(howManyBars) + " bars of historical data.")
Yes, I included the other functions and my commented-out testing statements for completeness. The output looks like this:
Todays timestamp: 2022-03-29 13:09:16.194627-05:00 Prior timestamp: 2022-03-28 00:00:00-05:00 Elapsed seconds: 133756.194627 Elapsed minutes: 2229 Elapsed hours: 37 Retrieve 9 bars of historical data.
Now I’m set to do all kinds of time things with bar data! It only took plenty of TIME to figure it out, lol.
More to come…