portrait Security and Organization records
[smr.git] / gui / lib / smr / watchlist.rb
blobb9f2ce471828916f63d766fb60ebd8fe33f32ae0
2 # This file is part of SMR.
4 # SMR is free software: you can redistribute it and/or modify it under the
5 # terms of the GNU General Public License as published by the Free Software
6 # Foundation, either version 3 of the License, or (at your option) any later
7 # version.
9 # SMR is distributed in the hope that it will be useful, but WITHOUT ANY
10 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License along with
14 # SMR.  If not, see <http://www.gnu.org/licenses/>.
17 module Smr  #:nodoc:
18   ##
19   # Total assets of current user at some point in time.
20   #
21   # Use #open_positions to access Smr::AssetPosition objects of all positions
22   # held. Then each knows more details about itself.
23   class Watchlist
25       # Ransack Gem query object, ie for use in controllers or views.
26       attr_reader :query
28       ##
29       # Makes the watchlist with Quote information at :date. The given User
30       # should be authenticated!
31       #
32       # By default a set of 15 randomly selected securities is shown. To see
33       # more, provide a :ransack_query.
34       # This is for the sake of speed at the first look. There might be
35       # hundrets to throusands of Security records in the database. Its no good
36       # to select all of them at once.
37       #
38       # Option :ransack_query is the query produced by Ransack's #search_form
39       # helper method. It will be used to select Security records.
40       #
41       # Optional :exclude_securities may give a collection if Security.id
42       # numbers that should not be shown in the watchlist.
43       #
44       #
45       # Note: if the :ransack_query contains #sorts, only the first (!) one
46       # will actually be used and passed on to Smr::WatchlistItem as
47       # :compare_by.
48       def initialize(user, date,
49             options={
50                 :exclude_securities=>false,
51                 :provides_collateral=>false, :provides_cashflow=>false,
52                 :provides_huge_coupon=>false, :provides_subannual_payments=>false,
53                 :matures_within_year=>false,
54                 :ransack_query=>false
55             }
56           )
57           raise ':user must be of User' unless user.is_a?(User)
58           raise ':date must be of Time' unless date.is_a?(Time)
60           @user = user
61           @date = date
62           @items = Array.new
63           exclude_securities = [ Smr::ID_CASH_SECURITY ]
64           exclude_securities += options[:exclude_securities] if options[:exclude_securities]
65           @filters = options[:filters] if options[:filters]
68           if options[:ransack_query]
69             @query = Security.ransack(options[:ransack_query])
70           else
71             c = Security.count
72             @query = Security.where(:id=>Array.new(15).map{|e| rand(c)}).ransack
73           end
75           @query.result.includes(:Organization,:SecurityBond,:SecurityStock).to_a.uniq.each do |s|
76             # apply filters
77             next if exclude_securities.include?(s.id)
78             next if s.is_expired?(date)
79             next if options[:provides_collateral] and s.collateral_coverage_ratio == 0
80             next if options[:provides_cashflow] and (s.cashflow_this_year == false or s.cashflow_this_year == 0)
81             next if options[:provides_subannual_payments] and not s.has_subannual_payments?
82             if s.type == :bond
83                 next if options[:matures_within_year] and not s.get_type.time_maturity.between?(@date, @date + 1.year)
84                 next if options[:provides_huge_coupon] and s.get_type.coupon < 9
85             elsif options[:matures_within_year] or options[:provides_huge_coupon]
86                 next  # skip securities that can be filtered with these options
87             end
89             # add to list
90             @items << Smr::WatchlistItem.new(
91                 s, date,
92                 :compare_by=>(if not @query.sorts.empty? then @query.sorts.first.name.to_sym else :date end)
93             )
94           end
95           @items.sort!
96           @items.reverse! if not @query.sorts.empty? and @query.sorts.first.dir == 'desc'
97       end
98   
99    public
101       ##
102       # loops over WatchlistItem collection
103       def each(&block)
104           @items.each(&block)
105       end
107       def collect(&block)
108           @items.collect(&block)
109       end
111       def count
112           @items.count
113       end
114   end
116   ##
117   # Represents a single item of a watchlist, suitable for easy
118   # rendering in template.
119   class WatchlistItem
120       include Comparable
122       # unique identifier for this item, right now wrapped to Security#id
123       attr_reader :id
125       # Time of this item.
126       attr_reader :date
128       # Quote of last trade known before :date, may be "old", wrapped to Security#last_quote.
129       attr_reader :quote
131       # underlying Security
132       attr_reader :security
134       ##
135       # A WatchlistItem is basically a Security shown at a :date.
136       #
137       # Use the :compare_by option to tune how a collection of these items
138       # should be ordered. Default is #date, possible are all attributes of
139       # Security.
140       def initialize(security, date, options={:compare_by=>:date})
141           raise ':security must be of type Security' unless security.is_a?(Security)
142           raise ':date must be of type Time' unless date.is_a?(Time)
143   
144           @security = security; @date=date
145           @quote = @security.last_quote(@date)
146           @id = @security.id
147           @compare_by = options[:compare_by] || :date
148       end
150       ##
151       # Evaluates :compare_by option, see #new.
152       def <=>(other)
153         if @compare_by != :date
154             mine = @security.send(@compare_by)
155             the_other = other.security.send(@compare_by)
157             retval = if mine==false or the_other==false then  # probably from Security.method_missing
158                         if mine or the_other then 1
159                         else 0 end
160                      elsif mine > the_other then 1
161                      elsif mine < the_other then -1
162                      else 0 end
163         else
164             retval = if date > other.date then 1
165                      elsif date < other.date then -1
166                      else 0 end
167         end
168         retval
169       end
170   end
171 end # module