#!/usr/bin/perl -w use strict; use warnings; my @tick_data = ( # DATE & TIME PRICE VOL. "2025-01-12 09:30:00", 100.5, 200, "2025-01-12 09:30:30", 100.8, 100, "2025-01-12 09:31:00", 101.0, 150, "2025-01-12 09:31:00", 100.9, 250, "2025-01-12 09:31:01", 101.2, 300, "2025-01-12 09:31:05", 101.5, 100, "2025-01-12 09:31:13", 101.7, 85, "2025-01-12 09:31:14", 103.6, 3500, "2025-01-12 09:31:14", 103.5, 1500, "2025-01-12 09:31:15", 103.4, 800, "2025-01-12 09:31:17", 103.3, 50, "2025-01-12 09:31:29", 103.2, 100, "2025-01-12 09:31:31", 103.4, 450, "2025-01-12 09:31:45", 103.8, 930, "2025-01-12 09:32:00", 107.00, 40000, "2025-01-12 09:32:01", 105.85, 10550, "2025-01-12 09:32:02", 105.85, 500, "2025-01-12 09:32:03", 105.84, 7600, "2025-01-12 09:32:08", 105.8, 3600, "2025-01-12 09:32:11", 105.89, 100, "2025-01-12 09:32:18", 105.75, 200 ); # Display raw input data: print "\n\tTIME STAMP\t\tPRICE\tVOLUME\n"; foreach (@tick_data) { print( (length($_) > 12) ? "\n" : '', "\t$_"); } # Group data into OHLCV intervals (e.g., 1 minute) my $interval_seconds = 30; # Set interval in seconds my $NOGAPS = 1; # When NOGAPS=1, previous close will ALWAYS be equal next bar's open. # When NOGAPS=0, there might be a gap between previous close and # next bar's open. # First, we convert time stamps to seconds foreach (@tick_data) { # Match time stamp format: $_ =~ m/\d{4}-\d{1,2}-\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2}/ and $_ = timestamp_to_epoch($_); } display_original_data(@tick_data); # Show our work # Next we convert the price data... my @ohlcv_data = convert_tick_data($interval_seconds, @tick_data); disply_ohlcv(@ohlcv_data); # Show final result exit; #################################################################### sub convert_tick_data { my $interval = shift; my @OUTPUT; my $O = 0; # Open my $H = 0; # High my $L = 0; # Low my $C = 0; # Close my $V = 0; # Volume my $INIT_PRICE; my $i = 0; my $START = -1; my $DIFF; while ($i < @_) { my $TIME = $_[$i++]; my $PRICE = $_[$i++]; my $VOLUME = $_[$i++]; if ($START >= 0) { $DIFF = $TIME - $START; if ($DIFF < $interval) # Adjust O H L C V data as time goes { $PRICE > $H and $H = $PRICE; $PRICE < $L and $L = $PRICE; $V += $VOLUME; $C = $PRICE; next; } if ($DIFF >= $interval) # Take a snapshot here { $O and push(@OUTPUT, $O, $H, $L, $C, $V); # Reset values $START = $TIME - ($DIFF % $interval); $V = $VOLUME; $INIT_PRICE = $PRICE; if ($NOGAPS) { # Previous bar's close is the initial price: $O = $H = $L = $C; if ($INIT_PRICE) { # If there's new data, then we process it here: $PRICE > $H and $H = $PRICE; $PRICE < $L and $L = $PRICE; $C = $PRICE; } } else { $O = $H = $L = $C = $INIT_PRICE; } } } else { $START = $TIME; $O = $H = $L = $C = $PRICE; $V = $VOLUME; } } $O and push(@OUTPUT, $O, $H, $L, $C, $V); return @OUTPUT; } #################################################################### sub disply_ohlcv { print "\n\n\n\tOPEN\tHIGH\tLOW\tCLOSE\tVOLUME\n"; while (@_ >= 5) { my $O = shift; my $H = shift; my $L = shift; my $C = shift; my $V = shift; print "\n\t$O\t$H\t$L\t$C\t$V"; } print "\n"; } #################################################################### # # This function loosely converts a YYYY-MM-DD HH:MM:SS formatted # time stamp to seconds. # # Usage: INTEGER = timestamp_to_epoch(STRING) # sub timestamp_to_epoch { defined $_[0] or return 0; my @T = split(/\D+/, $_[0]); return $T[5] + $T[4] * 60 + $T[3] * 3600 + $T[2] * 86400 + $T[1] * 2678400 + $T[0] * 32140800; } #################################################################### # sub display_original_data { print "\n\n\n\tSECONDS\t\tPRICE \tVOLUME\n"; my $i = 0; while ($i < @_) { my $TIME = $_[$i++]; my $PRICE = $_[$i++]; my $VOLUME = $_[$i++]; printf("\n\t%.0f\t%.4f\t%.4f", $TIME, $PRICE, $VOLUME); } print "\n"; }