finexclub 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,103 @@
1
+ require "mongo"
2
+ require "ap"
3
+
4
+ module Finexclub
5
+ class Manager
6
+
7
+ attr_reader :core
8
+ attr_accessor :db
9
+
10
+ def initialize(core)
11
+ @core = core
12
+ end
13
+
14
+ def collection
15
+ raise("No db configured") if db.nil?
16
+ db.collection('charts')
17
+ end
18
+
19
+ def find(query, opts = {})
20
+ Cursor.new(core, Chart, collection.find(query, opts))
21
+ end
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
28
+ end
29
+
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"])
33
+ end
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
+ :date => signal.updated_at.strftime("%Y-%m-%d")
40
+ }
41
+ collection.update(c, {"$push" => {signal_type => signal.to_doc}, "$set" => {:updated => signal.updated}}, :upsert => true)
42
+ end
43
+
44
+ class Cursor
45
+ include Enumerable
46
+ attr_accessor :mongo_cursor
47
+ attr_reader :core
48
+
49
+ def initialize(core, obj_class, mongo_cursor)
50
+ @core = core
51
+ @obj_class = obj_class
52
+ @mongo_cursor = mongo_cursor
53
+ end
54
+
55
+ #def method_missing(name, *args)
56
+ #@mongo_cursor.send name, *args
57
+ #end
58
+
59
+ # Is the cursor empty? This method is much more efficient than doing cursor.count == 0
60
+ def empty?
61
+ @mongo_cursor.has_next? == false
62
+ end
63
+
64
+ def next_document
65
+ if doc = @mongo_cursor.next_document
66
+ obj = @obj_class.new(core)
67
+ obj.build(doc)
68
+ obj
69
+ end
70
+ end
71
+
72
+ alias :next :next_document
73
+
74
+ def each
75
+ @mongo_cursor.each do |doc|
76
+ obj = @obj_class.new(core)
77
+ obj.build(doc)
78
+ yield(obj)
79
+ end
80
+ end
81
+
82
+ def current_limit
83
+ @mongo_cursor.limit
84
+ end
85
+
86
+ def limit(number_to_return)
87
+ @mongo_cursor.limit(number_to_return); self
88
+ end
89
+
90
+ def current_skip
91
+ @mongo_cursor.skip
92
+ end
93
+
94
+ def skip(number_to_skip)
95
+ @mongo_cursor.skip(number_to_skip); self
96
+ end
97
+
98
+ def sort(key_or_list, direction = nil)
99
+ @mongo_cursor.sort(key_or_list, direction); self
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,27 @@
1
+ module Finexclub
2
+ class Signal
3
+ include Finexclub::Document
4
+ attr_reader :core
5
+
6
+ def initialize(core, meta = {})
7
+ @core = core
8
+ apply_meta(meta)
9
+ end
10
+
11
+ class << self
12
+ def handler_for(signal_type)
13
+ #class_name = signal_type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
14
+ class_name = signal_type.to_s.gsub(/^(.)/) { $1.upcase }
15
+ Finexclub::Signals.const_get(class_name)
16
+ end
17
+
18
+ def build(core, signal_type, params)
19
+ signal = handler_for(signal_type).new(core)
20
+ signal.build(params)
21
+ signal
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+
@@ -0,0 +1,28 @@
1
+ module Finexclub
2
+ module Signals
3
+
4
+ class Alpha < Signal
5
+ field :symbol, :symbol
6
+ field :updated, :timestamp
7
+ field :index, :integer
8
+ field :direction, :integer
9
+
10
+ doc_fields :updated, :index, :direction
11
+
12
+ def trend
13
+ direction == 1 ? :bullish : :bearish
14
+ end
15
+
16
+ def stability
17
+ case index
18
+ when 100 then :confirmed
19
+ when 90..99 then :high
20
+ when 80..89 then :medium
21
+ when 70..79 then :low
22
+ else :unstable
23
+ end
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ module Finexclub
2
+ module Signals
3
+
4
+ class Zeta < Signal
5
+ field :symbol, :symbol
6
+ field :updated, :timestamp
7
+
8
+ field :up_support, :float
9
+ field :up_resist, :float
10
+ field :down_support, :float
11
+ field :down_resist, :float
12
+ field :screenshot, :image
13
+
14
+ doc_fields :up_support, :up_resist, :down_support, :down_resist, :screenshot, :updated
15
+
16
+ def up_channel?
17
+ up_support != 0 && up_resist != 0
18
+ end
19
+
20
+ def down_channel?
21
+ down_support != 0 && down_resist != 0
22
+ end
23
+ end
24
+
25
+ end
26
+ end
data/lib/finexclub.rb ADDED
@@ -0,0 +1,45 @@
1
+ # AUTOLOAD EVERYTHING IN THE FINEXCLUB DIRECTORY TREE
2
+
3
+ # The convention is that dirs are modules
4
+ # so declare them here and autoload any modules/classes inside them
5
+ # All paths here are absolute
6
+ def camelize(path)
7
+ # e.g. 'test/this_one' => Test::ThisOne
8
+ "#{path}".
9
+ chomp('/').
10
+ gsub('/','::').
11
+ gsub(/([^a-z])(\w)/){ "#{$1}#{$2.upcase}" }.
12
+ gsub('_','').
13
+ sub(/^(\w)/){ $1.upcase }
14
+ end
15
+ def autoload_files_in_dir(path, namespace)
16
+ # Define the module
17
+ eval("module #{namespace}; end")
18
+ # Autoload modules/classes in that module
19
+ Dir.glob("#{path}/*.rb").each do |file|
20
+ file = File.expand_path(file)
21
+ sub_const_name = camelize( File.basename(file, '.rb') )
22
+ eval("#{namespace}.autoload('#{sub_const_name}', '#{file}')")
23
+ end
24
+ # Recurse on subdirectories
25
+ Dir.glob("#{path}/*/").each do |dir|
26
+ sub_namespace = camelize( File.basename(dir) )
27
+ autoload_files_in_dir(dir, "#{namespace}::#{sub_namespace}")
28
+ end
29
+ end
30
+
31
+ autoload_files_in_dir("#{File.dirname(__FILE__)}/finexclub", 'Finexclub')
32
+
33
+ require 'forwardable'
34
+
35
+ module Finexclub
36
+ class << self
37
+ extend Forwardable
38
+ def_delegators :core, :store, :find, :configure
39
+ def_delegators :core, :images, :signals
40
+
41
+ def core
42
+ Core.instance
43
+ end
44
+ end
45
+ end
data/samples/egg.png ADDED
Binary file
@@ -0,0 +1,47 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe 'Finexclub::Signals::Alpha' do
4
+ before do
5
+ @core = mock_core
6
+ @alpha = Finexclub::Signals::Alpha.new(@core)
7
+ end
8
+
9
+ it 'should provide attribute accessors' do
10
+ @alpha.build("direction" => 1, "index" => 100)
11
+ @alpha.direction.should == 1
12
+ @alpha.index.should == 100
13
+ end
14
+
15
+ {
16
+ 1 => :bullish,
17
+ -1 => :bearish,
18
+ 0 => :bearish
19
+ }.each do |direction, trend|
20
+ it "should return #{trend} trend when direction is #{direction}" do
21
+ @alpha.direction = direction
22
+ @alpha.trend.should == trend
23
+ end
24
+ end
25
+
26
+ {
27
+ :confirmed => 100..100,
28
+ :high => 90..99,
29
+ :medium => 80..89,
30
+ :low => 70..79,
31
+ :unstable => 0..60
32
+ }.each do |stability, range|
33
+ it "should return #{stability} stability when index within #{range}" do
34
+ @alpha.index = range.first
35
+ @alpha.stability.should == stability
36
+
37
+ @alpha.index = range.last
38
+ @alpha.stability.should == stability
39
+ end
40
+ end
41
+
42
+ it 'should export valid doc' do
43
+ @alpha.build("direction" => 1, "index" => 100, "updated" => 123)
44
+ @alpha.to_doc.should == {:updated => 123, :index => 100, :direction => 1}
45
+ end
46
+ end
47
+
@@ -0,0 +1,89 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require File.dirname(__FILE__) + '/../spec_helper'
4
+ require 'rack/test'
5
+
6
+ include Rack::Test::Methods
7
+ include Finexclub::Fabrication
8
+
9
+ Finexclub.configure do |f|
10
+ f.images.screenshot_path = TEST_IMAGES_PATH
11
+ f.signals.db = Mongo::Connection.new.db("finexclub_test")
12
+ end
13
+
14
+ def app
15
+ Finexclub::App
16
+ end
17
+
18
+ def charts
19
+ Finexclub.signals.collection
20
+ end
21
+
22
+ def make_request(url, opts = {})
23
+ post url, opts
24
+ last_response
25
+ end
26
+
27
+ def date(ts = nil)
28
+ ts ||= Time.now.utc
29
+ ts.strftime("%Y-%m-%d")
30
+ end
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
37
+
38
+ def zeta_params(options = {})
39
+ #options[:symbol] ||= "EURUSD"
40
+ params = {}
41
+ params[:zeta] = [raw_zeta_hash(options)]
42
+ params
43
+ end
44
+
45
+ describe 'Finexclub::App' do
46
+ describe 'saving Alpha' do
47
+ before do
48
+ charts.remove
49
+ end
50
+
51
+ it 'should create first signal' do
52
+ response = make_request('/alpha', alpha_params)
53
+ response.status.should == 200
54
+
55
+ charts.find().count.should == 28
56
+ charts.find_one(:date => date, :symbol => 'eurusd')["alpha"].size.should == 1
57
+ end
58
+
59
+ it 'should add more alpha signals' do
60
+ Timecop.travel
61
+ response = make_request('/alpha', alpha_params)
62
+ response.status.should == 200
63
+
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
69
+ end
70
+ end
71
+
72
+ describe 'saving Zeta' do
73
+ before do
74
+ charts.remove
75
+ end
76
+
77
+ it 'should create first zeta signals' do
78
+ response = make_request('/zeta', zeta_params)
79
+ response.status.should == 200
80
+
81
+ charts.find().count.should == 1
82
+ charts.find_one(:date => date, :symbol => 'eurusd')["zeta"].size.should == 1
83
+ end
84
+ end
85
+
86
+ describe 'keep alive ping' do
87
+ end
88
+ end
89
+
@@ -0,0 +1,51 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe 'Finexclub::Chart' do
4
+ before do
5
+ @core = mock_core
6
+ @chart = Finexclub::Chart.new(@core)
7
+ end
8
+
9
+ describe 'Instance' do
10
+ before do
11
+ @a1, @a2 = mock('a1'), mock('a2')
12
+ @z1, @z2 = mock('z1'), mock('z2')
13
+ @ts = Time.now.to_i
14
+
15
+ chart_doc = {
16
+ :symbol => 'eurusd',
17
+ :date => '2010-09-08',
18
+ :updated => @ts,
19
+ :alpha => [ @a1, @a2 ],
20
+ :zeta => [ @z1, @z2 ]
21
+ }
22
+ @chart.build(chart_doc)
23
+ end
24
+
25
+ it 'should have attribute accessors' do
26
+ @chart.symbol.should == 'eurusd'
27
+ @chart.date.should == '2010-09-08'
28
+ @chart.updated.should == @ts
29
+ end
30
+
31
+ it 'should return all alpha signals' do
32
+ @chart.signals(:alpha).should.equal [@a1, @a2]
33
+ end
34
+
35
+ it 'should return all zeta signals' do
36
+ @chart.signals(:zeta).should.equal [@z1, @z2]
37
+ end
38
+
39
+ it 'should return latest alpha signal as Alpha' do
40
+ Finexclub::Signal.should.receive(:build).with(@core, :alpha, @a2).and_return(a = mock('alpha'))
41
+ @chart.alpha.should == a
42
+ end
43
+
44
+ it 'should return latest zeta signals as Zeta' do
45
+ Finexclub::Signal.should.receive(:build).with(@core, :zeta, @z2).and_return(z = mock('zeta'))
46
+ @chart.zeta.should == z
47
+ end
48
+ end
49
+
50
+ end
51
+
@@ -0,0 +1,91 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe 'Finexclub::Core' do
4
+
5
+ describe '.instance' do
6
+ it 'should create instance if it does not already exists' do
7
+ app = Finexclub::Core.instance
8
+ app.should.be.instance_of Finexclub::Core
9
+ end
10
+
11
+ it 'should return an existing instance' do
12
+ app = Finexclub::Core.instance
13
+ Finexclub::Core.instance.should == app
14
+ end
15
+
16
+ it 'should work with Finexclub.core' do
17
+ Finexclub.core.should == Finexclub::Core.instance
18
+ end
19
+ end
20
+
21
+ describe '.new' do
22
+ it 'should not be callable' do
23
+ lambda {
24
+ Finexclub::Core.new
25
+ }.should.raise(NoMethodError)
26
+ end
27
+ end
28
+
29
+ describe '.configure' do
30
+ before do
31
+ @core = test_core
32
+ end
33
+
34
+ it 'should allow to configure' do
35
+ @core.configure do |f|
36
+ f.should == @core
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '#images' do
42
+ before do
43
+ @core = test_core
44
+ end
45
+ it 'should return Images' do
46
+ @core.images.class.should == Finexclub::Images
47
+ end
48
+ end
49
+
50
+ describe '#signals' do
51
+ before do
52
+ @core = test_core
53
+ end
54
+ it 'should return Manager' do
55
+ @core.signals.class.should == Finexclub::Manager
56
+ end
57
+ end
58
+
59
+ describe '#find' do
60
+ before do
61
+ @core = test_core
62
+ end
63
+
64
+ it 'should fetch all charts for given date' do
65
+ @core.signals.should.receive(:find).with({:date => "2010-10-01"}).and_return(c = mock('cursor'))
66
+ @core.find(:all, "2010-10-01").should == c
67
+ end
68
+
69
+ it 'should allow fetching signals by date and symbol' do
70
+ @core.signals.should.receive(:find_one).with({:date => "2010-10-01", :symbol => "eurusd"}).and_return(chart = mock('chart'))
71
+ @core.find("eurusd", "2010-10-01").should == chart
72
+ end
73
+ end
74
+
75
+ describe '#store' do
76
+ before do
77
+ @core = test_core
78
+ end
79
+ it 'should store each signal given an array of params' do
80
+ @core.signals.should.receive(:add_signal).with(:alpha,p1=mock('p1'))
81
+ @core.signals.should.receive(:add_signal).with(:alpha,p2=mock('p2'))
82
+ @core.store(:alpha, [p1, p2])
83
+ end
84
+ it 'should store signal given signle hash of params' do
85
+ @core.signals.should.receive(:add_signal).with(:alpha,p1=mock('p1'))
86
+ @core.store(:alpha, p1)
87
+ end
88
+ end
89
+
90
+ end
91
+