Finance-Calendar

 view release on metacpan or  search on metacpan

t/calendar.t  view on Meta::CPAN

#!/usr/bin/perl

use strict;
use warnings;

use Test::MockTime qw( :all );
use Test::Most;
use Test::FailWarnings;
use Time::Local ();

use Finance::Exchange;
use Finance::Calendar;

my $date = Date::Utility->new('2013-12-01');    # first of December 2014

my $calendar = {
    holidays => {
        1367798400 => {
            "Early May Bank Holiday" => [qw(LSE)],
        },
        1387929600 => {
            "Christmas Day" => [qw(LSE FOREX METAL)],
        },
        1388534400 => {
            "New Year's Day" => [qw(LSE FOREX METAL)],
        },
        1364774400 => {
            "Easter Monday" => [qw(LSE USD)],
        },
    },
    early_closes => {
        1261612800 => {
            '4h30m' => ['HKSE'],
        },
        1293148800 => {'12h30m' => ['LSE']},
        1387843200 => {
            '12h30m' => ['LSE'],
        },
        1482364800 => {
            '18h' => ['FOREX', 'METAL'],
        },
    },
    late_opens => {
        1293148800 => {
            '2h30m' => ['HKSE'],
        },
    },
};

my $tc = Finance::Calendar->new(calendar => $calendar);
my ($LSE, $RANDOM, $FOREX, $ASX, $HKSE, $METAL, $JSC, $SWX) =
    map { Finance::Exchange->create_exchange($_) } qw(LSE RANDOM FOREX ASX HKSE METAL JSC SWX);

subtest 'trades_on' => sub {
    ok !$tc->trades_on($LSE,   Date::Utility->new('1-Jan-14')),  'LSE doesn\'t trade on 1-Jan-14 because it is on holiday.';
    ok !$tc->trades_on($LSE,   Date::Utility->new('12-May-13')), 'LSE doesn\'t trade on weekend (12-May-13).';
    ok $tc->trades_on($LSE,    Date::Utility->new('3-May-13')),  'LSE trades on normal day 4-May-13.';
    ok !$tc->trades_on($LSE,   Date::Utility->new('5-May-13')),  'LSE doesn\'t trade on 5-May-13 as it is a weekend.';
    ok $tc->trades_on($RANDOM, Date::Utility->new('5-May-13')),  'RANDOM trades on 5-May-13 as it is open on weekends.';
};

subtest 'trade_date_after' => sub {
    # forex holidays are on 2013-12-25 and 2014-01-01
    is $tc->trade_date_after($FOREX, Date::Utility->new('2013-12-23'))->date, '2013-12-24', 'trade date is next day if it is not a holiday';
    is $tc->trade_date_after($FOREX, Date::Utility->new('2013-12-24'))->date, '2013-12-26', 'trade date is on 26-Dec because 25-Dec is a holiday';
    is $tc->trade_date_after($FOREX, Date::Utility->new('2013-12-27'))->date, '2013-12-30',
        'trade date is on 30-Dec because Forex does not trade on weekend';

    is $tc->trade_date_after($RANDOM, Date::Utility->new('2013-12-24'))->date, '2013-12-25', 'trade date is on 25-Dec';
    is $tc->trade_date_after($RANDOM, Date::Utility->new('2013-12-27'))->date, '2013-12-28',
        'trade date is on 30-Dec because Random indices open on weekend';
};

subtest 'trade_date_before' => sub {
    # forex holidays are on 2013-12-25 and 2014-01-01
    is $tc->trade_date_before($FOREX, Date::Utility->new('2013-12-24'))->date, '2013-12-23', 'trade date is previous day if it is not a holiday';
    is $tc->trade_date_before($FOREX, Date::Utility->new('2013-12-26'))->date, '2013-12-24', 'trade date is on 24-Dec because 25-Dec is a holiday';
    is $tc->trade_date_before($FOREX, Date::Utility->new('2013-12-30'))->date, '2013-12-27',
        'trade date is on 27-Dec because Forex does not trade on weekend';

    is $tc->trade_date_before($RANDOM, Date::Utility->new('2013-12-25'))->date, '2013-12-24', 'trade date is on 24-Dec';
    is $tc->trade_date_before($RANDOM, Date::Utility->new('2013-12-28'))->date, '2013-12-27',
        'trade date is on 27-Dec because Random indices open on weekend';
};

subtest 'trading_date_for' => sub {
    my $non_dst_asx = Date::Utility->new('2017-09-30 15:59:59');
    my $dst_asx     = Date::Utility->new('2017-09-30 16:00:00');
    ok !$tc->is_in_dst_at($ASX, $non_dst_asx->epoch);
    ok $tc->is_in_dst_at($ASX,  $dst_asx->epoch);
    is $tc->trading_date_for($ASX, $non_dst_asx)->epoch, $non_dst_asx->truncate_to_day->epoch, 'same day on non dst';
    is $tc->trading_date_for($ASX, $dst_asx)->epoch,     $dst_asx->truncate_to_day->epoch,     'same day if time is before open on dst';
    is $tc->trading_date_for($ASX, $dst_asx->plus_time_interval('7h'))->epoch, $dst_asx->plus_time_interval('1d')->truncate_to_day->epoch, 'next day';
};

