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
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/>.
18 # Namespace for reapers that do the actual fetching. Provides protected helpers
19 # for processing data.
23 # List of symbols of reapers that can handle the given :security_type.
24 def get_suitable(security_type)
25 Smr::Reapers.constants.collect{ |reapername|
26 klass = ('Smr::Reapers::%s' % reapername).constantize
27 reapername if klass.type_supported? security_type
30 module_function :get_suitable
35 # Translates special characters in :string.
36 def clean_special_chars(string)
42 matcher = /#{replacement_rules.keys.join('|')}/
43 string.gsub(matcher, replacement_rules).strip
47 # load reapers, each will be part of Smr::Reapers namespace
50 # Dir('reapers/*.rb').each{do |f| require f}
52 # works in development but does nothing in production.
54 require 'finanzen_net'
59 require 'universal_investment'
62 # Fetch things from the internet. Things such as metadata of a Security or a
63 # Quote. Therefore this module is to be mixed into the Security model. Its
64 # methods operate on the attributes of a Security.
67 # Number of fails until :preferred_reaper is reset
68 PREFERRED_REAPER_FAILS = 5
71 # tell whether given Smr::Reapers::* object is the preferred one.
72 def is_preferred_reaper? reaper
73 raise 'need Smr::Reapers::* object' unless reaper.is_a? Smr::Reapers
74 (reaper.class.to_s.split('::').last == self.preferred_reaper )
78 # Uses Smr::Reapers to fetch metadata for :symbol and set object attributes
81 # This will also create a new Security.type model and fills with the
82 # things discovered. In case the type model exists, its attributes will be
85 # Returns true on success or false on failure.
87 # Reapers are called until one succeeds
89 raise ':symbol must be set to fetch anything' if self.symbol.blank?
91 Smr::Reapers.constants.each do |reaperreapername|
92 reaperclass = ('Smr::Reapers::%s' % reaperreapername).constantize
93 next unless reaperclass.type_supported? self.type
94 reaper = reaperclass.new(self)
95 next unless reaper.respond_to? :metadata
97 logger.debug '== lookup metadata => %s with %s' % [ self.symbol, reaper.class ]
100 # - cut :brief string at first number because the numerical part
101 # is created form the metadata by the :typemodel
102 self.brief = self.brief.split(/\d/).first
110 # Users Smr::Reapers to obtain a recent quote for :symbol.
114 # - 1 failure from the Reaper
115 # - 2 Quote was found but is older or of same time is the last known one
116 # - 3 no Reaper could handle that Security
118 # Reapers are called until one succeeds. The reaper succeeding will be
119 # remembered and is tried first at the next invokation of this method.
120 # This way each Security will adjust itself to a source thats most
121 # reliable, ie where there is active trading.
124 raise ':symbol must be set to update a quote' if self.symbol.blank?
128 reapers << self.preferred_reaper.to_sym unless self.preferred_reaper.blank?
129 reapers << Smr::Reapers.get_suitable(self.type) unless self.preferred_reaper_locked
130 reapers = reapers.flatten.uniq
132 logger.debug '== lookup quote => %s using reapers %s' % [ self.symbol, reapers.inspect ]
133 reapers.each do |reapername|
134 logger.debug ' trying %s' % reapername
135 begin reaper = ('Smr::Reapers::%s' % reapername).constantize.new(self)
136 next unless reaper.respond_to?(:quote)
138 lq = self.last_quote(Time.now, :false_if_not_found=>true, :no_caching=>true)
139 if lq and lq.date + self.preferred_reaper_delay >= Time.now.to_i
140 logger.debug ' ... delay not passed by, skipping.'
146 logger.fatal 'reaper crashed: %s on security.id=%i' % [ reaper.class, self.id ]
147 reset_reaper_config if is_preferred_reaper? reaper
152 if not lq or q.time > lq.time
153 logger.debug' ... quote found by %s' % reaper.class
156 logger.fatal 'Quote.safe! failed on security.id=%i reaper=%s msg=%s' % [self.id, reaper.class, $! ]
160 reset_reaper_config reapername if self.preferred_reaper.blank?
170 if is_preferred_reaper? reaper
171 logger.debug' ... failcount = %i' % read_attribute(:preferred_reaper_fail_count)
172 write_attribute(:preferred_reaper_fail_count, self.preferred_reaper_fail_count + 1)
174 reset_reaper_config if read_attribute(:preferred_reaper_fail_count) >= PREFERRED_REAPER_FAILS
179 logger.debug' ... preferred is %s' % self.preferred_reaper
180 logger.debug' retval = %s' % retval
189 # scale the :preferred_reaper_delay :up or :down, starting with one hour
190 def scale_delay direction=:up
191 val = self.preferred_reaper_delay == 0 ? 1800 : self.preferred_reaper_delay
193 if direction == :down
196 logger.debug ' ... delay scaled %s to %i seconds' % [direction, val]
197 write_attribute :preferred_reaper_delay, val