The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Finance::Shares::Function - Base class for function objects

SYNOPSIS

    use Finance::Shares::Function;

Data access

    $name   = $fn->id();
    $name   = $fn->name();
    $fsc    = $fn->chart();
    @names  = $fn->line_ids();
    $fsl    = $fn->func_line($name);
    @fsls   = $fn->func_lines();
    @fsls   = $fn->source_lines();
    $string = $fn->show_lines();

These functions should be implimented by any inheriting class.

    $fn->initialize();
    $fn->build();
    $n = $fn->lead_time();
    $v = $fn->value();
    

DESCRIPTION

All data, lines or points that appear on a Finance::Shares::Chart are Finance::Shares::Lines generated by a class named something like Finance::Shares::<function>. They are all inherit from this class.

Most Finance::Shares::Function classes will only generate one Finance::Shares::Line but some have several. All lines belonging to a function are built at the same time - so all are available once one line has been requested.

The Functions are created by Finance::Shares::Model which supervises their use. The only methods documented here are those of use to writers of function modules.

How to Write a Function Module

Apart from a constructor (which must be called new), the methods initialize, build must be implemented and optionally lead_time and/or value. This is best illustrated with an example.

Initialization

The code presented here is for a module Finance::Shares::my_module. It is found in a file called my_module.pm and begins quite normally.

    package Finance::Shares::my_module;
    use strict;
    use warnings;
    use Log::Agent;
    use Finance::Shares::Support qw(out);
    use Finance::Shares::Function;
    our @ISA = 'Finance::Shares::Function';

Perhaps the only thing that needs comment is Log::Agent. The whole suite uses this to output feedback and debugging messages.

The Constructor

Almost every constructor is the same.

    sub new {
        my $class = shift;
        my $o = new Finance::Shares::Function(@_);
        bless $o, $class;

        out($o, 4, "new $class");
        return $o;
    }

All functions inherit from Finance::Shares::Function. I use $o instead of $self as it is less to type, is rarely used and stands for $object. What you use is up to you.

The out() line sends a debug message to Log::Agent provided the verbose level is greater or equal to 4. See fsmodel or Finance::Shares::Model for details.

initialize()

The module will produce a line on an 'analysis' graph and requires two source lines to work on.

    sub initialize {
        my $o = shift;

        $o->common_defaults('analysis', 'close', 'open');

        $o->add_line('result', 
            gtype  => $o->{gtype},
            graph  => $o->{graph},

            key    => $o->{key} || '',
            style  => $o->{style},
            shown  => $o->{shown},
            order  => $o->{order},
        );
    }

The model engine, Finance::Shares::Model, creates the my_module object and passes the user parameters to it (the fields given in the relevant lines entry of the model spec). The parameters all become fields in the object hash, and some are passed on the the result line, as shown.

The initialize method provides the opportunity to validate or tidy up the user options, set any defaults required and prepare any lines that other functions might make use of. Using common_defaults ensures that the function works like others, so the user knows what to expect.

add_line creates the Finance::Shares::Line that will hold the function's results. Most of the valid options are passed to it. Note that key is given an empty string as the default value. This is used by build, when information from the source lines is available.

build()

This is the business method, where all the calculations are done. Being larger, we will consider it in sections.

    sub build {
        my $o = shift;
        my $q = $o->{quotes};
        my $d = $q->dates;

Some definitions needed later. Notice that the Finance::Shares::data object for the line's chart page is available through $o->{quotes}. Iterating through its dates is the best way to generate most lines.

        my $first = $o->{line}[0][0];
        my $second = $o->{line}[1][0];
        my $d1 = $first->{data};
        my $d2 = $second->{data};
        

Notice that {line} is a nested array.

The lines entries in the model spec actually define functions like 'greater_than' (rather than graph lines - confusing, I know). It is quite legal for a single function to produce several graph lines, such as an upper and lower bound on a range. The lines generated by each function are returned as an array ref so that the sequence of lines generated matches those requested.

For example, say the user specified the following, with 'this' and 'that' being dependent lines.

    lines => [
        myline => {
            function => 'myfunction',
            lines => ['this', 'that'],
        },
    ],

If 'this' produced a single line and 'that' produced three, they would later be accessed by:

    $o->{line}[0][0]    'this'

    $o->{line}[1][0]    'that'
    $o->{line}[1][1]
    $o->{line}[1][2]

Anyway, back to the build method. $first and $second are Finance::Shares::Line objects that have already been processed, so their {data} fields ($d1 and $d2) hold arrays of values, one per date.

    my (@points, $level);
    for (my $i = 0; $i <= $#$d; $i++) {
        my $result;
        my $value1 = $d1->[$i];
        my $value2 = $d2->[$i];
        
        if (defined $value1 and defined $value2) {
            #
            # calculate $level;
            #
            $result = $level;
        }
        
        push @points, $result;
    }

Inside the loop, details of the 'greater_than' calculations have been omitted to make the common points clearer.

Notice that whatever happens, a value is pushed onto @points for every date. It might be a calulated value, or undef. It is often useful to seperately keep track of the last valid value ($level here).

When the loop has finished, the result line can be updated with the new data and build returns.

      my $l = $o->func_line('result');
      $l->{data} = \@points;

      $l->{key} = "'$first->{key}' > '$second->{key}'"
        unless $l->{key};
    }

