How to create an ordered list in Perl from a set of inequality comparisons?

437 views Asked by At

I'm playing around with historical stock market analysis in Perl. One aspect deals with analyzing the accuracy of research firms past stock ratings. The most rudimentary rating scale would be Buy, Hold, Sell. However many of these firms use different terminology, some with more than 3 points on their scale.

What I have is a list of thousands of upgrade/downgrades issued by hundreds of different firms (from Yahoo Finance) that looks something like this:

Action      From      To
==================================================
Upgrade     Add           Buy
Downgrade   Add           Hold
Upgrade     Hold          Add
Downgrade   Buy           Outperform
Upgrade     Hold          Outperform
Downgrade   Hold          Reduce
Upgrade     Add           Outperform

So basically it's a list of comparisons like A > B, D < C, B > C, D < A

What I need for each research firm, taking in a long list of these comparisons, is an ordered list that looks like this:

A > B > C > D > E

I've given this problem a lot of thought and can't come up with a solution. If each upgrade/downgrade only jumped one increment, I think I could do it but I can't wrap my head around how to insert a comparison like C < A, where it jumps two increments.

Anybody have any ideas?




Update:

Thanks @ikegami. I tested with the original data and you are correct.

I also ran some data through Graph::Easy, which renders graphs.

Code:

use Graph::Easy;
my $graph = Graph::Easy->new( );

# Note that these are all in 'Upgrade' direction
$graph->add_edge ('Hold', 'Add');
$graph->add_edge ('Hold', 'Buy');
$graph->add_edge ('Hold', 'Outperform');
$graph->add_edge ('Buy', 'Outperform');
$graph->add_edge ('Reduce', 'Hold');
$graph->add_edge ('Add', 'Buy');

print $graph->as_ascii( );

Output:

               +------------------------+
               |                        v
+--------+     +------+     +-----+     +-----+     +------------+
| Reduce | --> | Hold | --> | Add | --> | Buy | --> | Outperform |
+--------+     +------+     +-----+     +-----+     +------------+
               |                                    ^
               +------------------------------------+

Here's a graph with some clear ambiguities. Perhaps I can somehow use both modules (or some feature of Graph) to test for ambiguities.

+------------------------------+
|                              v
+--------+     +---------+     +-----+
| Reduce | --> | Neutral | --> | Buy |
+--------+     +---------+     +-----+
                 ^               ^
                 |               |
                 |               |
               +---------+       |
               |  Sell   | ------+
               +---------+
1

There are 1 answers

2
Sean On BEST ANSWER

I don't use graphs all that often, but this code (using the Graph module) seems to do the job:

use Graph;
use strict;

my $graph = Graph->new;

while (<DATA>) {
    my ($dir, $x, $y) = split;
    if ($dir eq 'Downgrade') {
        ($x, $y) = ($y, $x);
    } elsif ($dir ne 'Upgrade') {
        die qq(Unknown direction "$dir"\n);
    }
    $graph->add_edge($x, $y);
}

$graph->is_dag
    or die "Graph has a cycle--unable to analyze\n";
$graph->is_weakly_connected
    or die "Graph is not weakly connected--unable to analyze\n";

print join(' < ', $graph->topological_sort), "\n";

__DATA__
Upgrade     Add           Buy
Downgrade   Add           Hold
Upgrade     Hold          Add
Downgrade   Buy           Outperform
Upgrade     Hold          Outperform
Downgrade   Hold          Reduce
Upgrade     Add           Outperform

This prints Reduce < Hold < Add < Outperform < Buy.