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::Model - Apply tests to stock quotes

SYNOPSIS

Most usage requires at least one Sample with some function lines calculated on it's data. The functions and data are compared to produce a test line and possibly trigger signals.

    use Finance::Shares::Model;
    use Finance::Shares::Sample;
    use Finance::Shares::Bands;
    use Finance::Shares::Chart;

    my $fsm = new Finance::Shares::Model;

    my $fss = new Finance::Shares::Sample(...);
    $fsm->add_sample( $fss );

    $fsm->add_signal('mark_buy', undef, {
        graph => 'volumes',
        line  => 'volume',
        key   => 'above envelope',
        style => {
            point => {
                color => [1, 0, 0],
                shape => 'circle',
                size  => 15,
            },
        },
    });

    my ($high, $low) = $fss->envelope(
        graph => 'prices', line => 'close',
        percent => 3,
    );

    $fsm->test(
        graph1 => 'prices', line1 => $high,
        graph2 => 'prices', line2 => 'high',
        test   => 'ge',
        graph  => 'signals',
        signal => [ 'mark_buy' ],
    );

    my $fsc = new Finance::Shares::Chart(
        sample => $fss,
    );
    $fsc->output($filename);
       

This pseudo-example draws a circle around the volume where a day's highest price is more than 3% above the previous closing price.

DESCRIPTION

This module provides the testing enviroment for the Finance::Shares suite. The Model brings a group of Finance::Shares::Samples together and applies tests to them all. The tests usually rely on functions from other modules such as Finance::Shares::Averages, and the results are usually seen using Finance::Shares::Chart.

Unusually, the Finance::Shares::Model constructor does nothing. However, nothing will happen until add_sample has been called at least once. The tests are applied to all samples, which don't need to have anything in common with each other. However, if the date ranges are completely different it would probably be better to run three seperate models. This is because the Model's date range covered by each of the tests is made from all dates in all samples.

The tests currently available are:

    gt      1st line moves above 2nd
    lt      moves below
    ge      moves above or touches
    le      moves below or touches
    eq      touches
    ne      doesn't touch  

The tests produce data lines in the standard format. This means that data, functions and tests can be used interchangeably. They can all be graphed (or hidden). Wherever a 'line' is expected, it can be a data, function or test line. I think a circular reference is not possible because of the declaration order, but it would be a Very Bad Thing (TM) so be aware of the possibility.

These results lines are analog in that they can take a range of values. Indeed they can be made to fade over time. But they represent a state or level at any particular time. Signals, on the other hand, are a form of output that is inherently digital - either it has been invoked or it hasn't. All tests can have zero or more signals associated with them which are invoked when some critical change of state happens, like when one line crosses over another. Currently the following signals are available:

mark_buy

Places a mark on a graph.

mark_sell

Places a mark on a graph.

Write signal results to a file.

print

Print a message on the console.

custom

Invoke a user defined callback.

CONSTRUCTOR

new

There are no options. All settings are given using add_sample, add_signal, test and output.

MAIN METHODS

add_sample( sample )

This adds stock quote data to the model. sample must be a Finance::Shares::Sample object. The results of the tests are written back to the sample which would typically be displayed on a Finance::Shares::Chart.

Multiple samples can be added. These might be for the same stock sampled over days, weeks and months, or for different stocks and different dates. Bear in mind that all tests are conducted on all dates, so it makes sense to keep the date ranges as similar as possible.

add_signal( signal, [ object [, args ]] )

Register a callback function which will be invoked when some test evaluates 'true'.

signal

This must be a known signal name, see "SIGNALS".

object

Use 'undef' here for those signals that need to use the Finance::Shares::Sample, such as 'mark_buy'. When registering print, this is the message to print and when registering a custom function, this is the function reference.

args

Any arguments that will be passed to the signal function.

See the individual signal handling methods for the arguments that signal requires.

test( options )

