finexclub 0.4.3 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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