finexclub 0.4.3 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,6 +1,51 @@
1
1
  = finexclub
2
2
 
3
- Nobody is really interested in this gem. Finexclub gem helps us to run http://trendsonforex.com and http://finexclub.net
3
+ Nobody is really interested in this gem. Finexclub gem helps us to run http://trendsonforex.com and http://finexclub.net.
4
+
5
+ Signals are saved into MongoDB according to the scheme:
6
+
7
+ chart:
8
+ symbol: 'eurusd' # Currency pair symbol, eg. 'eurusd', 'gbpusd', etc.
9
+ timeframe: 'h1' # Timeframe 'h1', 'h4', 'd1'
10
+ starts: 1293447600 # Timestamp when this timeframe slot starts, eg. Dec, 27 11:00:00
11
+ expires: 1293451200 # Timestamp when this timeframe slot is expired, eg. Dec, 27 12:00:00
12
+ updated: 1293447840 # UTC timestamp when this timeframe was last updated, eg. Dec, 27 11:04:00
13
+ indicators: # Latest data from indicators
14
+
15
+ alpha: # Alpha: trendline analysis
16
+ updated: 1293447840
17
+ index: 89 # Trend stability index 0..100
18
+ direction: 'up' # Trend direction 'up', 'down'
19
+
20
+ octopus: # Octopus: wave based analysis
21
+ updated: 1293447840
22
+ action: 'buy' # Order 'buy', 'sell', 'hold_buy', 'hold_sell', 'stop'
23
+ take_profit: 1.3350
24
+ profit: 90 # Possible profit in pips
25
+ stop_loss: 1.3250
26
+ loss: 60 # Possible loss in pips
27
+ index: 89 # Wave quality index 0..100; 0 - no wave matches found
28
+ screenshot: '...' # Path to S3 image
29
+
30
+ prognosis: # Prognosis: chaos theory based analysis
31
+ updated: 1293447840
32
+ action: 'hold_buy'
33
+ take_profit: 1.3350
34
+ profit: 90
35
+ stop_loss: 1.3250
36
+ loss: 60
37
+ acceleration: 'low' # 'high', 'low', 'undefined'
38
+ screenshot: '...'
39
+
40
+ zeta: # Zeta: support & resistance channels
41
+ updated: 1293447840
42
+ up_support: 1.3350 # 0 if no up-channel found
43
+ up_resist: 1.3250
44
+ down_support: 1.3250 # 0 if no down-channel found
45
+ down_resist: 1.3250
46
+ screenshot: '...'
47
+
48
+ To save a signal it looks for a valid (not expired) chart record in MongoDB that corresponds to the given symbol and timeframe. If chart has expired, it creates a new one with prolongated expiration date to accumulate incoming signals.
4
49
 
5
50
  == Copyright
6
51
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.3
1
+ 0.5.1
data/finexclub.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{finexclub}
8
- s.version = "0.4.3"
8
+ s.version = "0.5.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Alex Levin"]
12
- s.date = %q{2010-12-20}
12
+ s.date = %q{2011-02-04}
13
13
  s.default_executable = %q{finexclub_updater}