A test is added to the model and the resulting line added to each Sample. Signals are invoked when a date is encountered that passes the test. Tests may be binary (working on two lines) or unary (just working on one).

The method returns the identifier string for the resulting data line.

options are passed as key => value pairs, with the following as known keys.

graph1

The graph holding line1. Must be one of 'prices', 'volumes', 'cycles' or 'signals'.

line1

A string identifying the only line for a unary test or the first line for a binary test.

graph2

The graph holding line2. Must be one of 'prices', 'volumes', 'cycles' or 'signals'. Defaults to graph1.

line2

A string identifying the second line for a binary test. For a unary test this must be undefined.

test

The name of the test to be applied, e.g 'gt' or 'lt'. Note this is a string and not a function reference.

shown

True if the results should be shown on a graph.

style

If present, this should be either a PostScript::Graph::File object or a hash ref holding options for creating one.

graph

The destination graph, where line will be displayed. Must be one of 'prices', 'volumes', 'cycles' or 'signals'.

If not specified, graph1 is used. This is a little odd as the scales are usually meaningless. However, as mostly the result is an on-or-off function, the line is suitably scaled so the shape is clear enough.

line

An optional string identifying the results data in case you wish to refer to them in another test. Provided for completeness, but it is much better to use the internal one returned by this method.

key

The string which will appear in the Key panel identifying the test results.

weight

How important the test should appear. Most tests implement this as the height of the results line.

decay

If the condition is met over a continuous period, the results line can be made to decay. This factor is multiplied by the previous line value, so 0.95 would produce a slow decay while 0 signals only the first date in the period.

ramp

An alternative method for conditioning the signal line. This amount is added to the signal value with each period.

signal

This should be one or more of the signals registered with this model. It can either be a single name or an array ref holding a list of names.

The results line would typically be shown on the 'signals' graph. Most tests are either true or false, so the line is flat by default. The line can be conditioned, however, so its value changes over time. Here are some examples of how the relevant parameters interact.

Example 1

    decay   => 0.5,
    ramp    => 0,

An exponential decay, halving with each period.

    decay   => 0.95,
    ramp    => 0,

A much shallower decaying curve.

    weight => 100,
    decay  => 1,
    ramp   => -20,

A straight line decline which disappears after five days.

    weight => 100,
    decay  => 1.983,
    ramp   => -99,

An inverted curve with the first 5 days scoring more than 85 then dropping rapidly to 0 after 7 days.

output( [psfile,] [filename [, directory]] )

psfile can either be a PostScript::File object, a hash ref suitable for constructing one or undefined.

The charts are constructed and written out to the PostScript file. A suitable suffix (.ps, .epsi or .epsf) will be appended to filename.

If no filename is given, the PostScript text is returned. This makes handling CGI requests easier.

Examples

    my $file = $fsm->output();

The PostScript is returned as a string. The PostScript::File object has been constructed using defaults which produce a landscape A4 page.

    $fsm->output('myfile');

The default A4 landscape page(s) is/are saved as myfile.ps.

    my $pf = new PostScript::File(...);
    my $file = $fsm->output($pf);

PostScript is returned for printing using CGI.pm for example. The pages are formatted according to the PostScript::File parameters. The same result would have been obtained had $pf been a hash ref.

    my $pf = new PostScript::File(...);
    $fsm->output($pf, 'shares/myfile', $dir);

The specially tailored page(s) is/are written to $dir/shares/myfile.ps.

Note that it is not possible to print the charts individually once this has been called. However, it is possible to output them seperately to their own files, then call this to output a file showing them all.

SIGNALS

Before they can be used signals must have been registered with the Model using add_signal. The name must then be given to test as (part of) the signal value.

Most parameters are given when it is registered, but the date of the signal is also passed to the handler.

mark_buy

A 'buy' point is drawn on a graph when the test evaluates 'true'. The following parameters may be passed to add_signal within a hash ref.