subtest 'calendar_days_to_trade_date_after' => sub {
    is($tc->calendar_days_to_trade_date_after($FOREX, Date::Utility->new('20-Dec-13')),
        3, '3 calendar days until next trading day on FOREX after 20-Dec-13');
    is($tc->calendar_days_to_trade_date_after($FOREX, Date::Utility->new('27-Dec-13')),
        3, '3 calendar days until next trading day on FOREX after 27-Dec-13');
    is($tc->calendar_days_to_trade_date_after($FOREX, Date::Utility->new('7-Mar-13')),
        1, '1 calendar day until next trading day on FOREX after 7-Mar-13');
    is($tc->calendar_days_to_trade_date_after($FOREX, Date::Utility->new('8-Mar-13')),
        3, '3 calendar days until next trading day on FOREX after 8-Mar-13');
    is($tc->calendar_days_to_trade_date_after($FOREX, Date::Utility->new('9-Mar-13')),
        2, '2 calendar days until next trading day on FOREX after 9-Mar-13');
    is($tc->calendar_days_to_trade_date_after($FOREX, Date::Utility->new('10-Mar-13')),
        1, '1 calendar day until next trading day on FOREX after 10-Mar-13');
};

subtest 'trading_days_between' => sub {
    is($tc->trading_days_between($LSE, Date::Utility->new('29-Mar-13'), Date::Utility->new('1-Apr-13')),
        0, 'No trading days between 29th Mar and 1st Apr on LSE');
    is($tc->trading_days_between($LSE, Date::Utility->new('11-May-13'), Date::Utility->new('12-May-13')),
        0, 'No trading days between 11th and 12th May on LSE (over weekend)');
    is($tc->trading_days_between($LSE, Date::Utility->new('4-May-13'), Date::Utility->new('6-May-13')),
        0, 'No trading days between 4th May and 6th May on LSE (over weekend, then holiday on Monday)');
    is($tc->trading_days_between($LSE, Date::Utility->new('10-May-13'), Date::Utility->new('14-May-13')),
        1, '1 trading day between 10th and 14th May on LSE (over weekend, Monday open)');
};

subtest 'holiday_days_between' => sub {
    is $tc->holiday_days_between($LSE, Date::Utility->new('24-Dec-13'),  Date::Utility->new('3-Jan-14')), 2, "two holidays over the year end on LSE.";
    is $tc->holiday_days_between($LSE, Date::Utility->new('2017-03-03'), Date::Utility->new('2017-03-06')), 0, 'no holidays over the weekend';
};

subtest 'open/close' => sub {
    # testing the "use current time" methods for one date/time only.
    # Rest of tests will use the "_at" methods ("current time" ones
    # use them anyway).
    Test::MockTime::set_fixed_time('2013-05-03T09:00:00Z');
    is($tc->is_open($LSE), 1, 'LSE is open at 9am on a trading day');
    Test::MockTime::restore_time();

    # before opening time on an LSE trading day:
    my $six_am       = Date::Utility->new('3-May-13 06:00:00');
    my $six_am_epoch = $six_am->epoch;
    is($tc->is_open_at($LSE, $six_am),             undef, 'LSE not open at 6am');
    is($tc->seconds_since_open_at($LSE, $six_am),  undef, 'at 6am, LSE not open yet');
    is($tc->seconds_since_close_at($LSE, $six_am), undef, 'at 6am, LSE hasn\'t closed yet');

    # after closing time on an LSE trading day:
    my $six_pm = Date::Utility->new('3-May-13 18:00:00');
    is($tc->is_open_at($LSE, $six_pm),             undef,         'LSE not open at 6pm.');
    is($tc->seconds_since_open_at($LSE, $six_pm),  11 * 60 * 60,  'at 6pm, LSE opening was 11 hours ago.');
    is($tc->seconds_since_close_at($LSE, $six_pm), 2.5 * 60 * 60, 'at 6pm, LSE has been closed for 2.5 hours.');

    # LSE holiday:
    my $tc_holiday = Date::Utility->new('6-May-13 12:00:00');
    is($tc->is_open_at($LSE, $tc_holiday),             undef, 'is_open_at LSE not open today at all.');
    is($tc->seconds_since_open_at($LSE, $tc_holiday),  undef, 'seconds_since_open_at LSE not open today at all.');
    is($tc->seconds_since_close_at($LSE, $tc_holiday), undef, 'seconds_since_close_at LSE not open today at all.');
    # DST stuff
    # Europe: last Sunday of March.
    is($tc->is_open_at($LSE, Date::Utility->new('29-Mar-13 07:30:00')), undef, 'LSE not open at 7:30am GMT during winter.');
    is($tc->is_open_at($LSE, Date::Utility->new('3-Apr-13 07:30:00')),  1,     'LSE open at 7:30am GMT during summer.');

    is(
        $tc->opening_on($LSE, Date::Utility->new('3-May-13'))->epoch,
        Date::Utility->new('3-May-13 07:00')->epoch,
        'Opening time of LSE on 3-May-13 is 07:00.'
    );
    is(
        $tc->closing_on($LSE, Date::Utility->new('3-May-13'))->epoch,
        Date::Utility->new('3-May-13 15:30')->epoch,
        'Closing time of LSE on 3-May-13 is 14:30.'
    );
    is(



( run in 1.425 second using v1.01-cache-2.11-cpan-364913b4093 )