Compound Functions

The more interesting functions are made from combining other lines. To be useful these lines must be generated within the main function, rather than relying on the user to specify them.

The Model engine has two main phases. During the 'create' phase, the samples are analysed and all the objects that will be required are created. [In particular Finance::Shares::Function and Finance::Shares::Line objects from explicitly named lines and those they depend on.] The 'build' phase then visits these, filling out the data by calculating each Function as required.

During 'create' the Functions are called top-down: each one is inspected and any dependent lines are then created. 'build' works the other way round. When each function is built, it can rely on all the dependent lines being complete.

The initialize method is called during 'create' and build during 'build', naturally enough. This means new line specifications can be declared in initialize, fooling the Model engine into thinking they were there all the time. But there is a slight problem with this. Because the Model didn't ask for these additional functions, it isn't aware they need to be built either. So the build method must make sure any additional lines are built before it does any calculations on them.

The example followed here comes uses the 'gradient' function in its calculations. See Finance::Shares::rising as an example.

initialize()

Once all the parameters' defaults have been sorted out, a new entry is sneakily added into the lines model specification.

First, the entry will need a tag. It is assumed that {line} has just one source line and that is converted to a gradient. So the name of the source line is recorded and the new tag name becomes the dependent line.

        my $tag       = unique_name( 'gradient' );
        my $source    = $o->{line}[0];
        $o->{line}[0] = $tag;

        my ($shown, $style) = shown_style( $o->{gradient} );

A user field ('gradient') is passed to show_style which interprets options for displaying the underlying line:

'0'

The line is hidden.

'1'

The line is visible with the default style.

hash ref

The line uses a style created from this specification.

PostScript::Graph::Style object

The line is shown with the style given.

unique_name and show_style are support functions which adds an initial underscore and a trailing unique number to the given stem. It is declared in the Support module, so this 'use' line will be required at the start.

    use Finance::Shares::Support qw(out
                                    unique_name
                                    show_style);

$h holds the model spec lines options, plus some required fields (function and uname).

        my $h = {
            function => 'gradient',
            uname    => $tag,

            line     => [ $source ],
            shown    => $shown,
            style    => $style,
            strict   => $o->{strict},
            period   => $o->{period},
        };
        

With $h complete, the specification can be added to the model's options.

        my $data = $o->{quotes};
        my $fsm  = $data->model;
        $fsm->add_option('lines', $uname, $h);

Now the source lines have been sorted out, the rest of initialize can be completed. This usually just means calling add_line to create the Finance::Shares::Line object(s) that will hold the results of your Function.

lead_time()

The model engine now has a lines options entry filled out and the functions line field showing the new line as a dependent. It will therefore handle the inserted line normally. This means that lead_time should only reflect my_module's own calculations - the gradient lead time will be handled by its own function in the normal way.

build()

Nothing special needs to be done in the 'build' phase as the dependent lines should have been built in the right order.

One little quirk is worth mentioning. It is common for the dependent line's key to be quoted in the key for the results line. But of course the source line the user gave is now the 'grandchild' of this function. The problem is overcome by accessing the source line(s) of the inserted function.

In this case, the inserted function is gradient and is in position 0; gradient only produces one line, so

    my $grad_line = $o->{line}[0][0];
    
    my $grad_fn   = $grad_line->function();
    my @src_lines = $grad_fn->source_lines();
    

Only one source function is expected and that only has one line, so again

    my $src_key   = $src_lines[0][0]{key};
   

Value Functions

Some functions exist to support the tests and so don't need to be displayed at all. Implimenting these is similar to a normal Function, except that it must also have a value method and should probably support the no_line option.

The examples here are from Finance::Shares::standard_deviation. The initialize method includes:

    $o->common_defaults;
    $o->{no_line} = 0 unless defined $o->{no_line};
    if ($o->{no_line}) {
        $o->{shown} = 0;
        $o->{key}   = '';
    }

Note that the line must be hidden (not shown) if it is to be used only to calculate a value. The value method may take arguments, as this example shows.

    sub value {
        my ($o, $field) = @_;
        $field = 'std_dev' unless defined $field;
        if ($field eq 'mean') {
            return $o->{mean};
        } else {
            return $o->{std_dev};
        }
    }