Example

    $fsm->add_signal('mark_buy', undef, {
        graph => 'prices', 
        value => 440,
    });
    
graph

One of prices, volumes, cycles or signals.

value

If present, this should be a suitable Y coordinate. No bounds checking is done.

line

If no value is given, the value may be obtained from the line identified by this string.

key

Optional string appearing in the Key panel.

style

An optional hash ref containing options for a PostScript::Graph::Style, or a PostScript::Graph::Style object. It should only have a point group defined (line and bar make no sense). (Default: blue arrow).

shown

Optional flag, true if the mark is to be shown (Default: 1)

mark_sell

Draws a 'sell point'. See mark_buy.

This is the heavy duty print signal. See "print" for a lighter weight one.

It prints a string to a file or to STDOUT when the test evaluates 'true'. The following parameters may be passed to add_signal within a hash ref.

Example 1

    $fsm->add_signal('print_value', undef, {
            message => 'Volume is $value at $date', 
            graph => 'volumes', line => 'volume',
        });
message

This is the string that is output. It may include $date and $value, which will be replaced with the date and value for that signal. Note that this should be given in single quotes or with the '$' signs escaped. $date and $value look like variables but are actually just placeholders.

graph

One of prices, volumes, cycles or signals.

value

If present, this should be a suitable Y coordinate. No bounds checking is done.

line

If no value is given, the value may be obtained from the line identified by this string.

file

If given, this should be an already open file handle. It defaults to \*STDOUT.

Example 2

    my $fsm = new Finance::Shares::Model;
    
    my $sfile;
    open $sfile, '>>', 'signals.txt';
    
    $fsm->add_signal('print_value', undef, {
        message => '$date',
        file    => $sfile,
    });

    $fsm->test(
        graph1 => 'prices', line1 => 'close',
        graph1 => 'prices', line2 => 'open',
        test   => 'gt',
        signal => 'print_value',
    );

    close $sfile;

Here a list of dates are written to the file 'signals.txt' instead.

print

This is the lightweight print signal. See "print_value" for a fuller featured one.

It prints a string to STDOUT when the test evaluates 'true'.

Register the signal like this:

    my $fsm = new Finance::Shares::Model;
    
    $fsm->add_signal('print', 'Some message');

or even

    $fsm->add_signal('print');

Note that this is slighty different from all the others - there is no undef (the object placeholder).

custom

Use this to register your own callbacks. When your function is called, date will always be the first parameter, followed by any args given here. The format is as follows:

    $fsm->add_signal( 'custom', <coderef>, @args );
        

Example

    my $fss = new Finance::Shares::Sample(...);
    my $fsm = new Finance::Shares::Model;
    
    my $level = $fss->value(
        graph => 'volumes', value => 250000
    );

    sub some_func {
        my ($date, @args) = @_;
        ...
    }
    
    $fsm->add_signal( 'custom', \&some_func, 
        3, 'blind', $mice );

    $fsm->test(
        graph1 => 'volumes', line1 => 'volume',
        graph1 => 'volumes', line2 => $level,
        test   => 'gt',
        signal => 'custom',
    );

Here &some_func will be be called with four parameters whenever the volume moves above 250000.

SUPPORT METHODS

signal( signal [, object [, param ]] )

All callbacks of the type indicated by signal will be invoked.

signal

This can be either a single signal name, or an array ref containing signal names. Allowed names include:

    mark_buy
    mark_sell
    print
    custom
object

An object may be given when the signal was registered with add_signal. But if it was not, this will be the first parameter passed instead.

param

The second parameter passed to the callback function. Any number of arguments may be passed here as an array ref.

Any other registered parameters are passed after param.

BUGS

Please report those you find to the author.

AUTHOR

Chris Willmot, chris@willmot.org.uk

SEE ALSO

Finance::Shares::Sample, Finance::Shares::Chart.

There is also an introduction, Finance::Shares::Overview and a tutorial beginning with Finance::Shares::Lesson1.