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?
Oh man…
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[0])
myDay = int(myDateList[1])
myYear = int(myYearList[0])
# 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[0] + " 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[0] + " 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[0] + " 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[0] + " 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[0] + " 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[0] is month, element[1] is day
myYearParsed = re.findall(yearRegex, str(myCurrent)) # returns list, element[0] is year
offsetDate = dateCalcOffset(myDateParsed, myYearParsed)
myPriorNaive = datetime.datetime(offsetDate[0],offsetDate[1],offsetDate[2],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…