package Finance::StockAccount::Transaction; our $VERSION = '0.01'; use Time::Moment; use Finance::StockAccount::Stock; use strict; use warnings; use constant BUY => 0; use constant SELL => 1; use constant SHORT => 2; use constant COVER => 3; my $lineFormatPattern = "%-35s %-6s %-6s %8s %7.2f %10.2f %5.2f %10.2f\n"; my $headerPattern = "%-35s %-6s %-6s %8s %7s %10s %5s %10s\n"; my @headerNames = qw(Date Symbol Action Quantity Price Commission Fees CashEffect); sub new { my ($class, $init) = @_; my $self = { tm => undef, action => undef, stock => undef, quantity => undef, price => undef, commission => 0, regulatoryFees => 0, otherFees => 0, }; bless($self, $class); $init and $self->set($init); return $self; } sub order { return qw(date action stock quantity price commission regulatoryFees otherFees); } sub tm { # Time::Moment object getter/setter my ($self, $tm) = @_; if ($tm) { if (ref($tm) and ref($tm) eq 'Time::Moment') { $self->{tm} = $tm; return 1; } else { warn "$tm not a valid Time::Moment object.\n"; return 0; } } else { return $self->{tm}; } } sub dateString { my ($self, $dateString) = @_; if ($dateString) { my $tm = Time::Moment->from_string($dateString); if ($tm) { $self->{tm} = $tm; return 1; } else { warn "Unable to create Time::Moment object from date string $dateString.\n"; return 0; } } else { my $tm = $self->{tm}; if ($tm) { return $tm->to_string(); } else { warn "Time::Moment property not set.\n"; return undef; } } } sub action { my ($self, $action) = @_; if ($action) { if ($action eq 'buy') { return $self->buy(1); } elsif ($action eq 'sell') { return $self->sell(1); } elsif ($action eq 'short') { return $self->short(1); } elsif ($action eq 'cover') { return $self->cover(1); } else { die "Action must a string, one of 'buy', 'sell', 'short', 'cover'.\n"; } } else { return $self->{action}; } } sub stock { my ($self, $stock) = @_; if ($stock) { if (ref($stock) and 'Finance::StockAccount::Stock' eq ref($stock)) { $self->{stock} = $stock; return 1; } else { warn "$stock is not a recognized Finance::StockAccount::Stock object.\n"; return 0; } } else { $stock = $self->{stock}; if (!$stock) { $stock = Finance::StockAccount::Stock->new(); $self->{stock} = $stock; } return $stock; } } sub sameStock { my ($self, $testStock) = @_; my $stock = $self->{stock}; if ($stock) { return $stock->same($testStock); } else { warn "Can't test for sameStock, object stock property not yet defined.\n"; return 0; } } sub symbol { my ($self, $symbol) = @_; my $stock = $self->stock(); return $stock->symbol($symbol); } sub exchange { my ($self, $exchange) = @_; my $stock = $self->stock(); return $stock->exchange($exchange); } sub quantity { my ($self, $quantity) = @_; if ($quantity) { $self->{quantity} = $quantity; return 1; } else { return $self->{quantity}; } } sub price { my ($self, $price) = @_; if ($price) { $self->{price} = $price; return 1; } else { return $self->{price}; } } sub commission { my ($self, $commission) = @_; if ($commission) { $self->{commission} = $commission; return 1; } else { return $self->{commission}; } } sub regulatoryFees { my ($self, $regulatoryFees) = @_; if ($regulatoryFees) { $self->{regulatoryFees} = $regulatoryFees; return 1; } else { return $self->{regulatoryFees}; } } sub otherFees { my ($self, $otherFees) = @_; if ($otherFees) { $self->{otherFees} = $otherFees; return 1; } else { return $self->{otherFees}; } } sub priceByQuantity { my $self = shift; return $self->{price} * $self->{quantity}; } sub feesAndCommissions { my $self = shift; return $self->{commission} + $self->{regulatoryFees} + $self->{otherFees}; } sub cashEffect { my $self = shift; my $cashEffect; if ($self->buy() or $self->short()) { $cashEffect = 0 - ($self->priceByQuantity() + $self->feesAndCommissions()); } elsif ($self->sell() or $self->cover()) { $cashEffect = $self->priceByQuantity() - $self->feesAndCommissions(); } if ($cashEffect) { return $cashEffect; } else { warn "Cannot calculate cash effect.\n"; return 0; } } sub set { my ($self, $init) = @_; my $status = 1; foreach my $key (keys %{$init}) { if (exists($self->{$key})) { if ($key eq 'action') { $self->action($init->{$key}); } elsif ($key eq 'tm') { $self->tm($init->{$key}); } else { $self->{$key} = $init->{$key}; } } elsif ($key eq 'symbol') { $self->symbol($init->{$key}); } elsif ($key eq 'exchange') { $self->exchange($init->{$key}); } elsif ($key eq 'dateString') { $self->dateString($init->{$key}); } else { $status = 0; warn "Tried to set $key in StockAccount::Transaction object, but that's not a known key.\n"; } } return $status; } sub get { my ($self, $key) = @_; if ($key and exists($self->{$key})) { return $self->{$key}; } else { warn "Tried to get key from StockAccount::Transaction object, but that's not a known key.\n"; return 0; } } sub validateAction { my $self = shift; if (!defined($self->{action})) { die "Action has not yet been set."; return 0; } else { return 1; } } sub buy { my ($self, $assertion) = @_; if ($assertion) { $self->{action} = BUY; return 1; } else { $self->validateAction(); return $self->{action} == BUY; } } sub sell { my ($self, $assertion) = @_; if ($assertion) { $self->{action} = SELL; return 1; } else { $self->validateAction(); return $self->{action} == SELL; } } sub short { my ($self, $assertion) = @_; if ($assertion) { $self->{action} = SHORT; return 1; } else { $self->validateAction(); return $self->{action} == SHORT; } } sub cover { my ($self, $assertion) = @_; if ($assertion) { $self->{action} = COVER; return 1; } else { $self->validateAction(); return $self->{action} == COVER; } } sub actionString { my $self = shift; if ($self->buy()) { return 'buy'; } elsif ($self->sell()) { return 'sell'; } elsif ($self->short()) { return 'short'; } elsif ($self->cover()) { return 'cover'; } else { return ''; } } sub string { my $self = shift; my $pattern = "%14s %-35s\n"; my $string; foreach my $key ($self->order()) { if (defined($self->{$key})) { if ($key eq 'stock') { my $symbol = $self->symbol(); my $exchange = $self->exchange(); if (defined($symbol)) { $string .= sprintf($pattern, 'symbol', $self->symbol()); } if (defined($exchange)) { $string .= sprintf($pattern, 'exchange', $self->exchange()); } } else { my $value; if ($key eq 'action') { $value = $self->actionString(); } else { $value = $self->{$key}; } $string .= sprintf($pattern, $key, $value); } } elsif ($key eq 'date') { if ($self->{tm}) { $string .= sprintf($pattern, $key, $self->dateString()); } } } return $string; } sub lineFormatHeader { return sprintf($headerPattern, @headerNames); } sub lineFormatPattern { return $lineFormatPattern; } sub lineFormatValues { my $self = shift; return [ $self->{tm} || '', $self->symbol(), $self->actionString(), $self->{quantity}, $self->{price} || 0, $self->{commission} || 0, ($self->{regulatoryFees} + $self->{otherFees}) || 0, $self->cashEffect() || 0 ]; } sub lineFormatString { my $self = shift; return sprintf($lineFormatPattern, @{$self->lineFormatValues()}); } 1; __END__ =pod =head1 NAME Finance::StockAccount::Transaction =head1 SYNOPSIS my $ftr = Finance::StockAccount::Stock->new({ symbol => 'FTR', exchange => 'NASDAQ', }); my $st = Finance::StockAccount::Transaction->new({ date => $dt, action => Finance::StockAccount::Transaction::SELL, stock => $ftr, quantity => 42, price => 7.11, commission => 8.95, regulatoryFees => 0.01, }); print $st->price(), "\n"; # prints number 7.11 =head1 PROPERTIES These are the public properties of a StockAccount::Transaction object: date # 'presumed' DateTime object. See http://datetime.perl.org/wiki/datetime/dashboard and discussion below. action # One of module constants BUY, SELL, SHORT, COVER stock # A Finance::StockAccount::Stock object quantity # How many shares were bought or sold, e.g. 100 or 5. price # Numeric representation of price, e.g. 4.65 instead of the string '$4.65'. commission # Numeric representation of commission in same currency, e.g. 8.95 instead of the string '$8.95'. regulatoryFees # Numeric representation of the regulatory fees, see section on "Regulatory Fees" below. otherFees # Numeric aggregation of any other fees not included in commission and regulatory fees. Any public property can be instantiated in the C method, set with a method matching the name of the property, such as C<$st->date($dt)>, or set with the C method, e.g. C<$st->set({date => $dt})>, as specified further in the method description below. Public properties can also be retrieved by the same method matching the name of the property when no parameter is passed, e.g. C<$st->date()>. Or by the C method with a string naming the property, e.g. C<$st->get('price')>. All properties can also be read or written directly with a hash dereference. Some people don't consider this good object-oriented practice, but I won't stop you if that's what you want to do. E.g. C<$st->{date} = $dt> or C<$st->{price}>. =head1 REGULATORY FEES In the United States the Securities and Exchange Commission imposes regulatory fees on stock brokers or dealers. Instead of paying these with their profits, these for-profit companies often pass these fees onto their customers directly. The C property could be used for similar purposes in other jurisdictions. See http://www.sec.gov/answers/sec31.htm for more information. =cut