VTF – Fun With Time

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…