{"action":"created","comment":{"url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls/comments/65314233","id":65314233,"diff_hunk":"@@ -0,0 +1,1513 @@\n+package Quant::Framework::VolSurface;\n+\n+=head1 NAME\n+\n+Quant::Framework::VolSurface\n+\n+=head1 DESCRIPTION\n+\n+Base class for all volatility surfaces.\n+\n+=cut\n+\n+use 5.010;\n+\n+use Moose;\n+use Try::Tiny;\n+use DateTime::TimeZone;\n+use List::MoreUtils qw(notall none all);\n+use Scalar::Util qw( looks_like_number weaken );\n+use Scalar::Util::Numeric qw( isnum );\n+use List::Util qw( min max first );\n+use Number::Closest::XS qw(find_closest_numbers_around);\n+use Math::Function::Interpolator;\n+\n+use Quant::Framework::Utils::Types;\n+use Quant::Framework::VolSurface::Cutoff;\n+use Quant::Framework::VolSurface::Validator;\n+use Quant::Framework::VolSurface::Utils;\n+use Quant::Framework::Utils::Builder;\n+\n+=head2 for_date\n+\n+The date for which we want to have the volatility surface data\n+\n+=cut\n+\n+has for_date => (\n+    is      => 'ro',\n+    isa     => 'Maybe[Date::Utility]',\n+    default => undef,\n+);\n+\n+has chronicle_reader => (\n+    is  => 'ro',\n+    isa => 'Data::Chronicle::Reader',\n+);\n+\n+has chronicle_writer => (\n+    is  => 'ro',\n+    isa => 'Data::Chronicle::Writer',\n+);\n+\n+has underlying_config => (\n+    is  => 'ro',\n+    required => 1,\n+);\n+\n+has calendar => (\n+    is         => 'ro',\n+    isa        => 'Quant::Framework::TradingCalendar',\n+    lazy_build => 1,\n+);\n+\n+sub _build_calendar {\n+    my $self = shift;\n+\n+    return $self->builder->build_trading_calendar;\n+}\n+\n+has builder => (\n+    is  => 'ro',\n+    isa => 'Quant::Framework::Utils::Builder',\n+    lazy_build => 1,\n+);\n+\n+sub _build_builder {\n+    my $self = shift;\n+\n+    return Quant::Framework::Utils::Builder->new({\n+        for_date           => $self->for_date,\n+        chronicle_reader   => $self->chronicle_reader,\n+        chronicle_writer   => $self->chronicle_writer,\n+        underlying_config => $self->underlying_config,\n+    });\n+}\n+\n+=head1 ATTRIBUTES\n+\n+=head2 symbol\n+\n+The symbol of the underlying that this surface is for (e.g. frxUSDJPY)\n+\n+=cut\n+\n+has symbol => (\n+    is       => 'ro',\n+    isa      => 'Str',\n+    required => 1,\n+);\n+\n+has _market_name => (\n+    is         => 'ro',\n+    isa        => 'Str',\n+    lazy_build => 1,\n+);\n+\n+sub _build__market_name {\n+    return shift->underlying_config->market_name;\n+}\n+\n+=head2 recorded_date\n+\n+The date (and time) that the surface was recorded, as a Date::Utility.\n+\n+=cut\n+\n+has recorded_date => (\n+    is         => 'ro',\n+    isa        => 'Date::Utility',\n+    lazy_build => 1,\n+);\n+\n+sub _build_recorded_date {\n+    my $self          = shift;\n+    my $recorded_date = Date::Utility->new($self->document->{date});\n+\n+    if ($self->type eq 'flat') {\n+        $recorded_date = $self->for_date // Date::Utility->new;\n+    }\n+\n+    return $recorded_date;\n+}\n+\n+=head2 type\n+\n+Type of the surface, delta, moneyness or flat.\n+\n+=cut\n+\n+has type => (\n+    is       => 'ro',\n+    isa      => 'qf_surface_type',\n+    required => 1,\n+    init_arg => undef,\n+    default  => undef,\n+);\n+\n+=head2 surface\n+\n+Stores the represented surface as a data structure. Required and read-only.\n+\n+Term structures of CALL delta or moneyness to annualized volatilities.\n+\n+=cut\n+\n+has surface => (\n+    is         => 'rw',\n+    isa        => 'HashRef',\n+    lazy_build => 1,\n+);\n+\n+sub _build_surface {\n+    my $self = shift;\n+    return $self->document->{surfaces}->{$self->cutoff->code};\n+}\n+\n+=head2 default_vol_spread\n+\n+This will return default volatility spread (difference between ask and bid for atm volatility).\n+\n+=cut\n+\n+has default_vol_spread => (\n+    is         => 'ro',\n+    lazy_build => 1,\n+);\n+\n+sub _build_default_vol_spread {\n+    return {shift->atm_spread_point => 0.1};\n+}\n+\n+=head2 smile_points\n+\n+The points across a smile.\n+\n+It can be delta points, moneyness points or any other points that we might have in the future.\n+\n+=cut\n+\n+has smile_points => (\n+    is         => 'ro',\n+    isa        => 'ArrayRef',\n+    lazy_build => 1,\n+);\n+\n+sub _build_smile_points {\n+    my $self = shift;\n+\n+    # Default to the point found in the first day we find\n+    # in $self->surface that has a smile. As long as each smile\n+    # has the same points, this works. If each smile has different\n+    # points, the Validator is going to give you trouble!\n+    my $surface = $self->surface;\n+\n+    my $suitable_day = first { exists $surface->{$_}->{smile} } @{$self->term_by_day};\n+    my @smile_points = ();\n+    if ($suitable_day) {\n+        @smile_points =\n+            sort { $a <=> $b } keys %{$surface->{$suitable_day}->{smile}};\n+    }\n+\n+    return \\@smile_points;\n+}\n+\n+=head2 spread_points\n+\n+This will give an array-reference containing volatility spreads for first tenor which has a volatility spread (or ATM if none).\n+=cut\n+\n+has spread_points => (\n+    is         => 'ro',\n+    isa        => 'ArrayRef',\n+    lazy_build => 1,\n+);\n+\n+sub _build_spread_points {\n+    my $self = shift;\n+\n+    # Default to the point found in the first day we find\n+    # in $self->surface that has a volspread. As long as each volspread\n+    # has the same points, this works. If each smile has different\n+    # points, the Validator is going to give you trouble!\n+    my $surface = $self->surface;\n+\n+    my $suitable_day = first { exists $surface->{$_}->{vol_spread} } keys %{$surface};\n+\n+    my @spread_points = ();\n+    if ($suitable_day) {\n+\n+        @spread_points =\n+            sort { $a <=> $b } keys %{$surface->{$suitable_day}->{vol_spread}};\n+    } else {\n+\n+        $suitable_day = first { exists $surface->{$_}->{atm_spread} } keys %{$surface};\n+        push @spread_points, 'atm_spread';\n+    }\n+\n+    return \\@spread_points;\n+}\n+\n+=head2 print_precision\n+\n+The precision at which we should output vols, in number of digits after the decimal.\n+If set 0 or undefined, we will use full precision\n+\n+=cut\n+\n+has print_precision => (\n+    is      => 'ro',\n+    isa     => 'Maybe[Num]',\n+    default => 4,\n+);\n+\n+=head2 term_by_day\n+\n+Get all the terms in a surface in ascending order.\n+\n+=cut\n+\n+has term_by_day => (\n+    is         => 'ro',\n+    isa        => 'ArrayRef',\n+    init_arg   => undef,\n+    lazy_build => 1,\n+);\n+\n+sub _build_term_by_day {\n+    my $self = shift;\n+\n+    my @days = sort { $a <=> $b } keys %{$self->surface};\n+\n+    return \\@days;\n+}\n+\n+=head2 original_term\n+\n+The terms we were originally supplied for this surface.\n+\n+=cut\n+\n+has original_term => (\n+    is         => 'ro',\n+    isa        => 'HashRef',\n+    lazy_build => 1,\n+);\n+\n+sub _build_original_term {\n+    my $self = shift;\n+\n+    my $surface = $self->surface;\n+\n+    my $atm_spread_point = $self->atm_spread_point;\n+    my $spread_type;\n+    my @vol_spreads;\n+    my @days = @{$self->term_by_day};\n+\n+    my @smiles = grep { exists $surface->{$_}->{smile} } @days;\n+\n+    foreach (@days) {\n+\n+        if (exists $surface->{$_}->{vol_spread}) {\n+            $spread_type = 'vol_spread';\n+            push @vol_spreads, $_;\n+\n+        } elsif (exists $surface->{$_}->{atm_spread}) {\n+\n+            $spread_type = 'atm_spread';\n+            push @vol_spreads, $_;\n+        }\n+\n+    }\n+\n+# We must have at least 2 atm_spreads if this is a valid surface with multiple smiles.\n+# Set the end points to the default if they are unset to make up any difference.\n+    if (scalar @vol_spreads < min(2, scalar @smiles)) {\n+        foreach my $end_index (0, -1) {\n+            if (    exists $surface->{$days[$end_index]}\n+                and not defined $surface->{$days[$end_index]}->{vol_spread}\n+                and not defined $surface->{$days[$end_index]}->{atm_spread})\n+            {\n+                $spread_type = 'vol_spread';\n+                $surface->{$days[$end_index]}->{vol_spread} = $self->default_vol_spread;\n+                push @vol_spreads, $days[$end_index];\n+\n+            }\n+        }\n+    }\n+\n+    return +{\n+        $spread_type => \\@vol_spreads,\n+        smile        => \\@smiles,\n+    };\n+\n+}\n+\n+=head2 original_term_for_smile\n+\n+The original term structure we have for smiles on a surface\n+\n+=cut\n+\n+has original_term_for_smile => (\n+    is         => 'ro',\n+    isa        => 'ArrayRef',\n+    lazy_build => 1,\n+);\n+\n+sub _build_original_term_for_smile {\n+    my $self = shift;\n+\n+    my @smile_terms = @{$self->original_term->{smile}};\n+    my @sorted = sort { $a <=> $b } @smile_terms;\n+    return \\@sorted;\n+}\n+\n+=head2 original_term_for_spread\n+\n+The original term structure we have for spreads on a surface\n+\n+=cut\n+\n+has original_term_for_spread => (\n+    is         => 'ro',\n+    isa        => 'ArrayRef',\n+    lazy_build => 1,\n+);\n+\n+sub _build_original_term_for_spread {\n+    my $self = shift;\n+    my @spread_terms =\n+        defined $self->original_term->{vol_spread}\n+        ? @{$self->original_term->{vol_spread}}\n+        : @{$self->original_term->{atm_spread}};\n+\n+    my @sorted = sort { $a <=> $b } @spread_terms;\n+\n+    return \\@sorted;\n+}\n+\n+=head2 effective_date\n+\n+Surfaces roll over at 5pm NY time, so the vols of any surfaces recorded after 5pm NY but\n+before GMT midnight are effectively for the next GMT day. This attribute holds this\n+effective date.\n+\n+=cut\n+\n+has effective_date => (\n+    is         => 'ro',\n+    isa        => 'Date::Utility',\n+    init_arg   => undef,\n+    lazy_build => 1,\n+);\n+\n+sub _build_effective_date {\n+    my $self = shift;\n+\n+    return $self->_vol_utils->effective_date_for($self->recorded_date);\n+}\n+\n+# PRIVATE ATTRIBUTES:\n+\n+=head2 _new_surface\n+\n+A flag which determines whether this surface is a newly created surface or a one which is read from historical data.\n+\n+=cut\n+\n+has _new_surface => (\n+    is      => 'ro',\n+    default => 0,\n+);\n+\n+has _vol_utils => (\n+    is         => 'ro',\n+    isa        => 'Quant::Framework::VolSurface::Utils',\n+    init_arg   => undef,\n+    lazy_build => 1,\n+);\n+\n+sub _build__vol_utils {\n+    return Quant::Framework::VolSurface::Utils->new;\n+}\n+\n+has _vol_surface_validator => (\n+    is         => 'ro',\n+    isa        => 'Quant::Framework::VolSurface::Validator',\n+    init_arg   => undef,\n+    lazy_build => 1,\n+);\n+\n+sub _build__vol_surface_validator {\n+    return Quant::Framework::VolSurface::Validator->new;\n+}\n+\n+has _ON_day => (\n+    is         => 'ro',\n+    isa        => 'Int',\n+    lazy_build => 1,\n+);\n+\n+=head2\n+\n+Returns the day for over-night tenor of this surface \n+ON = over-night\n+\n+=cut\n+\n+sub _build__ON_day {\n+    my $self = shift;\n+\n+    return $self->calendar->calendar_days_to_trade_date_after($self->effective_date);\n+}\n+\n+around BUILDARGS => sub {\n+    my $orig  = shift;\n+    my $class = shift;\n+    my %args  = (ref $_[0] eq 'HASH') ? %{$_[0]} : @_;\n+    my %day_for_tenor;\n+\n+    die \"Chronicle reader is required to create a vol-surface\" if not defined $args{chronicle_reader};\n+    die \"Attribute underlying_config is required\" if not defined $args{underlying_config};\n+\n+    my $underlying_config = $args{underlying_config};\n+    if (ref $underlying_config\n+        and $underlying_config->isa('Quant::Framework::Utils::UnderlyingConfig'))\n+    {\n+        $args{symbol} = $underlying_config->system_symbol;\n+    }\n+\n+    if ($args{surface} or $args{recorded_date}) {\n+\n+        if (not $args{surface} or not $args{recorded_date}) {\n+            die('Must pass both \"surface\" and \"recorded_date\" if passing either.');\n+        }\n+\n+        $args{_new_surface} = 1;\n+        my $builder = Quant::Framework::Utils::Builder->new({\n+            for_date           => $args{for_date},\n+            chronicle_reader   => $args{chronicle_reader},\n+            chronicle_writer   => $args{chronicle_writer},\n+            underlying_config => $args{underlying_config},\n+          });\n+\n+        my $expiry_conventions = $builder->build_expiry_conventions;\n+\n+        # If the smile's day is given as a tenor, we convert\n+        # it to a day and add the tenor to the smile:\n+        foreach my $maturity (keys %{$args{surface}}) {\n+            my $effective_date;\n+\n+            if (_is_tenor($maturity)) {\n+                $effective_date ||= Quant::Framework::VolSurface::Utils->new->effective_date_for($args{recorded_date});\n+\n+                my $vol_expiry_date = $expiry_conventions->vol_expiry_date({\n+                    from => $effective_date,\n+                    term => $maturity\n+                });\n+                my $day = $vol_expiry_date->days_between($effective_date);\n+\n+                $args{surface}->{$day} = $args{surface}->{$maturity};\n+                $args{surface}->{$day}->{tenor} = $maturity;\n+                delete $args{surface}->{$maturity};\n+\n+                $day_for_tenor{$maturity} = $day;\n+            }\n+        }\n+    }\n+\n+    if (ref $args{original_term}) {\n+        if (\n+            not exists $args{original_term}->{smile}\n+            and (\n+                not(   exists $args{original_term}->{vol_spread}\n+                    or exists $args{original_term}->{atm_spread})))\n+        {\n+\n+            die('Given original_term attr must have both smile and vol_spread keys present.');\n+        }\n+        foreach my $which (qw( smile vol_spread )) {\n+            if (exists $args{original_term}->{$which}) {\n+                $args{original_term}->{$which} = [\n+                    sort { $a <=> $b }\n+                    map { _is_tenor($_) ? $day_for_tenor{$_} : $_ } @{$args{original_term}->{$which}}];\n+            }\n+        }\n+    }\n+    return $class->$orig(%args);\n+};\n+\n+sub _is_tenor {\n+    my $day = shift;\n+\n+    return ($day =~ /^(?:ON|\\d{1,2}[WMY])$/) ? 1 : 0;\n+}\n+\n+=head2 get_spread\n+\n+Spread is ask-bid difference which can be stored for different tenor and atm/max.\n+\n+USAGE:\n+\n+    my $spread = $volsurface->get_spread({sought_point=> 'atm', day=> 7});\n+    will return the atm spread for the day.\n+\n+   my $spread = $volsurface->get_spread({sought_point=> 'max', day=> 7});\n+    will return the max spread for the day.\n+\n+\n+=cut\n+\n+sub get_spread {\n+    my ($self, $args) = @_;\n+\n+    my $sought_point = $args->{sought_point};\n+    my $day          = $args->{day};\n+    my $spread;\n+    $day = $self->get_day_for_tenor($day)\n+        if ($day =~ /^(?:ON|\\d[WMY])$/);    # if day looks like tenor\n+\n+    if ($sought_point eq 'atm') {\n+\n+        $spread = $self->_get_atm_spread($day);\n+    } elsif ($sought_point eq 'max') {\n+\n+        $spread = $self->_get_max_spread_from_surface({\n+            day => $day,\n+        });\n+\n+    }\n+\n+    return $spread;\n+\n+}\n+\n+sub _get_max_spread_from_surface {\n+    my ($self, $args) = @_;\n+\n+    my $day              = $args->{day};\n+    my $atm_spread_point = $self->atm_spread_point;\n+    my $spread;\n+    my $surface = $self->surface;\n+    if (exists $surface->{$day} and exists $surface->{$day}->{atm_spread}) {\n+        $spread = $surface->{$day}->{atm_spread};\n+    } else {\n+\n+        my $smile = $self->get_smile_spread($day);\n+        my @spread;\n+        if ($atm_spread_point eq 'atm_spread') {\n+            @spread = values %{$smile->{atm_spread}};\n+        } else {\n+            foreach my $point (keys %{$smile->{vol_spread}}) {\n+                push @spread, $smile->{vol_spread}->{$point};\n+            }\n+        }\n+\n+        $spread = max(@spread);\n+\n+    }\n+\n+    return $spread;\n+}\n+\n+=head2 get_smile_spread\n+\n+Returns the ask/bid spread for smile of this volatility surface\n+\n+=cut\n+\n+sub get_smile_spread {\n+\n+    my ($self, $day) = @_;\n+\n+    my $surface       = $self->surface;\n+    my @market_points = @{$self->original_term_for_spread};\n+\n+    my $smile_spread;\n+\n+    if ($self->_market_name eq 'indices' and $self->type eq 'moneyness') {\n+\n+        foreach my $day (keys %{$surface}) {\n+\n+            #check and add min_vol_spread for shorter term vol_spreads\n+            if ($day < 30 and grep { $_ == $day } @market_points) {\n+\n+                if (exists $surface->{$day}->{atm_spread}\n+                    and $surface->{$day}->{atm_spread} < $self->min_vol_spread)\n+                {\n+\n+                    $surface->{$day}->{atm_spread} += $self->min_vol_spread;\n+\n+                } elsif (exists $surface->{$day}->{vol_spread}) {\n+                    my $vol_spread = $surface->{$day}->{vol_spread};\n+\n+                    foreach my $point (keys %{$vol_spread}) {\n+                        if ($vol_spread->{$point} < $self->min_vol_spread) {\n+                            $vol_spread->{$point} += $self->min_vol_spread;\n+                        }\n+                    }\n+\n+                    $surface->{$day}->{vol_spread} = $vol_spread;\n+\n+                }\n+\n+            }\n+\n+        }\n+\n+    }\n+\n+    if (exists $surface->{$day}) {\n+\n+        if (exists $surface->{$day}->{atm_spread}) {\n+            $smile_spread->{'atm_spread'} = $surface->{$day}->{atm_spread};\n+\n+        } elsif (exists $surface->{$day}->{vol_spread}) {\n+\n+            $smile_spread->{'vol_spread'} = $surface->{$day}->{vol_spread};\n+        }\n+\n+    } else {\n+\n+        $smile_spread = $self->_compute_and_set_smile_spread($day);\n+    }\n+\n+    return $smile_spread;\n+\n+}\n+\n+sub _compute_and_set_smile_spread {\n+    my ($self, $day) = @_;\n+\n+    my @points = $self->_get_points_to_interpolate($day, $self->original_term_for_spread);\n+    my $method =\n+        ($self->_is_between($day, \\@points))\n+        ? '_interpolate_smile_spread'\n+        : '_extrapolate_smile_spread';\n+    my $smile_spread = $self->$method($day, \\@points);\n+\n+    $self->set_smile_spread({\n+        days         => $day,\n+        smile_spread => $smile_spread\n+    });\n+\n+    return $smile_spread;\n+}\n+\n+=head2 set_smile_spread\n+\n+#TODO: complete this\n+sets smile spread for the given tenor\n+\n+=cut\n+\n+sub set_smile_spread {\n+    my ($self, $args) = @_;\n+\n+    my $day              = $args->{days};\n+    my $smile_spread     = $args->{smile_spread};\n+    my $atm_spread_point = $self->atm_spread_point;\n+    die(\"day[$day] must be a number.\") unless looks_like_number($day);\n+\n+    my $surface = $self->surface;\n+\n+    $surface->{$day} = $smile_spread;\n+\n+    return;\n+}\n+\n+sub _interpolate_smile_spread {\n+    my ($self, $seek, $points) = @_;\n+\n+    my $surface      = $self->surface;\n+    my $first_point  = $points->[0];\n+    my $second_point = $points->[1];\n+    my $interpolation_first_point;\n+    my $interpolation_second_point;\n+    my $interpolated_smile;\n+\n+    foreach my $spread_point (@{$self->spread_points}) {\n+\n+        if (exists $surface->{$first_point}->{atm_spread}) {\n+            $interpolation_first_point  = $surface->{$first_point}->{atm_spread};\n+            $interpolation_second_point = $surface->{$second_point}->{atm_spread};\n+        } else {\n+            $interpolation_first_point  = $surface->{$first_point}->{vol_spread}->{$spread_point};\n+            $interpolation_second_point = $surface->{$second_point}->{vol_spread}->{$spread_point};\n+        }\n+\n+        my $interp = Math::Function::Interpolator->new(\n+            points => {\n+                $first_point  => $interpolation_first_point,\n+                $second_point => $interpolation_second_point,\n+            });\n+\n+        if ($spread_point eq 'atm_spread') {\n+\n+            $interpolated_smile->{atm_spread} = $interp->linear($seek);\n+        } else {\n+            $interpolated_smile->{vol_spread}->{$spread_point} = $interp->linear($seek);\n+        }\n+\n+    }\n+\n+    return $interpolated_smile;\n+\n+}\n+\n+sub _extrapolate_smile_spread {\n+    my ($self, $seek, $points) = @_;\n+\n+    my $extrapolation_direction = $self->_get_extrapolate_direction($seek, $points);\n+    my $extrapolation_method = '_extrapolate_smile_spread_' . $extrapolation_direction;\n+\n+    return $self->$extrapolation_method($seek, $points);\n+}\n+\n+sub _extrapolate_smile_spread_up {\n+    my ($self, $seek, $points) = @_;\n+\n+    my @amp              = @{$self->original_term_for_smile};\n+    my $atm_spread_point = $self->atm_spread_point;\n+    my $day              = $amp[-1];\n+    my $extrapolation;\n+\n+    if ($atm_spread_point eq 'atm_spread') {\n+        $extrapolation->{'atm_spread'} = $self->surface->{$day}->{'atm_spread'};\n+    } else {\n+        $extrapolation->{'vol_spread'} = $self->surface->{$day}->{'vol_spread'};\n+    }\n+\n+    return $extrapolation;\n+\n+}\n+\n+sub _extrapolate_smile_spread_down {\n+    my ($self, $seek, $points) = @_;\n+\n+    my $surface      = $self->surface;\n+    my $first_point  = $points->[0];\n+    my $second_point = $points->[1];\n+    my $extrapolation_first_point;\n+    my $extrapolation_second_point;\n+    my $extrapolated_smile;\n+\n+    foreach my $spread_point (@{$self->spread_points}) {\n+\n+        if (exists $surface->{$first_point}->{atm_spread}) {\n+            $extrapolation_first_point  = $surface->{$first_point}->{atm_spread};\n+            $extrapolation_second_point = $surface->{$second_point}->{atm_spread};\n+        } else {\n+            $extrapolation_first_point  = $surface->{$first_point}->{vol_spread}->{$spread_point};\n+            $extrapolation_second_point = $surface->{$second_point}->{vol_spread}->{$spread_point};\n+        }\n+\n+        my $interp = Math::Function::Interpolator->new(\n+\n+            points => {\n+                $first_point  => $extrapolation_first_point,\n+                $second_point => $extrapolation_second_point,\n+            });\n+\n+        if ($spread_point eq 'atm_spread') {\n+\n+            $extrapolated_smile->{atm_spread} = $interp->linear($seek);\n+        } else {\n+            $extrapolated_smile->{vol_spread}->{$spread_point} = $interp->linear($seek);\n+        }\n+\n+    }\n+\n+    return $extrapolated_smile;\n+\n+}\n+\n+sub _get_atm_spread {\n+    my ($self, $day) = @_;\n+\n+    my $atm_spread;\n+    my $surface          = $self->surface;\n+    my $atm_spread_point = $self->atm_spread_point;\n+\n+    if (exists $surface->{$day} and exists $surface->{$day}->{atm_spread}) {\n+        $atm_spread = $surface->{$day}->{atm_spread};\n+    } else {\n+\n+        my $smile_spread = $self->get_smile_spread($day);\n+\n+        if (\n+            grep { $atm_spread_point eq $_ }\n+            keys %{$smile_spread->{'vol_spread'}})\n+        {\n+            $atm_spread = $smile_spread->{'vol_spread'}->{$atm_spread_point};\n+        } else {\n+\n+            my @points = $self->_get_points_to_interpolate($day, $self->original_term_for_spread);\n+\n+            my $method =\n+                ($self->_is_between($day, \\@points))\n+                ? '_interpolate_atm_spread'\n+                : '_extrapolate_atm_spread';\n+\n+            $smile_spread = $self->$method($day, \\@points);\n+\n+            if ($atm_spread_point eq 'atm_spread') {\n+                $atm_spread = $smile_spread->{$atm_spread_point};\n+            } else {\n+                $atm_spread = $smile_spread->{'vol_spread'}->{$atm_spread_point};\n+            }\n+\n+        }\n+\n+    }\n+\n+    return $atm_spread;\n+}\n+\n+sub _interpolate_atm_spread {\n+    my ($self, $seek, $points) = @_;\n+\n+    my $surface          = $self->surface;\n+    my $atm_spread_point = $self->atm_spread_point;\n+    my $first            = $points->[0];\n+    my $second           = $points->[1];\n+    my $interpolation_first_point;\n+    my $interpolation_second_point;\n+\n+    if (exists $surface->{$first}->{atm_spread}) {\n+        $interpolation_first_point  = $surface->{$first}->{atm_spread};\n+        $interpolation_second_point = $surface->{$second}->{atm_spread};\n+    } else {\n+        $interpolation_first_point  = $surface->{$first}->{vol_spread}->{$atm_spread_point};\n+        $interpolation_second_point = $surface->{$second}->{vol_spread}->{$atm_spread_point};\n+    }\n+\n+    my $interp = Math::Function::Interpolator->new(\n+\n+        points => {\n+            $first  => $interpolation_first_point,\n+            $second => $interpolation_second_point,\n+        });\n+\n+    my $interpolated_spread;\n+\n+    if ($atm_spread_point eq 'atm_spread') {\n+        $interpolated_spread->{$atm_spread_point} = $interp->linear($seek);\n+    } else {\n+        $interpolated_spread->{'vol_spread'}->{$atm_spread_point} = $interp->linear($seek);\n+    }\n+\n+    return $interpolated_spread;\n+}\n+\n+sub _extrapolate_atm_spread {\n+    my ($self, $seek, $points) = @_;\n+\n+    my $extrapolation_direction = $self->_get_extrapolate_direction($seek, $points);\n+    my $extrapolation_method = '_extrapolate_atm_spread_' . $extrapolation_direction;\n+\n+    return $self->$extrapolation_method($seek, $points);\n+}\n+\n+sub _extrapolate_atm_spread_up {\n+    my ($self, $seek, $points) = @_;\n+\n+    my @amp              = @{$self->original_term_for_smile};\n+    my $atm_spread_point = $self->atm_spread_point;\n+    my $day              = $amp[-1];\n+    my $extrapolation;\n+    if ($atm_spread_point eq 'atm_spread') {\n+        $extrapolation->{'atm_spread'} = $self->surface->{$day}->{atm_spread};\n+    } else {\n+        $extrapolation->{vol_spread}->{$atm_spread_point} = $self->surface->{$day}->{vol_spread}->{$atm_spread_point};\n+    }\n+\n+    return $extrapolation;\n+}\n+\n+sub _extrapolate_atm_spread_down {\n+    my ($self, $seek, $points) = @_;\n+\n+    my $surface          = $self->surface;\n+    my $atm_spread_point = $self->atm_spread_point;\n+    my $first_point      = $points->[0];\n+    my $second_point     = $points->[1];\n+    my $extrapolation_first_point;\n+    my $extrapolation_second_point;\n+    my $extrapolated_smile;\n+\n+    if (exists $surface->{$first_point}->{atm_spread}) {\n+        $extrapolation_first_point  = $surface->{$first_point}->{atm_spread};\n+        $extrapolation_second_point = $surface->{$second_point}->{atm_spread};\n+    } else {\n+        $extrapolation_first_point  = $surface->{$first_point}->{vol_spread}->{$atm_spread_point};\n+        $extrapolation_second_point = $surface->{$second_point}->{vol_spread}->{$atm_spread_point};\n+    }\n+\n+    my $interp = Math::Function::Interpolator->new(\n+        points => {\n+            $first_point  => $extrapolation_first_point,\n+            $second_point => $extrapolation_second_point,\n+        });\n+\n+    if ($atm_spread_point eq 'atm_spread') {\n+\n+        $extrapolated_smile->{atm_spread} = $interp->linear($seek);\n+    } else {\n+        $extrapolated_smile->{vol_spread}->{$atm_spread_point} = $interp->linear($seek);\n+    }\n+\n+    return $extrapolated_smile;\n+\n+}\n+\n+=head2 get_day_for_tenor\n+\n+USAGE:\n+\n+    my $day = $volsurface->get_day_for_tenor('1W');\n+\n+Get the corresponding day for a given tenor, if one exists.\n+\n+=cut\n+\n+sub get_day_for_tenor {\n+    my ($self, $tenor) = @_;\n+\n+    my $surface_data = $self->surface;\n+    my $day          = first {\n+        $surface_data->{$_}->{tenor} && $surface_data->{$_}->{tenor} eq $tenor;\n+    }\n+    @{$self->term_by_day};\n+\n+    # If tenor doesn't already exist on surface, get $days via the vol expiry date.\n+    if (not $day) {\n+        $day = $self->builder->build_expiry_conventions->vol_expiry_date({\n+                from => $self->effective_date,\n+                term => $tenor,\n+            })->days_between($self->effective_date);\n+    }\n+\n+    return $day;\n+}\n+\n+sub _get_points_to_interpolate {\n+    my ($self, $seek, $available_points) = @_;\n+    die('Need 2 or more term structures to interpolate.')\n+        if scalar @$available_points <= 1;\n+\n+    return @{find_closest_numbers_around($seek, $available_points, 2)};\n+}\n+\n+sub _is_between {\n+    my ($self, $seek, $points) = @_;\n+\n+    my @points = @$points;\n+\n+    die('some of the points are not defined')\n+        if (notall { defined $_ } @points);\n+    die('less than two available points')\n+        if (scalar @points < 2);\n+\n+    return if (all { $_ < $seek } @points);\n+    return if (all { $_ > $seek } @points);\n+    return 1;\n+}\n+\n+sub _market_maturities_interpolation_function {\n+    my ($self, $T, $T1, $T2) = @_;\n+\n+    # Implements interpolation over time based on the way Iain M Clark does\n+    # it in Foreign Exchange Option Pricing, A Practitioner's Guide, page 70.\n+    my $effective_date = $self->effective_date;\n+\n+    my %dates = (\n+        T  => $effective_date->plus_time_interval($T . 'd'),\n+        T1 => $effective_date->plus_time_interval($T1 . 'd'),\n+        T2 => $effective_date->plus_time_interval($T2 . 'd'),\n+    );\n+\n+    my $tau1       = $self->builder->weighted_days_in_period($dates{T1}, $dates{T}) / 365;\n+    my $tau2       = $self->builder->weighted_days_in_period($dates{T1}, $dates{T2}) / 365;\n+\n+    warn(     'Error in volsurface['\n+            . $self->recorded_date->datetime\n+            . '] for symbol['\n+            . $self->underlying_config->symbol\n+            . '] for maturity['\n+            . $T\n+            . '] points ['\n+            . $T1 . ','\n+            . $T2 . \"]\\n\")\n+        unless $tau2;\n+\n+    return sub {\n+        my ($vol1, $vol2) = @_;\n+        return sqrt(($tau1 / $tau2) * ($T2 / $T) * $vol2**2 + (($tau2 - $tau1) / $tau2) * ($T1 / $T) * $vol1**2);\n+    };\n+}\n+\n+=head1 METHODS\n+\n+=head2 get_volatility\n+\n+USAGE:\n+\n+    my $vol = $vol_surface->get_volatility({\n+                   days  => 7,\n+                   delta => 50,\n+               });\n+\n+Interpolates (on the two-dimensional vol surface) using Bloomberg's method.\n+\n+=cut\n+\n+sub get_volatility {\n+    my ($self, $args) = @_;\n+\n+    my $sought_point = $args->{sought_point};\n+    my $days         = $args->{days};\n+\n+    $self->_validate_sought_values($days, $sought_point);\n+\n+    if ((grep { $self->_market_name eq $_ } (qw(sectors)))\n+        and not grep { $days eq $_ } @{$self->original_term_for_smile})\n+    {\n+\n+        # We have constant vol on these markets, so return the\n+        # ATM vol of the first market point on the surface.\n+        return $self->get_volatility({\n+                delta => 50,\n+                days  => $self->original_term_for_smile->[0]});\n+    }\n+\n+    my $vol = $self->_get_volatility_from_surface({\n+        days         => $days,\n+        sought_point => $sought_point,\n+    });\n+\n+    return $vol;\n+}\n+\n+sub _convert_expiry_to_day {\n+    my ($self, $args) = @_;\n+\n+    return $args->{days} if $args->{days};\n+    my $days =\n+          $args->{expiry_date}\n+        ? $self->_get_days_for_expiry_date($args->{expiry_date})\n+        : $self->get_day_for_tenor($args->{tenor});\n+    delete $args->{expiry_date};\n+    delete $args->{tenor};\n+    return $days;\n+}\n+\n+sub _validate_sought_values {\n+    my ($self, $day, $sought_point) = @_;\n+\n+    if (notall { defined $_ } ($sought_point, $day)) {\n+        $sought_point ||= '';\n+        $day          ||= '';\n+        die(\"Days[$day] or sought_point[$sought_point] is undefined for underlying[\" . $self->symbol . \"]\");\n+    }\n+\n+    if (!isnum($sought_point)) {\n+        die(\"Point[$sought_point] must be a number\");\n+    }\n+\n+    if ($day <= 0 or $sought_point < 0) {\n+        die(\"get_volatility requires positive numeric days[$day] and sought_point[$sought_point]\");\n+    }\n+\n+    return 1;\n+}\n+\n+sub _get_volatility_from_surface {\n+    my ($self, $args) = @_;\n+\n+    my $day          = $args->{days};\n+    my $sought_point = $args->{sought_point};\n+    my $vol;\n+\n+    my $smile = $self->get_smile($day);\n+    if ($smile->{$sought_point}) {\n+        $vol = $smile->{$sought_point};\n+    } else {\n+        $vol = $self->interpolate({\n+            smile        => $smile,\n+            sought_point => $sought_point,\n+        });\n+    }\n+\n+    return $vol;\n+}\n+\n+=head2 get_smile\n+\n+Get the smile for specific day.\n+\n+Usage:\n+\n+    my $smile = $vol_surface->get_smile($days);\n+\n+=cut\n+\n+sub get_smile {\n+    my ($self, $day) = @_;\n+\n+    die(\"day[$day] must be a number.\")\n+        unless looks_like_number($day);\n+\n+    my $surface = $self->surface;\n+    my $smile =\n+        (exists $surface->{$day} and exists $surface->{$day}->{smile})\n+        ? $surface->{$day}->{smile}\n+        : $self->_compute_and_set_smile($day);\n+\n+    return $smile;\n+}\n+\n+sub _compute_and_set_smile {\n+    my ($self, $day) = @_;\n+\n+    my @points = $self->_get_points_to_interpolate($day, $self->original_term_for_smile);\n+    my $method =\n+        ($self->_is_between($day, \\@points))\n+        ? '_interpolate_smile'\n+        : '_extrapolate_smile';\n+    my $smile = $self->$method($day, \\@points);\n+\n+    $self->set_smile({\n+        days  => $day,\n+        smile => $smile\n+    });\n+\n+    return $smile;\n+}\n+\n+sub _days_with_smiles {\n+    my $self = shift;\n+    my @days_with_smiles =\n+        grep { exists $self->surface->{$_}->{smile} } @{$self->term_by_day};\n+    return \\@days_with_smiles;\n+}\n+\n+sub _interpolate_smile {\n+    my ($self, $seek, $points) = @_;\n+\n+    my $surface          = $self->surface;\n+    my $first_point      = $points->[0];\n+    my $second_point     = $points->[1];\n+    my $interpolate_func = $self->_market_maturities_interpolation_function($seek, $first_point, $second_point);\n+    my $interpolated_smile;\n+\n+    foreach my $smile_point (@{$self->smile_points}) {\n+        my $first_iv  = $surface->{$first_point}->{smile}->{$smile_point};\n+        my $second_iv = $surface->{$second_point}->{smile}->{$smile_point};\n+        $interpolated_smile->{smile}->{$smile_point} = $interpolate_func->($first_iv, $second_iv);\n+    }\n+\n+    return $interpolated_smile->{smile};\n+}\n+\n+sub _extrapolate_smile {\n+    my ($self, $seek, $points) = @_;\n+\n+    my $extrapolation_direction = $self->_get_extrapolate_direction($seek, $points);\n+    my $extrapolation_method = '_extrapolate_smile_' . $extrapolation_direction;\n+\n+    return $self->$extrapolation_method($seek);\n+}\n+\n+sub _get_initial_rr {\n+    my ($self, $market) = @_;\n+\n+    my %initial_rr;\n+    my $rr_adjustment;\n+    if ($self->_market_name ne 'indices') {\n+        $rr_adjustment = {\n+            rr_25 => 0.1,\n+            rr_10 => 0.1\n+        };\n+        $initial_rr{RR_25} = $rr_adjustment->{rr_25} * $market->{RR_25};\n+        $initial_rr{RR_10} = $rr_adjustment->{rr_10} * $market->{RR_10} if (exists $market->{RR_10});\n+    }\n+\n+    return \\%initial_rr;","path":"lib/Quant/Framework/VolSurface.pm","position":1237,"original_position":1237,"commit_id":"dd6d1444986bc57b559c0804870514a34c7b958c","original_commit_id":"dd6d1444986bc57b559c0804870514a34c7b958c","user":{"login":"basiliscos","id":3630048,"avatar_url":"https://avatars.githubusercontent.com/u/3630048?v=3","gravatar_id":"","url":"https://api.github.com/users/basiliscos","html_url":"https://github.com/basiliscos","followers_url":"https://api.github.com/users/basiliscos/followers","following_url":"https://api.github.com/users/basiliscos/following{/other_user}","gists_url":"https://api.github.com/users/basiliscos/gists{/gist_id}","starred_url":"https://api.github.com/users/basiliscos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/basiliscos/subscriptions","organizations_url":"https://api.github.com/users/basiliscos/orgs","repos_url":"https://api.github.com/users/basiliscos/repos","events_url":"https://api.github.com/users/basiliscos/events{/privacy}","received_events_url":"https://api.github.com/users/basiliscos/received_events","type":"User","site_admin":false},"body":"What is `rr`?","created_at":"2016-06-01T07:33:27Z","updated_at":"2016-06-01T07:33:27Z","html_url":"https://github.com/binary-com/perl-Quant-Framework/pull/19#discussion_r65314233","pull_request_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls/19","_links":{"self":{"href":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls/comments/65314233"},"html":{"href":"https://github.com/binary-com/perl-Quant-Framework/pull/19#discussion_r65314233"},"pull_request":{"href":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls/19"}}},"pull_request":{"url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls/19","id":69620332,"html_url":"https://github.com/binary-com/perl-Quant-Framework/pull/19","diff_url":"https://github.com/binary-com/perl-Quant-Framework/pull/19.diff","patch_url":"https://github.com/binary-com/perl-Quant-Framework/pull/19.patch","issue_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/issues/19","number":19,"state":"open","locked":false,"title":"modules initially moved from internal code","user":{"login":"mm-binary","id":9294049,"avatar_url":"https://avatars.githubusercontent.com/u/9294049?v=3","gravatar_id":"","url":"https://api.github.com/users/mm-binary","html_url":"https://github.com/mm-binary","followers_url":"https://api.github.com/users/mm-binary/followers","following_url":"https://api.github.com/users/mm-binary/following{/other_user}","gists_url":"https://api.github.com/users/mm-binary/gists{/gist_id}","starred_url":"https://api.github.com/users/mm-binary/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mm-binary/subscriptions","organizations_url":"https://api.github.com/users/mm-binary/orgs","repos_url":"https://api.github.com/users/mm-binary/repos","events_url":"https://api.github.com/users/mm-binary/events{/privacy}","received_events_url":"https://api.github.com/users/mm-binary/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-05-11T06:21:24Z","updated_at":"2016-06-01T07:33:27Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6114208e99be1277b10efe8ca809d175ffc123a5","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls/19/commits","review_comments_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls/19/comments","review_comment_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls/comments{/number}","comments_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/issues/19/comments","statuses_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/statuses/dd6d1444986bc57b559c0804870514a34c7b958c","head":{"label":"mm-binary:mm/vol_surface","ref":"mm/vol_surface","sha":"dd6d1444986bc57b559c0804870514a34c7b958c","user":{"login":"mm-binary","id":9294049,"avatar_url":"https://avatars.githubusercontent.com/u/9294049?v=3","gravatar_id":"","url":"https://api.github.com/users/mm-binary","html_url":"https://github.com/mm-binary","followers_url":"https://api.github.com/users/mm-binary/followers","following_url":"https://api.github.com/users/mm-binary/following{/other_user}","gists_url":"https://api.github.com/users/mm-binary/gists{/gist_id}","starred_url":"https://api.github.com/users/mm-binary/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mm-binary/subscriptions","organizations_url":"https://api.github.com/users/mm-binary/orgs","repos_url":"https://api.github.com/users/mm-binary/repos","events_url":"https://api.github.com/users/mm-binary/events{/privacy}","received_events_url":"https://api.github.com/users/mm-binary/received_events","type":"User","site_admin":false},"repo":{"id":50819638,"name":"perl-Quant-Framework","full_name":"mm-binary/perl-Quant-Framework","owner":{"login":"mm-binary","id":9294049,"avatar_url":"https://avatars.githubusercontent.com/u/9294049?v=3","gravatar_id":"","url":"https://api.github.com/users/mm-binary","html_url":"https://github.com/mm-binary","followers_url":"https://api.github.com/users/mm-binary/followers","following_url":"https://api.github.com/users/mm-binary/following{/other_user}","gists_url":"https://api.github.com/users/mm-binary/gists{/gist_id}","starred_url":"https://api.github.com/users/mm-binary/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mm-binary/subscriptions","organizations_url":"https://api.github.com/users/mm-binary/orgs","repos_url":"https://api.github.com/users/mm-binary/repos","events_url":"https://api.github.com/users/mm-binary/events{/privacy}","received_events_url":"https://api.github.com/users/mm-binary/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/mm-binary/perl-Quant-Framework","description":"A framework of objects upon which to build Financial Quantitative Analysis code","fork":true,"url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework","forks_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/forks","keys_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/teams","hooks_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/hooks","issue_events_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/issues/events{/number}","events_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/events","assignees_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/assignees{/user}","branches_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/branches{/branch}","tags_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/tags","blobs_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/git/refs{/sha}","trees_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/statuses/{sha}","languages_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/languages","stargazers_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/stargazers","contributors_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/contributors","subscribers_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/subscribers","subscription_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/subscription","commits_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/commits{/sha}","git_commits_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/git/commits{/sha}","comments_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/comments{/number}","issue_comment_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/issues/comments{/number}","contents_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/contents/{+path}","compare_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/merges","archive_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/downloads","issues_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/issues{/number}","pulls_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/pulls{/number}","milestones_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/milestones{/number}","notifications_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/labels{/name}","releases_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/releases{/id}","deployments_url":"https://api.github.com/repos/mm-binary/perl-Quant-Framework/deployments","created_at":"2016-02-01T06:36:04Z","updated_at":"2016-02-26T08:47:59Z","pushed_at":"2016-06-01T05:21:21Z","git_url":"git://github.com/mm-binary/perl-Quant-Framework.git","ssh_url":"git@github.com:mm-binary/perl-Quant-Framework.git","clone_url":"https://github.com/mm-binary/perl-Quant-Framework.git","svn_url":"https://github.com/mm-binary/perl-Quant-Framework","homepage":null,"size":336,"stargazers_count":0,"watchers_count":0,"language":"Perl","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"binary-com:master","ref":"master","sha":"bc9253029936fa2b5a7c85ac18b6eaca3aa82fde","user":{"login":"binary-com","id":7437559,"avatar_url":"https://avatars.githubusercontent.com/u/7437559?v=3","gravatar_id":"","url":"https://api.github.com/users/binary-com","html_url":"https://github.com/binary-com","followers_url":"https://api.github.com/users/binary-com/followers","following_url":"https://api.github.com/users/binary-com/following{/other_user}","gists_url":"https://api.github.com/users/binary-com/gists{/gist_id}","starred_url":"https://api.github.com/users/binary-com/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/binary-com/subscriptions","organizations_url":"https://api.github.com/users/binary-com/orgs","repos_url":"https://api.github.com/users/binary-com/repos","events_url":"https://api.github.com/users/binary-com/events{/privacy}","received_events_url":"https://api.github.com/users/binary-com/received_events","type":"Organization","site_admin":false},"repo":{"id":50404733,"name":"perl-Quant-Framework","full_name":"binary-com/perl-Quant-Framework","owner":{"login":"binary-com","id":7437559,"avatar_url":"https://avatars.githubusercontent.com/u/7437559?v=3","gravatar_id":"","url":"https://api.github.com/users/binary-com","html_url":"https://github.com/binary-com","followers_url":"https://api.github.com/users/binary-com/followers","following_url":"https://api.github.com/users/binary-com/following{/other_user}","gists_url":"https://api.github.com/users/binary-com/gists{/gist_id}","starred_url":"https://api.github.com/users/binary-com/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/binary-com/subscriptions","organizations_url":"https://api.github.com/users/binary-com/orgs","repos_url":"https://api.github.com/users/binary-com/repos","events_url":"https://api.github.com/users/binary-com/events{/privacy}","received_events_url":"https://api.github.com/users/binary-com/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/binary-com/perl-Quant-Framework","description":"A framework of objects upon which to build Financial Quantitative Analysis code","fork":false,"url":"https://api.github.com/repos/binary-com/perl-Quant-Framework","forks_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/forks","keys_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/keys{/key_id}","collaborators_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/teams","hooks_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/hooks","issue_events_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/issues/events{/number}","events_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/events","assignees_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/assignees{/user}","branches_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/branches{/branch}","tags_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/tags","blobs_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/git/refs{/sha}","trees_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/git/trees{/sha}","statuses_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/statuses/{sha}","languages_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/languages","stargazers_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/stargazers","contributors_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/contributors","subscribers_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/subscribers","subscription_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/subscription","commits_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/commits{/sha}","git_commits_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/git/commits{/sha}","comments_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/comments{/number}","issue_comment_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/issues/comments{/number}","contents_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/contents/{+path}","compare_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/compare/{base}...{head}","merges_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/merges","archive_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/downloads","issues_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/issues{/number}","pulls_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls{/number}","milestones_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/milestones{/number}","notifications_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/labels{/name}","releases_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/releases{/id}","deployments_url":"https://api.github.com/repos/binary-com/perl-Quant-Framework/deployments","created_at":"2016-01-26T05:05:38Z","updated_at":"2016-03-24T01:59:42Z","pushed_at":"2016-06-01T05:21:22Z","git_url":"git://github.com/binary-com/perl-Quant-Framework.git","ssh_url":"git@github.com:binary-com/perl-Quant-Framework.git","clone_url":"https://github.com/binary-com/perl-Quant-Framework.git","svn_url":"https://github.com/binary-com/perl-Quant-Framework","homepage":null,"size":374,"stargazers_count":2,"watchers_count":2,"language":"Perl","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":7,"mirror_url":null,"open_issues_count":3,"forks":7,"open_issues":3,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls/19"},"html":{"href":"https://github.com/binary-com/perl-Quant-Framework/pull/19"},"issue":{"href":"https://api.github.com/repos/binary-com/perl-Quant-Framework/issues/19"},"comments":{"href":"https://api.github.com/repos/binary-com/perl-Quant-Framework/issues/19/comments"},"review_comments":{"href":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls/19/comments"},"review_comment":{"href":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/binary-com/perl-Quant-Framework/pulls/19/commits"},"statuses":{"href":"https://api.github.com/repos/binary-com/perl-Quant-Framework/statuses/dd6d1444986bc57b559c0804870514a34c7b958c"}}}}
{"actor":{"display_login":"basiliscos"}}