finexclub 0.1.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.
@@ -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
+