14
14
  s.description = %q{Finexclub gem stores and retrieves Forex signals and screenshots. It is the heart of the http://trendsonforex.com and http://finexclub.net}
15
15
  s.email = %q{clubfinex@gmail.com}
data/lib/finexclub/app.rb CHANGED
@@ -19,10 +19,16 @@ module Finexclub
19
19
  end
20
20
  end
21
21
 
22
- post "/:signal" do
23
- signal_type = params[:signal].to_sym
24
- ap params[signal_type]
25
- Finexclub.store(signal_type, params[signal_type])
22
+ post "/:indicator" do
23
+ indicator = params[:indicator].to_sym
24
+ indicator_data = params[indicator]
25
+
26
+ indicator_data = params[indicator].is_a?(Array) ? params[indicator] : [ params[indicator] ]
27
+ indicator_data.each do |s|
28
+ ap s.merge(:updated => Time.now.utc)
29
+ Finexclub.signals.add_signal(indicator, s, Time.now.utc)
30
+ end
31
+
26
32
  status 200
27
33
  end
28
34
  end
@@ -4,35 +4,67 @@ module Finexclub
4
4
 
5
5
  include Document
6
6
 
7
- field :updated, :timestamp
8
7
  field :symbol, :symbol
9
8
  field :timeframe, :string
10
- field :date, :string
9
+ field :starts, :timestamp
10
+ field :expires, :timestamp
11
+ field :updated, :timestamp
11
12
 
12
13
  attr_reader :core
14
+ attr_accessor :indicators
15
+
16
+ doc_fields :symbol, :timeframe, :starts, :expires, :updated, :indicators
13
17
 
14
18
  def initialize(core)
15
19
  @core = core
16
- @signals = {}
20
+ @indicators = {}
17
21
  end
18
22
 
19
- [:alpha, :zeta, :octopus, :prognosis].each do |indicator|
20
- define_method("#{indicator}=") do |arr|
21
- @signals[indicator] = arr
22
- end
23
+ def handler_for(indicator)
24
+ self.class.handler_for(indicator)
25
+ end
26
+
27
+ def self.build_new(core, symbol, tf, time)
28
+ time = time.is_a?(Time) ? time : Time.at(time).utc
23
29
 
24
- define_method(indicator) do
25
- return nil unless signals(indicator)
26
- Signal.build(core, indicator, signals(indicator).last)
30
+ c = Chart.new(core)
31
+ c.updated= time.to_i
32
+ c.symbol= symbol
33
+ c.timeframe = tf
34
+ c.starts = Time.utc(time.year, time.month, time.day, time.hour, 0).to_i
35
+ c.expires = c.starts + 1*60*60
36
+ c
37
+ end
38
+
39
+ def self.indicator_handler(indicator, indicator_handler_class)
40
+ add_handler_for(indicator, indicator_handler_class)
41
+
42
+ #define_method("#{indicator}=") do |doc|
43
+ #@indicator_docs[indicator] = doc
44
+ #end
45
+
46
+ define_method("#{indicator}") do
47
+ return nil unless doc = indicators[indicator.to_s]
48
+
49
+ indicator_obj = handler_for(indicator.to_s).new(core)
50
+ indicator_obj.build(doc)
51
+ indicator_obj
27
52
  end
28
53
  end
29
54
 
30
- def signals(indicator)
31
- @signals[indicator]
55
+ def self.add_handler_for(indicator, klass)
56
+ @handlers ||= {}
57
+ @handlers[indicator.to_s] = klass
32
58
  end
33
-
34
- def to_doc
35
- {:symbol => symbol, :date => updated_date}
59
+
60
+ def self.handler_for(indicator)
61
+ @handlers ||= {}
62
+ @handlers[indicator.to_s]
36
63
  end
64
+
65
+ indicator_handler :alpha, Finexclub::Signals::Alpha
66
+ indicator_handler :zeta, Finexclub::Signals::Zeta
67
+ indicator_handler :octopus, Finexclub::Signals::Octopus
68
+ indicator_handler :prognosis, Finexclub::Signals::Prognosis
37
69
  end
38
70
  end
@@ -17,23 +17,24 @@ module Finexclub
17
17
  self
18
18
  end
19
19
 
20
- def store(signal_type, params)
21
- params = params.is_a?(Array) ? params : [params]
22
- params.each do |s|
23
- signals.add_signal(signal_type, s)
24
- end
25
- end
20
+ #def store(signal_type, params)
21
+ #params = params.is_a?(Array) ? params : [params]
22
+ #params.each do |s|
23
+ #signals.add_signal(signal_type, s)
24
+ #end
25
+ #end
26
+
27
+ #def find(symbol_or_array, date)
28
+ #case symbol_or_array
29
+ #when :all
30
+ #signals.find(:date => date)
31
+ #when Array
32
+ #signals.find(:date => date, :symbol => {:$in => symbol_or_array})
33
+ #when String
34
+ #signals.find_one(:date => date, :symbol => symbol_or_array)
35
+ #end
36
+ #end
26
37
 
27
- def find(symbol_or_array, date)
28
- case symbol_or_array
29
- when :all
30
- signals.find(:date => date)
31
- when Array
32
- signals.find(:date => date, :symbol => {:$in => symbol_or_array})
33
- when String
34
- signals.find_one(:date => date, :symbol => symbol_or_array)
35
- end
36
- end
37
38
  end
38
39
  end
39
40
 
@@ -7,6 +7,11 @@ module Finexclub
7
7
  extend ClassMethods
8
8
 
9
9
  def _id=(value)
10
+ @id = value
11
+ end
12
+
13
+ def _id
14
+ @id
10
15
  end
11
16
  end
12
17
  end
@@ -1,12 +1,13 @@
1
1
  module Finexclub
2
2
  module Fabrication
3
3
  def raw_alpha_hash(options = {})
4
- {"symbol" => "EURUSD", "index"=>"2", "direction"=>"1"}.merge(options)
4
+ {"symbol" => "EURUSD", "timeframe" => 'h1', "index"=>"2", "direction"=>"1"}.merge(options)
5
5
  end
6
6
 
7
7
  def raw_zeta_hash(options={})
8
8
  {
9
9
  "symbol" => "EURUSD",
10
+ "timeframe" => "h1",
10
11
  "up_support" => "1.124",
11
12
  "up_resist" => "1.123",
12
13
  "down_support" => "1.124",
@@ -12,34 +12,61 @@ module Finexclub
12
12
  end
13
13
 
14
14
  def collection
15
- raise("No db configured") if db.nil?
15
+ raise ConfigurationError.new("No db is configured") if db.nil?
16
16
  db.collection('charts')
17
17
  end
18
18
 
19
19
  def find(query, opts = {})
20
- Cursor.new(core, Chart, collection.find(query, opts))
20
+ Cursor.new(core, Finexclub::Chart, collection.find(query, opts))
21
21
  end
22
22
 
23
- def find_one(query, opts = {})
24
- return nil unless doc = collection.find_one(query, opts)
25
- chart = Chart.new(core)
26
- chart.build(doc)
27
- chart
23
+ def find_one(symbol, tf, time = nil)
24
+ time ||= Time.now.utc
25
+ time = time.respond_to?(:to_i) ? time.to_i : time
26
+ find({:symbol => symbol, :timeframe => tf, :starts => {:$lte => time}, :expires => {:$gt => time}}, :limit => 1, :sort => [[:expires, -1]]).first
28
27
  end
29
28
 
30
- def last_updated
31
- c = collection.find({}, :fields => {:updated => 1}, :sort => [['updated', :desc]], :limit => 1).first
32
- c.nil?? nil : Time.at(c["updated"])
29
+ def find_many(symbol_or_array, tf, time = nil)
30
+ time ||= Time.now.utc
31
+ time = time.respond_to?(:to_i) ? time.to_i : time
32
+ find({:symbol => {:$in => symbol_or_array}, :timeframe => tf, :starts => {:$lte => time}, :expires => {:$gt => time}}, :limit => 1, :sort => [[:expires, -1]])
33
33
  end
34
34
 
35
- def add_signal(signal_type, params)
36
- signal = Signal.build(core, signal_type, params.merge(:updated => Time.now))
37
- c = {
38
- :symbol => signal.symbol,
39
- :timeframe => signal.timeframe,
40
- :date => signal.updated_at.strftime("%Y-%m-%d")
41
- }
42
- collection.update(c, {"$push" => {signal_type => signal.to_doc}, "$set" => {:updated => signal.updated}}, :upsert => true)
35
+ def find_latest(symbol, tf)
36
+ find({:symbol => symbol, :timeframe => tf}, :sort => [[:expires, :desc]], :limit => 1).first
37
+ end
38
+
39
+ def find_one_or_create(symbol, tf, time)
40
+ chart = find_one(symbol, tf, time)
41
+ return chart if chart
42
+
43
+ id = collection.insert(Chart.build_new(core, symbol, tf, time).to_doc)
44
+ find({"_id" => id}).first
45
+ end
46
+
47
+ def last_updated(tf)
48
+ doc = collection.find({:timeframe => tf}, :fields => {:updated => 1}, :sort => [[:updated, :desc]], :limit => 1).first
49
+ doc.nil?? nil : doc["updated"]
50
+ end
51
+
52
+ def last_updated_at(tf)
53
+ ts = last_updated(tf)
54
+ ts.nil? ? nil : Time.at(ts).utc
55
+ end
56
+
57
+ def add_signal(indicator, params, time)
58
+ time = time.respond_to?(:to_i) ? time.to_i : time
59
+
60
+ signal = Signal.build(core, indicator, params.merge(:updated => time))
61
+ chart = find_one_or_create(signal.symbol, signal.timeframe, time)
62
+
63
+ c = if chart._id
64
+ {"_id" => chart._id}
65
+ else
66
+ chart.to_doc
67
+ end
68
+
69
+ collection.update(c, {"$set" => {:updated => signal.updated, "indicators.#{indicator}" => signal.to_doc}}, :upsert => true)
43
70
  end
44
71
 
45
72
  class Cursor
data/lib/finexclub.rb CHANGED
@@ -33,9 +33,11 @@ autoload_files_in_dir("#{File.dirname(__FILE__)}/finexclub", 'Finexclub')
33
33
  require 'forwardable'
34
34
 
35
35
  module Finexclub
36
+ class ConfigurationError < StandardError; end
37
+
36
38
  class << self
37
39
  extend Forwardable
38
- def_delegators :core, :store, :find, :configure
40
+ def_delegators :core, :configure
39
41
  def_delegators :core, :images, :signals
40
42
 
41
43
  def core
@@ -24,62 +24,130 @@ def make_request(url, opts = {})
24
24
  last_response
25
25
  end
26
26
 
27
- def date(ts = nil)
28
- ts ||= Time.now.utc
29
- ts.strftime("%Y-%m-%d")
30
- end
27
+ describe 'Finexclub::App' do
28
+ before do
29
+ charts.drop
30
+ end
31
31
 
32
- def alpha_params()
33
- params = {}
34
- params[:alpha] = Finexclub::Chart::SYMBOLS.inject([]) {|alpha, symbol| alpha << raw_alpha_hash("symbol" => symbol.upcase)}
35
- params
36
- end
32
+ describe 'Alpha' do
33
+ before do
34
+ @params = {}
35
+ @params[:alpha] = []
36
+
37
+ Finexclub::Chart::SYMBOLS.each do |symbol|
38
+ @params[:alpha] << {
39
+ "symbol" => symbol.upcase,
40
+ "timeframe" => 'h1',
41
+ "direction" => 1,
42
+ "index" => 100
43
+ }
44
+ end
45
+ @params
46
+ end
37
47
 
38
- def zeta_params(options = {})
39
- #options[:symbol] ||= "EURUSD"
40
- params = {}
41
- params[:zeta] = [raw_zeta_hash(options)]
42
- params
43
- end
48
+ it 'should save signal' do
49
+ response = nil
50
+ Timecop.travel(Time.utc(2010, 12, 27, 11, 5)) do
51
+ response = make_request('/alpha', @params)
52
+ end
53
+ response.status.should == 200
44
54
 
45
- describe 'Finexclub::App' do
46
- describe 'saving Alpha' do
55
+ charts.find(:timeframe => 'h1').count.should == 28
56
+ c = charts.find_one(:symbol => 'eurusd')
57
+ c["updated"].should == Time.utc(2010, 12, 27, 11, 5).to_i
58
+ c["indicators"]["alpha"].should.not.be.nil?
59
+ end
60
+ end
61
+
62
+ describe 'Zeta' do
47
63
  before do
48
- charts.remove
64
+ @params = {}
65
+ @params[:zeta] = [{
66
+ "symbol" => "EURUSD",
67
+ "timeframe" => "h1",
68
+ "up_support" => "1.124",
69
+ "up_resist" => "1.123",
70
+ "down_support" => "2.124",
71
+ "down_resist" => "2.123",
72
+ "screenshot_filename" => "egg.png"
73
+ }]
49
74
  end
50
75
 
51
- it 'should create first signal' do
52
- response = make_request('/alpha', alpha_params)
76
+ it 'should save signal' do
77
+ response = nil
78
+ Timecop.travel(Time.utc(2010, 12, 27, 11, 5)) do
79
+ response = make_request('/zeta', @params)
80
+ end
53
81
  response.status.should == 200
54
82
 
55
- charts.find().count.should == 28
56
- charts.find_one(:date => date, :symbol => 'eurusd')["alpha"].size.should == 1
83
+ charts.find(:timeframe => 'h1').count.should == 1
84
+ c = charts.find_one(:symbol => 'eurusd')
85
+ c["updated"].should == Time.utc(2010, 12, 27, 11, 5).to_i
86
+ c["indicators"]["zeta"].should.not.be.nil?
57
87
  end
58
-
59
- it 'should add more alpha signals' do
60
- Timecop.travel
61
- response = make_request('/alpha', alpha_params)
88
+ end
89
+
90
+ describe 'Octopus' do
91
+ before do
92
+ @params = {}
93
+ @params[:octopus] = [{
94
+ "symbol" => "EURUSD",
95
+ "timeframe" => "h1",
96
+ "action" => "buy",
97
+ "take_profit" => 1.123,
98
+ "profit" => 100,
99
+ "stop_loss" => 2.123,
100
+ "loss" => 50,
101
+ "index" => 100,
102
+ "tsi" => 90,
103
+ "trend" => 1,
104
+ "screenshot_filename" => "egg.png"
105
+ }]
106
+ end
107
+
108
+ it 'should save signal' do
109
+ response = nil
110
+ Timecop.travel(Time.utc(2010, 12, 27, 11, 5)) do
111
+ response = make_request('/octopus', @params)
112
+ end
62
113
  response.status.should == 200
63
114
 
64
- Timecop.travel(1*60*60) #travel 1 hour later
65
- response = make_request('/alpha', alpha_params)
66
- response.status.should == 200
67
- charts.find().count.should == 28
68
- charts.find_one(:date => date, :symbol => 'eurusd')["alpha"].size.should == 2
115
+ charts.find(:timeframe => 'h1').count.should == 1
116
+ c = charts.find_one(:symbol => 'eurusd')
117
+ c["updated"].should == Time.utc(2010, 12, 27, 11, 5).to_i
118
+ c["indicators"]["octopus"].should.not.be.nil?
69
119
  end
70
120
  end
71
-
72
- describe 'saving Zeta' do
121
+
122
+ describe 'Prognosis' do
73
123
  before do
74
- charts.remove
124
+ @params = {}
125
+ @params[:prognosis] = [{
126
+ "symbol" => "EURUSD",
127
+ "timeframe" => "h1",
128
+ "action" => "buy",
129
+ "acceleration" => "undefined",
130
+ "take_profit" => 1.123,
131
+ "profit" => 100,
132
+ "stop_loss" => 2.123,
133
+ "loss" => 50,
134
+ "tsi" => 90,
135
+ "trend" => 1,
136
+ "screenshot_filename" => "egg.png"
137
+ }]
75
138
  end
76
139
 
77
- it 'should create first zeta signals' do
78
- response = make_request('/zeta', zeta_params)
140
+ it 'should save signal' do
141
+ response = nil
142
+ Timecop.travel(Time.utc(2010, 12, 27, 11, 5)) do
143
+ response = make_request('/prognosis', @params)
144
+ end
79
145
  response.status.should == 200
80
146
 
81
- charts.find().count.should == 1
82
- charts.find_one(:date => date, :symbol => 'eurusd')["zeta"].size.should == 1
147
+ charts.find(:timeframe => 'h1').count.should == 1
148
+ c = charts.find_one(:symbol => 'eurusd')
149
+ c["updated"].should == Time.utc(2010, 12, 27, 11, 5).to_i
150
+ c["indicators"]["prognosis"].should.not.be.nil?
83
151
  end
84
152
  end
85
153
 
@@ -2,59 +2,153 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe 'Finexclub::Chart' do
4
4
  before do
5
- @core = mock_core
5
+ @core = test_core
6
6
  @chart = Finexclub::Chart.new(@core)
7
+
8
+ Finexclub.signals.collection.drop
9
+ end
10
+
11
+ describe 'indicators handling' do
12
+ before do
13
+ class Omega < Finexclub::Signal
14
+ field :symbol, :symbol
15
+ field :timeframe, :string
16
+ field :updated, :timestamp
17
+ field :foo, :string
18
+
19
+ doc_fields :foo, :updated
20
+ end
21
+ end
22
+
23
+ describe 'Chart#indicator_handler(:omega, Omega)' do
24
+ before do
25
+ Finexclub::Chart.indicator_handler(:omega, Omega)
26
+ end
27
+
28
+ it 'should return valid class as indicator handler' do
29
+ Finexclub::Chart.handler_for(:omega).should == Omega
30
+ @chart.handler_for(:omega) == Omega
31
+ end
32
+
33
+ describe '#omega signal accessor' do
34
+ before do
35
+ @omega_doc = {:symbol => 'usdjpy', :timeframe => 'h1', :foo => "Ololo!"}
36
+ Finexclub.signals.add_signal(:omega, @omega_doc, Time.utc(2010, 12, 27, 11, 5))
37
+ @chart = Finexclub.signals.find_one('usdjpy', 'h1', Time.utc(2010, 12, 27, 11, 10))
38
+ end
39
+
40
+ it 'should return valid Omega object' do
41
+ @chart.should.respond_to?(:omega)
42
+ @chart.omega.should.not == nil
43
+ @chart.omega.class.should == Omega
44
+
45
+ @chart.omega.foo.should == "Ololo!"
46
+ @chart.omega.updated.should == Time.utc(2010, 12, 27, 11, 5).to_i
47
+ end
48
+
49
+ it 'should return nil if no indicator doc is available' do
50
+ @chart.alpha.should == nil
51
+ end
52
+ end
53
+
54
+ #it 'should add #omega=(Signal) accessor to save raw document' do
55
+ #omega_doc = {:foo => "Ololo!", :updated => Time.now.utc.to_i}
56
+ #@chart.omega =
57
+ #@chart.indicators[:omega].should == omega_doc
58
+ #end
59
+ end
60
+
61
+ describe 'complex chart with default indicators' do
62
+ before do
63
+ Finexclub.signals.add_signal(:alpha, {:symbol => 'eurusd', :timeframe => 'h1'}, Time.now.utc)
64
+ Finexclub.signals.add_signal(:zeta, {:symbol => 'eurusd', :timeframe => 'h1'}, Time.now.utc)
65
+ Finexclub.signals.add_signal(:octopus, {:symbol => 'eurusd', :timeframe => 'h1'}, Time.now.utc)
66
+ Finexclub.signals.add_signal(:prognosis, {:symbol => 'eurusd', :timeframe => 'h1'}, Time.now.utc)
67
+ end
68
+
69
+ {
70
+ :alpha => Finexclub::Signals::Alpha,
71
+ :zeta => Finexclub::Signals::Zeta,
72
+ :octopus => Finexclub::Signals::Octopus,
73
+ :prognosis => Finexclub::Signals::Prognosis
74
+ }.each do |indicator, klass|
75
+ it "should have :#{indicator} handler as #{klass}" do
76
+ Finexclub::Chart.handler_for(indicator).should == klass
77
+ end
78
+
79
+ it "should access chart.#{indicator}" do
80
+ chart = Finexclub.signals.find_one('eurusd', 'h1')
81
+ chart.send(indicator).class.should == klass
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ describe 'Chart.build_signal(indicator, params)' do
88
+ end
89
+
90
+ describe 'Chart.build_new(symbol, tf, time)' do
91
+ before do
92
+ @chart = Finexclub::Chart.build_new(@core, 'eurusd', 'h1', Time.utc(2010, 12, 27, 11, 30).to_i)
93
+ end
94
+
95
+ it 'should return valid new (unsaved) chart instance' do
96
+ @chart._id.should == nil
97
+ @chart.symbol.should == 'eurusd'
98
+ @chart.timeframe.should == 'h1'
99
+ @chart.starts.should == Time.utc(2010, 12, 27, 11, 0).to_i
100
+ @chart.expires.should == Time.utc(2010, 12, 27, 12, 0).to_i
101
+ @chart.updated.should == Time.utc(2010, 12, 27, 11, 30).to_i
102
+ @chart.indicators.should == {}
103
+ end
104
+
105
+ it 'should build valid chart given time as Time object' do
106
+ Finexclub::Chart.build_new(@core, 'eurusd', 'h1', Time.utc(2010, 12, 27, 11, 30)).starts.should == Time.utc(2010, 12, 27, 11, 0).to_i
107
+ end
108
+
7
109
  end
110
+
8
111
 
9
- describe 'Instance' do
112
+ describe 'instance' do
10
113
  before do
11
114
  @a1, @a2 = mock('a1'), mock('a2')
12
- @z1, @z2 = mock('z1'), mock('z2')
13
- @ts = Time.now.to_i
115
+ @z1 = mock('z1')
14
116
 
15
- chart_doc = {
117
+ @chart_doc = {
16
118
  :symbol => 'eurusd',
17
- :date => '2010-09-08',
18
119
  :timeframe => 'h1',
19
- :updated => @ts,
20
- :alpha => [ @a1, @a2 ],
21
- :zeta => [ @z1, @z2 ]
120
+ :starts => Time.utc(2010, 12, 27, 11, 0).to_i,
121
+ :expires => Time.utc(2010, 12, 27, 12, 0).to_i,
122
+ :updated => Time.utc(2010, 12, 27, 11, 5).to_i,
123
+ :indicators => {
124
+ :alpha => @a1,
125
+ :zeta => @z1
126
+ }
22
127
  }
23
- @chart.build(chart_doc)
24
- end
25
128
 
26
- it 'should have attribute accessors' do
27
- @chart.symbol.should == 'eurusd'
28
- @chart.timeframe.should == 'h1'
29
- @chart.date.should == '2010-09-08'
30
- @chart.updated.should == @ts
129
+ @chart.build(@chart_doc)
31
130
  end
32
- end
33
131
 
34
- [
35
- :alpha,
36
- :zeta,
37
- :octopus,
38
- :prognosis
39
- ].each do |indicator|
40
- describe "#{indicator} signals" do
41
- before do
42
- @s1, @s2 = mock("signal_#{indicator}_1"), mock("signal_#{indicator}_2")
43
- @chart.build(indicator => [@s1, @s2])
44
- end
132
+ it 'should provide access to raw indicator documents' do
133
+ @chart.indicators[:alpha].should == @a1
134
+ @chart.indicators[:zeta].should == @z1
135
+ end
45
136
 
46
- it "should provide :#{indicator} signals" do
47
- @chart.signals(indicator).should == [@s1, @s2]
48
- end
137
+ it 'should update raw indicator document' do
138
+ @chart.indicators[:alpha] = @a2
139
+ @chart.indicators[:alpha].should == @a2
140
+ end
49
141
 
50
- it "should return latest :#{indicator} wrapped as Signal" do
51
- Finexclub::Signal.should.receive(:build).with(@core, indicator, @s2).and_return(a = mock('signal'))
52
- @chart.send(indicator).should == a
53
- end
142
+ it 'should provide common accessors' do
143
+ @chart.symbol.should == 'eurusd'
144
+ @chart.timeframe.should == 'h1'
145
+ @chart.updated.should == @chart_doc[:updated]
146
+ @chart.starts.should == @chart_doc[:starts]
147
+ @chart.expires.should == @chart_doc[:expires]
54
148
  end
55
149
 
56
- it "should return nil as latest if no #{indicator} signals available" do
57
- @chart.send(indicator).should == nil
150
+ it 'should export valid document' do
151
+ @chart.to_doc.should == @chart_doc
58
152
  end
59
153
  end
60
154
 
@@ -59,41 +59,41 @@ describe 'Finexclub::Core' do
59
59
  end
60
60
  end
61
61
 
62
- describe '#find' do
63
- before do
64
- @core = test_core
65
- end
62
+ #describe '#find' do
63
+ #before do
64
+ #@core = test_core
65
+ #end
66
66
 
67
- it 'should fetch all charts for given date' do
68
- @core.signals.should.receive(:find).with({:date => "2010-10-01"}).and_return(c = mock('cursor'))
69
- @core.find(:all, "2010-10-01").should == c
70
- end
67
+ #it 'should fetch all charts for given date' do
68
+ #@core.signals.should.receive(:find).with({:date => "2010-10-01"}).and_return(c = mock('cursor'))
69
+ #@core.find(:all, "2010-10-01").should == c
70
+ #end
71
71
 
72
- it 'should fetch an array of charts for given date' do
73
- @core.signals.should.receive(:find).with({:symbol => {:$in => ["eurusd", "usdjpy"]}, :date => "2010-10-01"}).and_return(c = mock('cursor'))
74
- @core.find(["eurusd", "usdjpy"], "2010-10-01").should == c
75
- end
72
+ #it 'should fetch an array of charts for given date' do
73
+ #@core.signals.should.receive(:find).with({:symbol => {:$in => ["eurusd", "usdjpy"]}, :date => "2010-10-01"}).and_return(c = mock('cursor'))
74
+ #@core.find(["eurusd", "usdjpy"], "2010-10-01").should == c
75
+ #end
76
76
 
77
- it 'should allow fetching signals by date and symbol' do
78
- @core.signals.should.receive(:find_one).with({:date => "2010-10-01", :symbol => "eurusd"}).and_return(chart = mock('chart'))
79
- @core.find("eurusd", "2010-10-01").should == chart
80
- end
81
- end
77
+ #it 'should allow fetching signals by date and symbol' do
78
+ #@core.signals.should.receive(:find_one).with({:date => "2010-10-01", :symbol => "eurusd"}).and_return(chart = mock('chart'))
79
+ #@core.find("eurusd", "2010-10-01").should == chart
80
+ #end
81
+ #end
82
82
 
83
- describe '#store' do
84
- before do
85
- @core = test_core
86
- end
87
- it 'should store each signal given an array of params' do
88
- @core.signals.should.receive(:add_signal).with(:alpha,p1=mock('p1'))
89
- @core.signals.should.receive(:add_signal).with(:alpha,p2=mock('p2'))
90
- @core.store(:alpha, [p1, p2])
91
- end
92
- it 'should store signal given signle hash of params' do
93
- @core.signals.should.receive(:add_signal).with(:alpha,p1=mock('p1'))
94
- @core.store(:alpha, p1)
95
- end
96
- end
83
+ #describe '#store' do
84
+ #before do
85
+ #@core = test_core
86
+ #end
87
+ #it 'should store each signal given an array of params' do
88
+ #@core.signals.should.receive(:add_signal).with(:alpha,p1=mock('p1'))
89
+ #@core.signals.should.receive(:add_signal).with(:alpha,p2=mock('p2'))
90
+ #@core.store(:alpha, [p1, p2])
91
+ #end
92
+ #it 'should store signal given signle hash of params' do
93
+ #@core.signals.should.receive(:add_signal).with(:alpha,p1=mock('p1'))
94
+ #@core.store(:alpha, p1)
95
+ #end
96
+ #end
97
97
 
98
98
  end
99
99
 
@@ -4,59 +4,161 @@ describe 'Finexclub::Manager' do
4
4
  before do
5
5
  @core = mock_core
6
6
  @manager = Finexclub::Manager.new(@core)
7
- @manager.db = mock_mongo
8
- end
9
7
 
10
- describe 'mongo' do
11
- before do
12
- @mongo = mock('mongo')
13
- @manager.db = @mongo
14
- end
15
- it 'should provide mongo instance' do
16
- @manager.db.should == @mongo
17
- end
18
- it 'should have collection accessor' do
19
- @mongo.stub!(:collection).and_return(coll = mock('coll'))
20
- @manager.collection.should == coll
21
- end
8
+ @mongo = test_mongo
9
+ @manager.db = @mongo
10
+
11
+ @mongo.collection("charts").drop
22
12
  end
23
13
 
24
- describe '.find' do
25
- it 'should call find on collection and return Cursor' do
26
- @manager.collection.should.receive(:find).with({:date => "2010-10-01"}, {}).and_return(c = mock('cursor'))
27
- @manager.find(:date => "2010-10-01")
14
+ describe '.db' do
15
+ it 'should return mongo instance' do
16
+ @manager.db.should == @mongo
28
17
  end
29
18
  end
30
19
 
31
- describe '.find_one' do
32
- it 'should call find on collection' do
33
- @manager.collection.should.receive(:find_one).with({:date => "2010-10-01", :symbol => "eurusd" }, {}).and_return({})
34
- @manager.find_one(:date => "2010-10-01", :symbol => "eurusd").class == Finexclub::Chart
20
+ describe '.collection' do
21
+ it 'should raise if no db is configured' do
22
+ @manager.db = nil
23
+ should.raise(Finexclub::ConfigurationError) { @manager.collection }
35
24
  end
36
25
 
37
- it 'should return nil if nothing found' do
38
- @manager.collection.should.receive(:find_one).with({:date => "2010-10-01", :symbol => "eurusd"}, {}).and_return(nil)
39
- @manager.find_one(:date => "2010-10-01", :symbol => "eurusd").should == nil
26
+ it 'should return charts collection' do
27
+ @manager.collection.is_a?(Mongo::Collection).should.be.true
28
+ @manager.collection.name.should == "charts"
40
29
  end
41
30
  end
42
31
 
43
- describe '.last_updated' do
32
+ describe 'finding charts' do
44
33
  before do
45
- @cursor = mock('cursor', :first => {"updated" => 123})
34
+ charts = @mongo.collection('charts')
35
+ charts.drop
36
+ @c1 = {:symbol => 'eurusd', :timeframe => 'h1', :starts => Time.utc(2010, 12, 27, 11, 0).to_i, :expires => Time.utc(2010, 12, 27, 12, 0).to_i, :updated => Time.utc(2010, 12, 27, 11, 3).to_i}
37
+ @c2 = {:symbol => 'eurusd', :timeframe => 'h1', :starts => Time.utc(2010, 12, 27, 12, 0).to_i, :expires => Time.utc(2010, 12, 27, 13, 0).to_i, :updated => Time.utc(2010, 12, 27, 12, 5).to_i}
38
+ @c3 = {:symbol => 'usdjpy', :timeframe => 'h1', :starts => Time.utc(2010, 12, 27, 12, 0).to_i, :expires => Time.utc(2010, 12, 27, 13, 0).to_i, :updated => Time.utc(2010, 12, 27, 12, 30).to_i}
39
+ @c4 = {:symbol => 'usdjpy', :timeframe => 'h1', :starts => Time.utc(2010, 12, 27, 13, 0).to_i, :expires => Time.utc(2010, 12, 27, 14, 0).to_i, :updated => Time.utc(2010, 12, 27, 13, 30).to_i}
40
+
41
+ @id1 = charts.insert(@c1)
42
+ @id2 = charts.insert(@c2)
43
+ @id3 = charts.insert(@c3)
44
+ @id4 = charts.insert(@c4)
46
45
  end
47
46
 
48
- it 'should search for the latest timestamp in collection' do
49
- @manager.collection.should.receive(:find).with({}, :fields => {:updated => 1}, :sort => [['updated', :desc]], :limit => 1).and_return(@cursor)
50
- @manager.last_updated.should == Time.at(123)
47
+ describe 'generalized .find()' do
48
+ #it 'should pass given query and options to collection#find' do
49
+ #@mongo.collection.should.receive(:find).with({:symbol => 'eurusd'}, {})
50
+ #@manager.find({:symbol => 'eurusd'})
51
+ #end
52
+
53
+ it 'should return Cursor object' do
54
+ cursor = lambda {|obj| obj.is_a? Finexclub::Manager::Cursor}
55
+ @manager.find(:symbol => 'eurusd').should.be.a cursor
56
+ end
51
57
  end
52
58
 
53
- it 'should return nil if nothing found' do
54
- @manager.collection.stub!(:find).and_return(mock('cursor', :first => nil))
55
- @manager.last_updated.should == nil
59
+ describe 'find single chart' do
60
+ describe '.find_one(symbol, tf, time)' do
61
+ it 'should find a chart given time as Time' do
62
+ c = @manager.find_one('eurusd', 'h1', Time.utc(2010, 12, 27, 11, 30))
63
+ c._id.should == @id1
64
+ end
65
+
66
+ it 'should find a chart given symbol, timeframe and time' do
67
+ c = @manager.find_one('eurusd', 'h1', Time.utc(2010, 12, 27, 11, 30).to_i)
68
+ c._id.should == @id1
69
+ end
70
+
71
+ it 'should find a chart at present moment (Time.now) by default ' do
72
+ Timecop.travel(Time.utc(2010, 12, 27, 11, 30))
73
+ c = @manager.find_one('eurusd', 'h1')
74
+ Timecop.return
75
+
76
+ c._id.should == @id1
77
+ end
78
+
79
+ it 'should return nil given time greather then the last available chart' do
80
+ @manager.find_one('eurusd', 'h1', Time.utc(2010, 12, 27, 13, 30).to_i).should == nil
81
+ end
82
+
83
+ it 'should return nil if nothing found for given timeframe' do
84
+ @manager.find_one('eurusd', 'h4').should == nil
85
+ end
86
+
87
+ it 'should return nil if nothing found for given symbol' do
88
+ @manager.find_one('usdcad', 'h1').should == nil
89
+ end
90
+ end
91
+
92
+ describe '.find_one_or_create(symbol, tf, time)' do
93
+ it 'should return existing chart for given symbol, timeframe and time' do
94
+ c = @manager.find_one_or_create('eurusd', 'h1', Time.utc(2010, 12, 27, 11, 30))
95
+ c._id.should == @id1
96
+ end
97
+
98
+ #it 'should build valid chart if no chart exists' do
99
+ #Finexclub::Chart.should.receive(:build_new).with(@core, 'eurusd', 'h1', Time.utc(2010, 12, 27, 13, 5)).and_return(c = mock('chart'))
100
+ #@manager.find_one_or_create('eurusd', 'h1', Time.utc(2010, 12, 27, 13, 5)).should == c
101
+ #end
102
+
103
+ it 'should create a new chart if no chart exists' do
104
+ c = @manager.find_one_or_create('eurusd', 'h1', Time.utc(2010, 12, 27, 13, 5))
105
+ c._id.should.not.be.nil?
106
+ end
107
+ end
108
+
109
+ describe '.find_latest(symbol, tf)' do
110
+ it 'should find the latest chart by symbol and timeframe' do
111
+ c = @manager.find_latest('eurusd', 'h1')
112
+ c._id.should == @id2
113
+ end
114
+
115
+ it 'should return nil if nothing found for given timeframe' do
116
+ @manager.find_latest('eurusd', 'h4').should == nil
117
+ end
118
+
119
+ it 'should return nil if nothing found for given symbol' do
120
+ @manager.find_latest('usdcad', 'h1').should == nil
121
+ end
122
+ end
123
+
124
+ describe '.last_updated(tf)' do
125
+ it 'should return timestamp of the latest update through out all symbols for given timeframe' do
126
+ # it should be @c4, which is usdjpy:h1
127
+ @manager.last_updated('h1').should == @c4[:updated]
128
+ end
129
+
130
+ it 'should return nil if no charts found' do
131
+ @manager.last_updated('h4').should == nil
132
+ end
133
+ end
134
+
135
+ describe '.last_updated_at(tf)' do
136
+ it 'should return Time of the latest collection update' do
137
+ @manager.last_updated_at('h1').is_a? Time
138
+ @manager.last_updated_at('h1').should == Time.at(@c4[:updated]).utc
139
+ end
140
+
141
+ it 'should return nil if no charts found' do
142
+ @manager.last_updated_at('h4').should == nil
143
+ end
144
+ end
145
+ end
146
+
147
+ describe 'find many charts' do
148
+ describe '.find_many(symbol_or_arr, tf_or_arr, time)' do
149
+ it 'should find charts given time as Time' do
150
+ #cur = @manager.find_many(['eurusd', 'usdcad'], 'h1', Time.utc(2010, 12, 27, 12, 40))
151
+ #cur.count.should == 2
152
+ #[ cur[0]._id, cur[1]._id ].should.be.same_as [@id2, @id3]
153
+ should.flunk
154
+ end
155
+ end
156
+
56
157
  end
158
+
57
159
  end
58
160
 
59
- describe '.add_signal' do
161
+ describe 'saving signals' do
60
162
  before do
61
163
  class Omega < Finexclub::Signal
62
164
  field :symbol, :symbol
@@ -66,28 +168,77 @@ describe 'Finexclub::Manager' do
66
168
 
67
169
  doc_fields :foo, :updated
68
170
  end
171
+ Finexclub::Chart.indicator_handler(:omega, Omega)
172
+
173
+ charts = @mongo.collection('charts')
174
+ charts.drop
175
+ @c1 = {
176
+ :symbol => 'eurusd',
177
+ :timeframe => 'h1',
178
+ :starts => Time.utc(2010, 12, 27, 11, 0).to_i,
179
+ :expires => Time.utc(2010, 12, 27, 12, 0).to_i,
180
+ :updated => nil,
181
+ :indicators => {}
182
+ }
183
+ @c2 = {
184
+ :symbol => 'usdcad',
185
+ :timeframe => 'h1',
186
+ :starts => Time.utc(2010, 12, 27, 11, 0).to_i,
187
+ :expires => Time.utc(2010, 12, 27, 12, 0).to_i,
188
+ :updated => Time.utc(2010, 12, 27, 11, 3).to_i,
189
+ :indicators => {
190
+ :omega => {:foo => "bar", :updated => Time.utc(2010, 12, 27, 11, 3).to_i}
191
+ }
192
+ }
193
+ @id1 = charts.insert(@c1)
194
+ @id2 = charts.insert(@c2)
69
195
  end
70
196
 
71
- it 'should update collection' do
72
- #Mon Oct 04 18:10:20 0700 2010
73
- ts = 1286190620
74
-
75
- Finexclub::Signal.stub!(:handler_for).with(:omega).and_return(Omega)
76
- @manager.collection.should.receive(:update).with(
77
- {
78
- :date => '2010-10-04',
79
- :timeframe => 'h1',
80
- :symbol => 'eurusd'
81
- },
82
- {
83
- "$push" => {:omega => {:foo => "bar", :updated => 1286190620}},
84
- "$set" => {:updated => 1286190620}
85
- },
86
- :upsert => true
87
- )
88
-
89
- Timecop.travel(Time.at(ts))
90
- @manager.add_signal(:omega, {"foo" => "bar", "timeframe" => 'h1', "symbol" => "EURUSD"})
197
+ describe '.add_signal(indicator, raw_params, time)' do
198
+ it 'should add signal to existing chart' do
199
+ @manager.add_signal(:omega, {"symbol" => "EURUSD", "timeframe" => "h1", "foo" => "bar"}, Time.utc(2010, 12, 27, 11, 5))
200
+
201
+ c = @manager.collection.find_one({"_id" => @id1})
202
+ c["symbol"].should == 'eurusd'
203
+ c["timeframe"].should == 'h1'
204
+ c["starts"].should == Time.utc(2010, 12, 27, 11, 0).to_i
205
+ c["expires"].should == Time.utc(2010, 12, 27, 12, 0).to_i
206
+ c["updated"].should == Time.utc(2010, 12, 27, 11, 5).to_i
207
+ c["indicators"]["omega"].should == {
208
+ "foo" => "bar",
209
+ "updated" => Time.utc(2010, 12, 27, 11, 5).to_i
210
+ }
211
+ end
212
+
213
+ it 'should update existing signal on existing chart' do
214
+ @manager.add_signal(:omega, {"symbol" => "USDCAD", "timeframe" => "h1", "foo" => "ololo!"}, Time.utc(2010, 12, 27, 11, 30))
215
+
216
+ c = @manager.collection.find_one({"_id" => @id2})
217
+ c["symbol"].should == 'usdcad'
218
+ c["timeframe"].should == 'h1'
219
+ c["starts"].should == Time.utc(2010, 12, 27, 11, 0).to_i
220
+ c["expires"].should == Time.utc(2010, 12, 27, 12, 0).to_i
221
+ c["updated"].should == Time.utc(2010, 12, 27, 11, 30).to_i
222
+ c["indicators"]["omega"].should == {
223
+ "foo" => "ololo!",
224
+ "updated" => Time.utc(2010, 12, 27, 11, 30).to_i
225
+ }
226
+ end
227
+
228
+ it 'should add signals to non-existent chart' do
229
+ @manager.add_signal(:omega, {"symbol" => "EURUSD", "timeframe" => "h4", "foo" => "bar"}, Time.utc(2010, 12, 27, 11, 5))
230
+
231
+ c = @manager.collection.find_one({:symbol => 'eurusd', :timeframe => 'h4'})
232
+ c["symbol"].should == 'eurusd'
233
+ c["timeframe"].should == 'h4'
234
+ c["starts"].should == Time.utc(2010, 12, 27, 11, 0).to_i
235
+ c["expires"].should == Time.utc(2010, 12, 27, 12, 0).to_i
236
+ c["updated"].should == Time.utc(2010, 12, 27, 11, 5).to_i
237
+ c["indicators"]["omega"].should == {
238
+ "foo" => "bar",
239
+ "updated" => Time.utc(2010, 12, 27, 11, 5).to_i
240
+ }
241
+ end
91
242
  end
92
243
  end
93
244
 
data/spec/spec_helper.rb CHANGED
@@ -25,9 +25,15 @@ class Bacon::Context
25
25
  }.merge(extra_stubs)
26
26
  )
27
27
  end
28
+
29
+ def test_mongo
30
+ Mongo::Connection.new().db('finexclub_test')
31
+ end
28
32
 
29
33
  def test_core
30
- Finexclub::Core.send(:new)
34
+ core = Finexclub::Core.send(:new)
35
+ Finexclub.signals.db = test_mongo
36
+ core
31
37
  end
32
38
  end
33
39
 
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 4
8
- - 3
9
- version: 0.4.3
7
+ - 5
8
+ - 1
9
+ version: 0.5.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Alex Levin
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-12-20 00:00:00 +07:00
17
+ date: 2011-02-04 00:00:00 +07:00
18
18
  default_executable: finexclub_updater
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency