File Coverage

blib/lib/Finance/GeniusTrader/Tools.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Finance::GeniusTrader::Tools;
2              
3             # Copyright 2000-2002 Raphaël Hertzog, Fabien Fulhaber
4             # This file is distributed under the terms of the General Public License
5             # version 2 or (at your option) any later version.
6              
7             # baseline 8 Jun 2005 9830 bytes
8             # $Id$
9              
10 1     1   13 use strict;
  1         2  
  1         33  
11 1     1   5 use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS $PI);
  1         2  
  1         230  
12              
13             require Exporter;
14             @ISA = qw(Exporter);
15             @EXPORT_OK = qw(min max PI sign
16             extract_object_number
17             resolve_alias resolve_object_alias long_name short_name
18             isin_checksum isin_validate isin_create_from_local
19             get_timeframe_data parse_date_str find_calculator check_dates
20             );
21             %EXPORT_TAGS = ("math" => [qw(min max PI sign)],
22             "generic" => [qw(extract_object_number)],
23             "conf" => [qw(resolve_alias resolve_object_alias long_name short_name)],
24             "isin" => [qw(isin_checksum isin_validate isin_create_from_local)],
25             "timeframe" => [qw(get_timeframe_data parse_date_str find_calculator check_dates)]
26             );
27              
28 1     1   633 use Finance::GeniusTrader::DateTime;
  0            
  0            