Where $o-{mean}> and $o-{std_dev}> were calculated as part of the build method, which the following is taken from.

    if ($o->{no_line}) {
        my $l = $o->func_line('sd0');
        $l->{data} = [];
    } else {
        # fill the data and key(s) as normal
    }

In use, a special function value() accesses the data.

    lines => [
        ...
        sd_range => {
            function => 'standard_deviation',
        },
    ],
    tests => [
        range => {
            before => q(
                print "Std dev = ", value( $sd_price, 'std_dev'), "\n";
                print "Mean    = ", value( $sd_price, 'mean'), "\n";
            ),
        },
    ],
    

METHODS

initialize( )

Override this to initialize the object. For example, use this to ensure needed values have suitable defaults.

Due to the way functions are created by the model, no parameters of consequence are given to the constructor. They are passed using an internal call to add_parameters, which finishes by calling init, which by default does nothing.

common_defaults( [gtype [, line(s)]] )

This method provides some default settings which might be useful within initialize.

gtype becomes a default graph type. (Default: 'price').

line(s) is a list of zero or more line names. Either simple line tags as given in the model specification, or in the form <function>/<line>. These are used as a default for the line option (Default: 'data/close').

shown is also set depending on whether a style value was declared.

It also sets a new field {quotes} which points to the chart's price and volume data.

level_defaults( [decay [, ramp]] )

Ensure suitable defaults for functions intended for the 'logic' graph type.

lead_time( )

Override this if the function requires any periods of working prior to the first requested date. This default method returns 0.

value( )

This should be overridden if the function is to return some value to a test code fragment. The default method just returns 'undef'.

add_line( id, @options )

Add a line to the function under the identifer id. It should be followed by a list of options in hash key => value format. This constructs a Finance::Shares::Line object accessible as:

    $fn->func_line( $id )

ACCESS METHODS

id( )

Return the user tag identifying the definitions in the model lines specification.

name( )

Return the canonical name for this function.

chart( )

Return the Finance::Shares::Chart object displaying this function.

model( )

Return the Finance::Shares::Model engine.

line_ids( )

Return a list of the keys used to identify the Lines created by the function. Override this if the function generates more than one line. This default method returns ''.

Note that this lists the result lines and is nothing to do with the line option. line lists the source lines this function depends on.

func_line( id )

Return the Finance::Shares::Line identified by the tag id. If id is omitted, the first id returned by line_ids is assumed. Note that the leaf name is expected, rather than the full line name.

func_line_list( regexp )

regexp should match the last (fnline) portion of the full line name. If omitted, or '*', all lines are matched. An array ref is returned holding the list of found line objects.

func_lines( )

Returns a list of all the Finance::Shares::Line objects generated by this function.

source_lines( )

Return a list of the Finance::Shares::Line objects used as source lines. The list retains the ordering as given to the line entry. As each of those functions may return multiple lines, there is a double nesting.

Example

    lines => [
        tag1 => {
            ...
        },
        ...
        example1 => {
            function => 'multiline_mean',
            lines    => [qw(tag1 tag2 tag3)],
        },
    ],

This will produce a function:

    my $mmfn = new Finance::Shares::multiline_mean(...);
    my @src  = $mmfn->source_lines();

@src will have structure something like this:

    @src = (
        [ $tag1_line1 ],  
        [ $tag2_line1, $tag2_line2, $tag2_line3 ],  
        [ $tag3_line1 ], 
    );

sources( )

Return a flat list of all source lines. If every source function only produces one line, the order will still be correct.

SUPPORT METHODS

show_lines( )

Return a string holding the fully qualified line names of all the source lines. See also "show" in Finance::Shares::Support for another very useful debugging function.

BUGS

Please do let me know when you suspect something isn't right. A short script working from a CSV file demonstrating the problem would be very helpful.

AUTHOR

Chris Willmot, chris@willmot.org.uk

LICENCE

Copyright (c) 2003 Christopher P Willmot

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. A copy can be found at http://www.gnu.org/copyleft/gpl.html

SEE ALSO

Finance::Shares::Overview provides an introduction to the suite, and fsmodel is the principal script.

Modules involved in processing the model include Finance::Shares::Model, Finance::Shares::MySQL, Finance::Shares::Chart. Chart and file details may be found in PostScript::File, PostScript::Graph::Paper, PostScript::Graph::Key, PostScript::Graph::Style.

Functions are invoked from their own modules, all with lower-case names such as Finance::Shares::moving_average. The nitty-gritty on how to write each line specification are found there.

The quote data is stored in a Finance::Shares::data object. For information on writing additional line functions see Finance::Shares::Function and Finance::Shares::Line. Also, Finance::Shares::Code covers writing your own tests.