29             use Finance::GeniusTrader::Prices;
30             use Finance::GeniusTrader::Eval;
31             use Finance::GeniusTrader::ArgsTree;
32             use Date::Calc qw( Date_to_Days );
33              
34             =head1 NAME
35              
36             Finance::GeniusTrader::Tools - Various helper functions
37              
38             =head1 DESCRIPTION
39              
40             This modules provides several helper functions that can be used in all
41             modules and scripts.
42              
43             There are 5 groupings
44              
45             =over 6
46              
47             =item * math -- min, max, pi, sign
48              
49             =item * generic -- extract_object_number
50              
51             =item * conf -- resolve_alias resolve_object_alias long_name short_name
52              
53             =item * isin -- isin_checksum isin_validate isin_create_from_local
54              
55             =item * timeframe -- get_timeframe_data parse_date_str find_calculator check_dates
56              
57             =back
58              
59             =head2 math
60              
61             It provides mathematical functions, that can be imported with
62             use Finance::GeniusTrader::Tools qw(:math) :
63              
64             =over 4
65              
66             =item C<< PI() >>
67              
68             Returns PI.
69              
70             =item C<< min(...) >>
71              
72             Returns the minimum of all given arguments.
73              
74             =item C<< max(...) >>
75              
76             Returns the maximum of all given arguments.
77              
78             =item C<< sign($value) >>
79              
80             Returns 1 for a positive (or null) value, -1 for a negative value.
81              
82             =back
83              
84             =cut
85             sub PI() { 3.14159265 }
86              
87             sub max {
88             my $max = $_[0];
89             foreach (@_) {
90             if (! defined($_)) {
91             warn "Finance::GeniusTrader::Tools::max called with undef argument !\n";
92             next;
93             }
94             if ( ! m/\d/ ) {
95             # warn "Finance::GeniusTrader::Tools::max called with non-numeric argument \"$_\" !\n";
96             next;
97             }
98             $max = ($_ > $max) ? $_ : $max;
99             }
100             return $max;
101             }
102              
103             sub min {
104             my $min = $_[0];
105             foreach (@_) {
106             if (! defined($_)) {
107             warn "Finance::GeniusTrader::Tools::min called with undef argument !\n";
108             next;
109             }
110             if ( ! m/\d/ ) {
111             # warn "Finance::GeniusTrader::Tools::min called with non-numeric argument \"$_\" !\n";
112             next;
113             }
114             $min = ($_ < $min) ? $_ : $min;
115             }
116             return $min;
117             }
118              
119             sub sign {
120             ($_[0] >= 0) ? 1 : -1;
121             }
122              
123             =pod
124              
125             =head2 generic
126              
127             It provides helper functions to manage arguments in "Generic" objects.
128             You can import those functions with use Finance::GeniusTrader::Tools qw(:generic) :
129              
130             =over 4
131              
132             =item C<< extract_object_number(@args) >>
133              
134             Returns the number associated to the first the object described
135             by the arguments.
136              
137             =back
138              
139             =cut
140             sub extract_object_number {
141             my ($name) = shift;
142             if ($name =~ m#/(\d+)$#)
143             {
144             return $1 - 1;
145             }
146             return 0;
147             }
148              
149             =pod
150              
151             =head2 conf
152              
153             And a few other very-specific functions :
154              
155             use Finance::GeniusTrader::Tools qw(:conf) :
156              
157             =over
158              
159             =item C<< resolve_alias($alias) >>
160              
161             Return the long name of the system as described in the configuration
162             file.
163              
164             =cut
165             sub resolve_alias {
166             my ($alias) = @_;
167             my $name = $alias;
168             my @param;
169             if ($alias =~ m/^\s*(.*)\s*\[(.*)\]\s*$/) {
170             $name = $1;
171             @param = split(",", $2);
172             }
173             my $sysname = '';
174             if (scalar @param) {
175             $sysname = Finance::GeniusTrader::Conf::get("Aliases::Global::$name" . "[]");
176             } else {
177             $sysname = Finance::GeniusTrader::Conf::get("Aliases::Global::$name");
178             }
179             if (! $sysname)
180             {
181             die "$0: error: Alias `$alias' wasn't found in options file!"
182             . "\nkey looked for was \"Aliases::Global::$name\"\n";
183             }
184             # The alias content may list another alias ...
185             while ($sysname !~ /:/) {
186             $sysname = resolve_alias($sysname);
187             }
188             my $n = 1;
189             foreach (@param)
190             {
191             $sysname =~ s/#$n/$_/g;
192             $n++;
193             }
194              
195             # Take care about operators + - / * in a string like #1+#2
196             eval {
197             $sysname =~ s|(\d+)\*(\d+)| $1 * $2 |eg;
198             $sysname =~ s|(\d+)\/(\d+)| $1 / $2 |eg;
199             $sysname =~ s|(\d+)\+(\d+)| $1 + $2 |eg;
200             $sysname =~ s|(\d+)\-(\d+)| $1 - $2 |eg;
201             };
202            
203             if ($sysname =~ /#(\d+)/)
204             {
205             die "The alias '$alias' is lacking the parameter number $1.\n";
206             }
207             return $sysname;
208             }
209              
210             =item C<< resolve_object_alias($alias, @param) >>
211              
212             Return the complete description of the object designed by "alias". @param
213             is the array of parameters as returned by Finance::GeniusTrader::ArgsTree::parse_args().
214              
215             Object aliases can be defined in global files (as defined in the option
216             Path::Aliases::), for each kind of object (e.g., Signals,
217             Indicators, etc.), or in user-specific files (~/.gt/aliases/).
218              
219             Such aliases are defined via the syntax
220            
221              
222             For example
223             MyMean { I:Generic:Eval ( #1 + #2 ) / 2 }
224              
225             An object alias can also be defined in the option file with the syntax
226             Aliases::::
227              
228             For example:
229              
230             Aliases::Indicators::MyMean { I:Generic:Eval ( #1 + #2 ) / 2 }
231              
232             Then you can use this alias in any other place where you could have used
233             a standard indicator as argument. Here's how you would reference it with
234             custom parameters :
235              
236             { @I:MyMean 50 {I:RSI} }
237              
238             If you don't need any parameters then you can just say "@I:MyMean".
239              
240             =cut
241             sub resolve_object_alias {
242             my ($alias, @param) = (@_);
243              
244             # Lookup the alias
245             my $def = Finance::GeniusTrader::Conf::get("Aliases\::$alias");
246            
247             my $n = 1;
248             foreach my $arg (Finance::GeniusTrader::ArgsTree::args_to_ascii(@param))
249             {
250             $def =~ s/#$n/$arg/g;
251             $n++;
252             }
253              
254             # Take care about operators + - / * in a string like #1+#2
255             eval {
256             $def =~ s|(\d+)\*(\d+)| $1 * $2 |eg;
257             $def =~ s|(\d+)\/(\d+)| $1 / $2 |eg;
258             $def =~ s|(\d+)\+(\d+)| $1 + $2 |eg;
259             $def =~ s|(\d+)\-(\d+)| $1 - $2 |eg;
260             } if $def;
261            
262             return $def;
263             }
264              
265             =item C<< my $l = long_name($short) >>
266              
267             =item C<< my $s = short_name($long) >>
268              
269             Most module names can be shortened with some standard abreviations. Those
270             functions let you switch between the long and the short version of the
271             names. The recognized abreviations are :
272              
273             =over 6
274              
275             =item * Analyzers:: = A:
276              
277             =item * CloseStrategy:: = CS:
278              
279             =item * Generic:: = G:
280              
281             =item * Indicators:: = I:
282              
283             =item * MoneyManagement:: = MM:
284              
285             =item * OrderFactory:: = OF:
286              
287             =item * Signals:: = S:
288              
289             =item * Systems:: = SY:
290              
291             =item * TradeFilters:: = TF:
292              
293             =back
294              
295             =cut
296             sub long_name {
297             my ($name) = @_;
298              
299             $name =~ s/A::?/Analyzers::/g;
300             $name =~ s/CS::?/CloseStrategy::/g;
301             $name =~ s/OF::?/OrderFactory::/g;
302             $name =~ s/TF::?/TradeFilters::/g;
303             $name =~ s/MM::?/MoneyManagement::/g;
304             $name =~ s/SY::?/Systems::/g;
305             $name =~ s/S::?/Signals::/g;
306             $name =~ s/I::?/Indicators::/g;
307             $name =~ s/G::?/Generic::/g;
308             $name =~ s/:+/::/g;
309              
310             return $name;
311             }
312             sub short_name {
313             my ($name) = @_;
314              
315             $name =~ s/Generic::?/G:/g;
316             $name =~ s/Indicators::?/I:/g;
317             $name =~ s/Systems::?/SY:/g;
318             $name =~ s/Signals::?/S:/g;
319             $name =~ s/TradeFilters::?/TF:/g;
320             $name =~ s/CloseStrategy::?/CS:/g;
321             $name =~ s/MoneyManagement::?/MM:/g;
322             $name =~ s/OrderFactory::?/OF:/g;
323             $name =~ s/Analyzers::?/A:/g;
324             $name =~ s/::/:/g;
325              
326             return $name;
327             }
328              
329             =back
330              
331             =head2 isin
332              
333             use Finance::GeniusTrader::Tools qw(:isin) :
334              
335             =over 4
336              
337             =item C<< isin_checksum($code) >>
338              
339             This computes the checksum of a given code. The whole ISIN is returned.
340              
341             =cut
342             sub isin_checksum {
343             my $isin = shift;
344             my $tmp = "";
345             return if (length($isin) < 11);
346             $isin = substr($isin, 0, 11);
347              
348             # Gernerate lookup
349             my %lookup = ();
350             my $c = 10;
351             foreach ( "A".."Z" ) {
352             $lookup{$_} = $c;
353             $c++;
354             }
355              
356             # Transform into numbers
357             for (my $i=0; $i
358             if (defined($lookup{uc(substr($isin, $i, 1))}) ) {
359             $tmp .= $lookup{uc(substr($isin, $i, 1))};
360             } else {
361             $tmp .= substr($isin, $i, 1);
362             }
363             }
364              
365             # Computation of the checksum
366             my $checksum = 0;
367             my $multiply = 2;
368             for (my $i=length($tmp)-1; $i>=0; $i--) {
369             my $t = ( $multiply * substr($tmp, $i, 1) );
370             $t = 1 + ($t % 10) if ($t >= 10);
371             $checksum += $t;
372             $multiply = ($multiply==2) ? 1 : 2;
373             }
374             $checksum = 10 - ($checksum % 10);
375             $checksum = 0 if ($checksum == 10);
376              
377             return $isin . $checksum;
378             }
379              
380             =item C<< isin_validate($isin) >>
381              
382             Validate the ISIN and its checksum.
383              
384             =back
385              
386             =cut
387             sub isin_validate {
388             my $isin = shift;
389             my $isin2 = isin_checksum($isin);
390             return if (!defined($isin2));
391             return 1 if ($isin eq $isin2);
392             return 0;
393             }
394              
395             sub isin_create_from_local {
396             my ($country, $code) = @_;
397             $country = uc($country);
398             while ( length($code) < 9 ) {
399             $code = "0" . $code;
400             }
401             my $isin = isin_checksum("$country$code");
402             return $isin;
403             }
404              
405             =head2 timeframe
406              
407             use Finance::GeniusTrader::Tools qw(:timeframe) :
408              
409             =over 4
410              
411             =item C<< GetTimeFrameData ($code, $timeframe, $db, $max_loaded_items) >>
412              
413             Returns a prices and a calculator object with data for the required
414             $code in the specified $timeframe. It uses $db object to fetch the data.
415             If for instance, weekly data is requested, but only daily data is available,
416             the weekly data is calculated from the daily data.
417              
418             Optionally, you can set the configuration file directive DB::timeframes_available
419             to specify which timeframes are available.
420             For instance:
421             DB::timeframes_available 5min,hour,day
422              
423             =cut
424             sub get_timeframe_data {
425             my ($code, $timeframe, $db, $max_loaded_items) = @_;
426             #WAR# WARN "Fetching all available data, because the max_loaded_items parameter was not set." unless(defined($max_loaded_items));
427             $max_loaded_items = -1 unless(defined($max_loaded_items));
428             my @tf;
429             my $available_timeframes = Finance::GeniusTrader::Conf::get('DB::timeframes_available');
430             my $q;
431              
432             die("Max loaded items cannot be zero") if ($max_loaded_items==0);
433             die("Parameter \$code not set in get_timeframe_data") if (!defined($code));
434             die("Parameter \$timeframe not set in get_timeframe_data") if (!defined($timeframe));
435             die("Parameter \$db not set in get_timeframe_data") if (!defined($db));
436              
437             if (defined($available_timeframes)) {
438             foreach my $tf_name (split(',', $available_timeframes)) {
439             my $tf_code = Finance::GeniusTrader::DateTime::name_to_timeframe($tf_name);
440             push @tf, $tf_code;
441             if (!defined($tf_code)) {
442             my $tfs = join("\n\t", map(Finance::GeniusTrader::DateTime::name_of_timeframe($_), Finance::GeniusTrader::DateTime::list_of_timeframe));
443             die("Invalid timeframe name in available_timeframes configuration item: $tf_name\n\nValid timeframes are: \n\t" . $tfs . "\n\n");
444             }
445             }
446             @tf = sort(@tf);
447             } else {
448             @tf = Finance::GeniusTrader::DateTime::list_of_timeframe;
449             }
450              
451             #ERR# ERROR "Invalid db argument in get_timeframe_data" unless ( ref($db) =~ /Finance::GeniusTrader::DB/);
452             #ERR# ERROR "Timeframe parameter not set in get_timeframe_data." unless(defined($timeframe));
453              
454             foreach(reverse(@tf)) {
455             next if ($_ > $timeframe);
456             $q = $db->get_last_prices($code, $max_loaded_items, $_);
457             last if ($q->count > 0);
458             }
459              
460             if (!defined($q) || $q->count == 0) {
461             my $msg="No data available to generate ".Finance::GeniusTrader::DateTime::name_of_timeframe($timeframe)." data.";
462             $msg.="\nAvailable timeframes are: ($available_timeframes)" if (defined($available_timeframes));
463             die($msg);
464             }
465              
466             warn ("No data is available to complete the request for $code") if ($q && $q->count == 0);
467             my $calc = Finance::GeniusTrader::Calculator->new($q);
468             $calc->set_code($code);
469              
470             if ($q->timeframe != $timeframe) {
471             $calc->set_current_timeframe($timeframe);
472             $q = $calc->prices;
473             }
474              
475             return ($q, $calc);
476             }
477              
478             sub parse_date_str {
479             #
480             # inputs: date string reference var required
481             # error string reference var (optional)
482             # returns 1 for good date
483             # zero (null) for bad date
484             #
485             # notes: @ if called in void context with bad date value the internal
486             # error handling will put error message text on stderr and die called
487             # @ date ref var may be altered to conform to std date-time format
488             # @ error string will contain details about bad date-time string
489             #
490             # usage examples
491             # typical usage in perl script
492             # my $err_msg;
493             # if ( ! parse_date_str( \$date, \$err_msg ) ) {
494             # die "Error: $err_msg\n";
495             # }
496             #
497             # usage using internal error handling
498             # my $date = "24oct07";
499             # parse_date_str( \$date );
500             #
501             my ( $dtstref, $errref ) = @_;
502              
503             if ( eval { require Date::Manip } ) {
504             use Date::Manip qw(ParseDate UnixDate);
505             if ( $$dtstref =~ m/[- :\w\d]/ ) {
506             if ( my $date = ParseDate($$dtstref) ) {
507             $$dtstref = UnixDate("$date", "%Y-%m-%d %T");
508             }
509             }
510             }
511             # dates only allow digits, date separator is '-', time separator is ':'
512             # date and time field separator is a single space not even a tab
513             #
514             # timeframe seps: '-' day and week
515             # '/' month
516             # '_' date and time part separator
517             if ( $$dtstref =~ m/[^- :\d]/ ) {
518             # bad chars in date string
519             $$errref = "invalid character in date \"$$dtstref\"" if ( $errref );
520             return if defined wantarray;
521             die "pds: invalid character in date \"$$dtstref\"\n";
522             }
523             my ( $year, $mon, $day, $time )
524             = $$dtstref =~ /^(\d{4})-?(\d{2})-?(\d{2})\s?([\d:]+)*$/;
525             # not capturing time field separator intentionally
526             if ( ! $year || ! $mon || ! $day ) {
527             $$errref = "bad date format \"$$dtstref\"" if ( $errref );
528             return if defined wantarray;
529             die "pds: bad date format \"$$dtstref\"\n";
530             }
531              
532             # valididate date
533             if ( ! Date::Calc::check_date($year, $mon, $day) ) {
534             $$errref = "invalid date \"$$dtstref\"" if ( $errref );
535             return if defined wantarray;
536             die "pds: invalid date \"$$dtstref\"\n";
537             }
538              
539             # valididate time
540             if ( $time ) {
541             my ( $hour, $min, $sec ) = split /:/, $time;
542             if ( ! Date::Calc::check_time($hour, $min, $sec) ) {
543             #print STDERR "pds: invalid time \"$hour:$min:$sec\"\n";
544             #return 0 if ( defined wantarray );
545             $$errref = "invalid time \"$time\"" if ( $errref );
546             return if defined wantarray;
547             die "pds: invalid time \"$time\"\n";
548             }
549             }
550              
551             # good date
552             # clear err just in case
553             $$errref = "" if ( $errref );
554              
555             return 1;
556              
557             =pod
558              
559             =item C<< parse_date_str ( \$date_string, \$err_msg ) >>
560              
561             Returns 1 if \$date_string is valid parsable date, zero (or null) otherwise
562             \$date_string will be altered to be a gt compliant date string on return
563             \$err_msg is optional
564              
565             =over 6
566              
567             =item * input params must be references to the object
568              
569             =item * if called in void context with bad date value the internal
570             error handling will put error message text on stderr and die called
571              
572             =item * date ref var may be altered to conform to std date-time format
573              
574             =item * error string will contain details about bad date-time string
575              
576             =back
577              
578             If the user has Date::Manip installed it allows the use of date strings
579             that can be parsed by Date::Manip in addition the to defacto standard
580             date-time format accepted by GT (YYYY-MM-DD HH:MM:SS) time part is optional
581              
582             Date::Manip is not required, without it users cannot use short-cuts to
583             specify date strings. such short cuts include
584             --start '6 months ago'
585             --end 'today'
586              
587             The date string checking includes verifying the date string format
588             is valid and the date is a valid date (and time if provided)
589              
590             Errors will be displayed and the script will terminate.
591              
592             =head3 Application usage examples:
593              
594             with Date::Manip installed
595              
596             % scan.pl --timeframe day --start '6 months ago' \
597             --end 'today' market_file 'today' system_file
598              
599             without Date::Manip you will need to use:
600              
601             % scan.pl --timeframe day --start 2007-04-24 \
602             --end 2007-10-24 market_file 2007-10-24 system_file
603              
604             or
605              
606             % scan.pl --timeframe week --start 2007-04-24
607             --end 2007-10-24 market_file 2007-10-24 system_file
608              
609             =head3 Usage of parse_date_str in application script
610              
611             use Finance::GeniusTrader::Tools qw( :timeframe );
612             # tag name to get &parse_date_str visibility
613              
614             my $err_msg;
615             # get date string from command line
616             my $date = shift;
617              
618             my ( $d_yr, $d_mn, $d_dy, $d_tm );
619             if ( ! parse_date_str( \$date, \$err_msg ) ) {
620             die "Error: $err_msg\n";
621             } else {
622             ( $d_yr, $d_mn, $d_dy, $d_tm ) = split /[- ]/, $date;
623             }
624              
625             =cut
626             } # sub parse_date_str
627              
628             =item C<< find_calculator($code, $timeframe, $full, $start, $end, $nb_item, $max_loaded_item) >>
629              
630             Find a calculator: Returns $calc (the calculator), as well as
631             $first and $last (indices used by the calculator).
632              
633             The interval examined (bound by $first and $last) is computed as follows (stop whenever $first and $last have been determined):
634             1. if present, use --start (otherwise default $first to 2 years back) and --end (otherwise default $last to last price)
635             2. use --nb-item (from first or last, whichever has been determined), if present
636             3. use first or last price, whichever has not yet been determined, if --full is present
637             4. otherwise, use two years worth of data.
638              
639             Note that the values given to --start and --end are relative to the selected time frame (i.e., if timeframe is "day", these indicate a date; if timeframe is "week", these indicate a week; etc.). Format is "YYYY-MM-DD" for dates, "YYYY-WW" for weeks, "YYYY-MM" for months, and "YYYY" for years.
640              
641             =cut
642              
643             sub find_calculator {
644             my ($db, $code, $timeframe, $full, $start, $end, $nb_item, $max_loaded_items) = @_;
645             $nb_item ||= 0;
646             $max_loaded_items ||= -1;
647              
648             if (!defined $timeframe) {
649             my $msg = "Unkown timeframe. Available timeframes are:\n";
650             foreach (Finance::GeniusTrader::DateTime::list_of_timeframe()) {
651             $msg .= "\t".Finance::GeniusTrader::DateTime::name_of_timeframe($_) . "\n";
652             }
653             die($msg);
654             }
655              
656             my ($prices, $calc) = get_timeframe_data($code, $timeframe, $db, $max_loaded_items);
657              
658             my $c = $prices->count;
659             my $first;
660             my $last;
661             $last = $c - 1 unless ($end || $start);
662             if ($end) {
663             my $date = $prices->find_nearest_preceding_date($end);
664             $last = $prices->date($date);
665             }
666             if ($start) {
667             my $date = $prices->find_nearest_following_date($start);
668             $first = $prices->date($date);
669             }
670             unless ($start) {
671             $first = $last - 2 * Finance::GeniusTrader::DateTime::timeframe_ratio($YEAR,
672             $calc->current_timeframe);
673             $first = 0 if ($full);
674             $first = $last - $nb_item + 1 if ($nb_item);
675             $first = 0 if ($first < 0);
676             }
677             unless ($last) {
678             $last = $first + 2 * Finance::GeniusTrader::DateTime::timeframe_ratio($YEAR,
679             $calc->current_timeframe);
680             $last = $c - 1 if ($full);
681             $last = $first + $nb_item - 1 if ($nb_item);
682             $last = $c - 1 if ($last >= $c);
683             }
684              
685             return ($calc, $first, $last);
686              
687             }
688              
689             =item C<< check_dates($timeframe, $start, $end, $date) >>
690              
691             Converts the given dates into the proper dates relative to the
692             chosen time frame, if necessary. For example, if a date 2000-02-01
693             is given with --timeframe=week, this date is converted to 2000-05.
694              
695             Verifies that the start date is before the end.
696              
697             If a third date is given, verifies that this date is between the
698             start and end dates.
699              
700             =cut
701              
702             sub check_dates {
703              
704             my $timeframe = $_[0];
705             my $start = $_[1];
706             my $end = $_[2];
707             my $date = $_[3] || 0;
708              
709             my $err_msg;
710              
711             $timeframe = $DAY unless ( $timeframe );
712              
713             my ( $s_yr, $s_mn, $s_dy, $s_tm );
714             if ( $start ) {
715             if ( ! parse_date_str( \$start, \$err_msg ) ) {
716             die "Error: \$err_msg\n";
717             } else {
718             ( $s_yr, $s_mn, $s_dy, $s_tm ) = split /[- ]/, $start;
719             }
720             }
721              
722             my ( $e_yr, $e_mn, $e_dy, $e_tm );
723             if ( $end ) {
724             if ( ! parse_date_str( \$end, \$err_msg ) ) {
725             die "Error: \$err_msg\n";
726             } else {
727             ( $e_yr, $e_mn, $e_dy, $e_tm ) = split /[- ]/, $end;
728             }
729             }
730              
731             if ( $start && $end ) {
732             # $start must be prior to $end
733             if (Date_to_Days($s_yr, $s_mn, $s_dy) >=
734             Date_to_Days($e_yr, $e_mn, $e_dy)) {
735             die "Error: --start date $start must be prior to --end date $end\n";
736             }
737             }
738            
739             # timeframe relative date conversions
740             if ( $start && $timeframe != $DAY ) {
741             $start = Finance::GeniusTrader::DateTime::convert_date($start, $DAY, $timeframe);
742             }
743              
744             if ( $end && $timeframe != $DAY ) {
745             $end = Finance::GeniusTrader::DateTime::convert_date($end, $DAY, $timeframe);
746             }
747              
748             my ( $d_yr, $d_mn, $d_dy, $d_tm );
749             if ( $date ) {
750             if ( ! parse_date_str( \$date, \$err_msg ) ) {
751             die "Error: $err_msg\n";
752             } else {
753             ( $d_yr, $d_mn, $d_dy, $d_tm ) = split /[- ]/, $date;
754             }
755              
756             if ( $end ) {
757             # $date must be $end or before
758             if (Date_to_Days($d_yr, $d_mn, $d_dy) >
759             Date_to_Days($e_yr, $e_mn, $e_dy)) {
760             die "Error: date $date must be prior to or equal to --end date $end\n";
761             }
762             }
763            
764             if ( $start ) {
765             # $start must be prior to $date
766             if (Date_to_Days($s_yr, $s_mn, $s_dy) >=
767             Date_to_Days($d_yr, $d_mn, $d_dy)) {
768             die "Error: --start $start must be prior to date $date\n";
769             }
770             }
771              
772             if ( $timeframe != $DAY && $timeframe > $DAY ) {
773             $date = Finance::GeniusTrader::DateTime::convert_date($date, $DAY, $timeframe);
774             }
775            
776             $_[3] = $date;
777              
778             }
779              
780             $_[1] = $start;
781             $_[2] = $end;
782              
783             }
784              
785             =back
786              
787             =cut
788             1;