fin 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY CHANGED
@@ -25,3 +25,7 @@
25
25
  == 0.1.0 / 2011-04-10
26
26
 
27
27
  * Namespace renamed to Fin
28
+
29
+ == 0.1.2 / 2011-05-08
30
+
31
+ * Version bump
@@ -13,8 +13,8 @@ other markets and other data feed types.
13
13
 
14
14
  Domain models represent distinct individual entities that appear in market data feeds
15
15
  (such as Quote, Order, Deal, Instrument, Position and whatnot). Intention is to keep
16
- model API very similar to ActiveModel, with a goal of full ActiveModel compatibility
17
- in the near future.
16
+ model API very similar to ActiveModel, with a goal of full ActiveModel compatibility
17
+ in the near future.
18
18
 
19
19
  Enclosed data structures (Lists and such) are effectively containers for domain objects.
20
20
  They are used to accumulate, analyse and visualize sets of domain objects, received from
@@ -24,9 +24,9 @@ by client and gateway components (data adapters) that deal with actual market da
24
24
  data analysers for storage and retrieval of domain objects passing through them.
25
25
 
26
26
  Ideally, container data structures need to be:
27
- 1) memory-efficient
28
- 2) thread-safe (without too much synchronization penalty)
29
- 3) iteration-safe (adding new element while iterating should not raise exception)
27
+ 1. memory-efficient
28
+ 2. thread-safe (without too much synchronization penalty)
29
+ 3. iteration-safe (adding new element while iterating should not raise exception)
30
30
 
31
31
  == INSTALL:
32
32
 
data/Rakefile CHANGED
@@ -9,9 +9,9 @@ end
9
9
  require 'pathname'
10
10
 
11
11
  BASE_PATH = Pathname.new(__FILE__).dirname
12
- LIB_PATH = BASE_PATH + 'lib'
13
- PKG_PATH = BASE_PATH + 'pkg'
14
- DOC_PATH = BASE_PATH + 'rdoc'
12
+ LIB_PATH = BASE_PATH + 'lib'
13
+ PKG_PATH = BASE_PATH + 'pkg'
14
+ DOC_PATH = BASE_PATH + 'rdoc'
15
15
 
16
16
  $LOAD_PATH.unshift LIB_PATH.to_s
17
17
  require 'version'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.2
data/lib/fin.rb CHANGED
@@ -1,12 +1,9 @@
1
1
  require 'version'
2
- require 'rubygems'
3
- require 'bundler/setup'
4
- Bundler.require(:default)
5
2
 
6
- require 'fin/container_list'
7
- require 'fin/order_list'
3
+ require 'fin/quote_list'
8
4
  require 'fin/deal_list'
9
5
  require 'fin/models/deal'
6
+ require 'fin/models/quote'
10
7
  require 'fin/models/order'
11
8
  require 'fin/models/instrument'
12
9
  require 'fin/models/position'
@@ -1,8 +1,8 @@
1
1
  require 'fin/container_list'
2
2
 
3
3
  module Fin
4
- # Represents Book (OrderBook, DealBook, etc...) for one security(isin)
5
- # It is used as additional index by BookedList subclass (OrderList, DealList)
4
+ # Represents Book (QuoteBook, DealBook, etc...) for one security(isin)
5
+ # It is used as additional index by BookedList subclass (QuoteList, DealList)
6
6
  class Book < ContainerList
7
7
 
8
8
  attr_reader :isin_id
@@ -5,7 +5,7 @@ module Fin
5
5
  # Adds to including List a set of @books, each related to a single security(isin).
6
6
  # @books is effectively an additional index in a List, a set of containers for
7
7
  # its items, grouped by item's isin_id.
8
- # For example: OrderBook, DealBook
8
+ # For example: QuoteBook, DealBook
9
9
  #
10
10
  module BookManager
11
11
 
@@ -26,7 +26,7 @@ module Fin
26
26
 
27
27
  def add? item
28
28
  if super
29
- @changed = true # Mark List as changed
29
+ @changed = true # Mark List as changed
30
30
  @change_count += 1
31
31
  item
32
32
  end
@@ -22,12 +22,21 @@ module Fin
22
22
  end
23
23
 
24
24
  def add_record rec
25
- add? @item_type.from_record(rec)
25
+ add? @item_type.from_record rec
26
26
  end
27
27
 
28
28
  def remove_record rec, id
29
29
  index = @item_type.index_for rec
30
30
  remove? self[index]
31
31
  end
32
+
33
+ def add_message msg
34
+ add? @item_type.from_msg msg
35
+ end
36
+
37
+ def remove_message msg, id
38
+ item = @item_type.from_msg msg
39
+ remove? self[item.index]
40
+ end
32
41
  end
33
42
  end
@@ -2,8 +2,8 @@ require 'fin/models/deal'
2
2
  require 'fin/book_manager'
3
3
 
4
4
  module Fin
5
- # Represents list of ALL Orders, indexed by id (replId)
6
- # Its @books is a set of OrderBooks by isin. Each OrderBook lists Orders by price.
5
+ # Represents list of ALL Deals, indexed by deal_id
6
+ # Its @books is a set of DealBooks by isin_id. Each DealBook lists Deals by deal_id.
7
7
  class DealList < ContainerList
8
8
 
9
9
  include BookManager
@@ -2,74 +2,68 @@ require 'fin/models/model'
2
2
 
3
3
  module Fin
4
4
  # Represents a single deal (trade) for one security
5
- # Source table: FORTS_FUTTRADE_REPL::deal � c�����
5
+ # Source table: FORTS_FUTTRADE_REPL::deal / user_deal � c�����
6
6
  #
7
7
  class Deal < Model
8
+ model_class_id 11
8
9
 
9
10
  # Properties as per P2ClientGate API
10
- prop_accessor [:repl_id, :id], [:repl_rev, :rev],
11
- [:isin_id, :isin], # i4 ���������� �������� ������������� �����������
12
- :price, # price d16.5 ����
13
- :amount, # amount i4 �����, ���-�� ������ �����������
14
- [:deal_id, :id_deal], # id_deal i8 ����� ������
15
- [:sess_id, :session_id], # sess_id i4 ������������� ������
16
- :moment, # moment t ����� ���������� ������.
17
- :pos, # i4 ���-�� ������� �� ����������� �� ����� ����� ������.
18
- :nosystem, # i1 1-������������ ������, 0-�������
19
- [:repo_id, :repo, :id_repo], # i8 ����� ������ ����� ������ ����
20
- :id_deal_multileg # i8 ����� ������ �� ������
11
+ property :isin_id => :i4, # - ���������� �������� �����. �����������
12
+ :price => :'d16.5', # - ����
13
+ :amount => :i4, # - �����, ���-�� ������ �����������
14
+ [:id_deal, :deal_id] => :i8, # - ����� ������
15
+ [:sess_id, :session_id] => :i4, # ������������� ������
16
+ :moment => :t, # ����� ���������� ������.
17
+ :pos => :i4, # ���-�� ������� �� ����������� �� ����� ����� ������.
18
+ :nosystem => :i1 # 1-������������ ������, 0-�������
21
19
 
22
- # ��� ���� ����������� ������ ��� ����� ������:
23
- prop_accessor :code_sell, # c7 ��� ��������:status_sell,
24
- :id_ord_sell, # i8 ����� ������ ��������
25
- :ext_id_sell, # i4 ������� ����� �� ������ ��������
26
- :comment_sell, # c20 ����������� �� ������ ��������.
27
- :trust_sell, # i1 ������� �� (�������������� ����������) �� ������ ��������
28
- :status_sell, # i4 ������ ������ �� ������� ��������
29
- :hedge_sell, # i1 ������� �������� ������ �� ������� ��������
30
- :fee_sell, # d26.2 ���� �� ������ ��������
31
- :login_sell, # c20 ����� ������������ ��������
32
- :code_rts_sell, # c7 ��� ��� ��������
33
- :code_buy, # c7 ��� ����������
34
- :id_ord_buy, # i8 ����� ������ ����������.
35
- :ext_id_buy, # i4 ������� ����� �� ������ ����������
36
- :comment_buy, # c20 ����������� �� ������ ����������
37
- :trust_buy, # i1 ������� �� (�������������� ����������) �� ������ ����������
38
- :status_buy, # i4 ������ ������ �� ������� ����������
39
- :hedge_buy, # i1 ������� �������� ������ �� ������� ����������
40
- :fee_buy, # d26.2 ���� �� ������ ����������
41
- :login_buy, # c20 ����� ������������ ����������
42
- :code_rts_buy # c7 ��� ��� ����������
20
+ # Optional fields, only for repo/multileg deals:
21
+ property [:id_deal_multileg, :deal_multileg_id] => :i8, # ����� ������ �� ������
22
+ [:id_repo, :repo_id] => :i8 # ����� ������ ����� ������ ����
43
23
 
44
- attr_accessor :book
24
+ # Optional fields, only for OWN deals:
25
+ property :code_sell => :c7, # ��� ��������:status_sell,
26
+ :id_ord_sell => :i8, # ����� ������ ��������
27
+ :ext_id_sell => :i4, # ������� ����� �� ������ ��������
28
+ :comment_sell => :c20, # ����������� �� ������ ��������.
29
+ :trust_sell => :i1, # ������� �������������� ���������� �� ������ ��������
30
+ :status_sell => :i4, # ������ ������ �� ������� ��������
31
+ :hedge_sell => :i1, # ������� �������� ������ �� ������� ��������
32
+ :fee_sell => :'d26.2', # ���� �� ������ ��������
33
+ :login_sell => :c20, # ����� ������������ ��������
34
+ :code_rts_sell => :c7, # ��� ��� ��������
35
+ :code_buy => :c7, # ��� ����������
36
+ :id_ord_buy => :i8, # ����� ������ ����������.
37
+ :ext_id_buy => :i4, # ������� ����� �� ������ ����������
38
+ :comment_buy => :c20, # ����������� �� ������ ����������
39
+ :trust_buy => :i1, # ������� �������������� ���������� �� ������ ����������
40
+ :status_buy => :i4, # ������ ������ �� ������� ����������
41
+ :hedge_buy => :i1, # ������� �������� ������ �� ������� ����������
42
+ :fee_buy => :'d26.2', # ���� �� ������ ����������
43
+ :login_buy => :c20, # ����� ������������ ����������
44
+ :code_rts_buy => :c7 # ��� ��� ����������
45
45
 
46
- def self.from_record rec
47
- new :isin_id => rec.GetValAsLong('isin_id'),
48
- :deal_id => rec.GetValAsLong('id_deal'),
49
- :id => rec.GetValAsString('replID').to_i,
50
- :rev => rec.GetValAsString('replRev').to_i,
51
- :price => rec.GetValAsString('price').to_f,
52
- :moment => rec.GetValAsString('moment'),
53
- :amount => rec.GetValAsString('amount').to_i
54
- end
46
+ attr_accessor :book
55
47
 
56
48
  def self.index_for rec
57
49
  rec.GetValAsLong('id_deal')
58
50
  end
59
51
 
60
52
  def index
61
- @deal_id
53
+ deal_id
62
54
  end
63
55
 
64
- def price= val
65
- val = val.to_f
66
- @price = val.round == val ? val.to_i : val
56
+ def price_as_integer
57
+ if price && price.round == price
58
+ price.to_i
59
+ else
60
+ price
61
+ end
67
62
  end
68
63
 
69
- def inspect
70
- "#{moment}:#{id}[#{isin}] #{price}>#{amount}"
64
+ def to_s
65
+ "#{moment}:#{deal_id}[#{isin_id}] #{price}>#{amount}"
71
66
  end
72
67
 
73
- alias to_s inspect
74
68
  end
75
69
  end
@@ -5,74 +5,56 @@ module Fin
5
5
  # Source table: FORTS_FUTINFO_REPL::fut_sess_contents
6
6
  #
7
7
  class Instrument < Model
8
+ model_class_id 12
8
9
 
9
10
  # Properties as per P2ClientGate API
10
- prop_accessor [:repl_id, :id], [:repl_rev, :rev],
11
- [:sess_id, :sess, :session, :session_id], # i4 ����� ������.
12
- :isin_id, # i4 ���������� �������� ��� �����������.
13
- :isin, # c25 ���������� ��� �����������
14
- :short_isin, # c25 ��������� �����������.
15
- :name, # c75 ������������ �����������.
16
- :inst_term, # i4 �������� �� �����.
17
- :code_vcb, # c25 ��� ���������.
18
- # Not extracted from record yet...
19
- :is_limited, # i1 ������� ������� ������� � ������.
20
- :limit_up, # d16.5 ������� ����� ����.
21
- :limit_down, # d16.5 ������ ����� ����.
22
- :old_kotir, # d16.5 ����������������� ��������� ���� ���������� ������.
23
- :buy_deposit, # d16.2 �� ����������.
24
- :sell_deposit, # d16.2 �� ��������.
25
- :roundto, # i4 ���������� ������ ����� ������� � ����.
26
- :min_step, # d16.5 ����������� ��� ����.
27
- :step_price, # d16.5 ��������� ���� ����.
28
- :d_pg, # t ���� ��������� ��������� �����������.
29
- :is_spread, # i1 ������� ��������� �������� � ����������� �����.
30
- # 1 ������; 0 �� ������.
31
- :coeff, # d9.6 ����������� ������������ ������.
32
- :d_exp, # t ���� ���������� �����������.
33
- :is_percent, # i1 ������� ����, ��� ������� ��������� � ���������.
34
- # 1 - ��������� ���������, 0 � ��������� �� ���������
35
- :percent_rate, # d6.2 ���������� ������ ��� ������� ������������ �����
36
- # �� ���������� ���������.
37
- :last_cl_quote, # d16.5 ��������� ����� ���������� ��������.
38
- :signs, # i4 ���� ���������.
39
- :is_trade_evening, # i1 ������� �������� �������� ������.
40
- :ticker, # i4 ���������� �������� ��� �������� �����.
41
- :state, # i4 ��������� �������� �� �����������
42
- :price_dir, # i1 ����������� ���� �����������
43
- :multileg_type, # i1 ��� ������
44
- :legs_qty, # i4 ���������� ������������ ������
45
- :step_price_clr, # d16.5 C�������� ���� ���� ��������� ��������
46
- :step_price_interclr, # d16.5 ��������� ���� ���� ����. ��������
47
- :step_price_curr # d16.5 ��������� ������������ ���� ����, ���������� � ������
48
-
49
- def self.from_record rec
50
- new :isin_id => rec.GetValAsLong('isin_id'),
51
- :isin => rec.GetValAsString('isin'),
52
- :short_isin => rec.GetValAsString('short_isin'),
53
- :name => rec.GetValAsString('name'),
54
- :inst_term => rec.GetValAsLong('inst_term'),
55
- :code_vcb => rec.GetValAsString('code_vcb'),
56
-
57
- end
11
+ property [:sess_id, :session_id] => :i4, # ����� ������.
12
+ :isin_id => :i4, # ���������� �������� ��� �����������.
13
+ :isin => :c25, # ���������� ��� �����������
14
+ :short_isin => :c25, # ��������� �����������.
15
+ :name => :c75, # ������������ �����������.
16
+ :inst_term => :i4, # �������� �� �����.
17
+ :code_vcb => :c25, # ��� ���������.
18
+ :is_limited => :i1, # ������� ������� ������� � ������.
19
+ :limit_up => :'d16.5', # ������� ����� ����.
20
+ :limit_down => :'d16.5', # ������ ����� ����.
21
+ :old_kotir => :'d16.5', # ����������������� ��������� ���� ���������� ������.
22
+ :buy_deposit => :'d16.2', # �� ����������.
23
+ :sell_deposit => :'d16.2', #�� ��������.
24
+ :roundto => :i4, # ���������� ������ ����� ������� � ����.
25
+ :min_step => :'d16.5', # ����������� ��� ����.
26
+ :step_price => :'d16.5', # ��������� ���� ����.
27
+ :d_pg => :t, # ���� ��������� ��������� �����������.
28
+ :is_spread => :i1, # ������� ��������� �������� � ����������� �����.
29
+ # 1 ������; 0 � �� ������.
30
+ :coeff => :'d9.6', # ����������� ������������ ������.
31
+ :d_exp => :t, # ���� ���������� �����������.
32
+ :is_percent => :i1, # ������� ��������� � ���������?
33
+ # 1 - ��������� � ���������, 0 � �� � ���������
34
+ :percent_rate => :'d6.2', # ���������� ������ ��� ������� ������������
35
+ # ����� �� ���������� ���������.
36
+ :last_cl_quote => :'d16.5', # ��������� ����� ���������� ��������.
37
+ :signs => :i4, # ���� ���������.
38
+ :is_trade_evening => :i1, # ������� �������� �������� ������.
39
+ :ticker => :i4, # ���������� �������� ��� �������� �����.
40
+ :state => :i4, # ��������� �������� �� �����������
41
+ :price_dir => :i1, # ����������� ���� �����������
42
+ :multileg_type => :i1, # ��� ������
43
+ :legs_qty => :i4, # ���������� ������������ � ������
44
+ :step_price_clr => :'d16.5', # C�������� ���� ���� ��������� ��������
45
+ :step_price_interclr => :'d16.5', # ��������� ���� ���� ����. ��������
46
+ :step_price_curr => :'d16.5' # ��������� ������������ ���� ���� ������
58
47
 
59
48
  def self.index_for rec
60
49
  rec.GetValAsLong('isin_id')
61
50
  end
62
51
 
63
52
  def index
64
- @isin_id
53
+ isin_id
65
54
  end
66
55
 
67
- def price= val
68
- val = val.to_f
69
- @price = val.round == val ? val.to_i : val
70
- end
71
-
72
- def inspect
56
+ def to_s
73
57
  "#{name}:#{short_isin}[#{isin}]"
74
58
  end
75
-
76
- alias to_s inspect
77
59
  end
78
60
  end
@@ -1,39 +1,135 @@
1
1
  module Fin
2
- # Represents business domain model for a single item (Order, Deal, Instrument, etc...)
3
- # currently it is only used to extract common functionality from record wrappers,
4
- # down the road it may be subclassed from ActiveModel
2
+ # Represents business domain model for a single item (Quote, Deal, Instrument, etc...)
3
+ # Currently it is only used to extract common functionality from record wrappers,
4
+ # down the road the goal is to add ActiveModel compatibility
5
5
  class Model
6
- def self.prop_reader *args
7
- args.each do |arg|
8
- aliases = [arg].flatten
9
- name = aliases.shift
10
- instance_eval do
11
- attr_reader name
12
- aliases.each { |ali| alias_method "#{ali}", name }
13
- end
6
+
7
+ include Enumerable
8
+
9
+ def self.attribute_types
10
+ @attribute_types ||= superclass.attribute_types.dup rescue {}
11
+ end
12
+
13
+ def self.model_class_id value = nil
14
+ if value
15
+ @model_class_id ||= value
16
+ model_classes[@model_class_id] = self
17
+ else
18
+ @model_class_id
14
19
  end
15
20
  end
16
21
 
17
- def self.prop_accessor *args
18
- args.each do |arg|
22
+ def self.model_classes
23
+ @model_classes ||= superclass.model_classes rescue {} #shared list for all subclasses
24
+ end
25
+
26
+ def self.property prop_hash
27
+ prop_hash.each do |arg, type|
19
28
  aliases = [arg].flatten
20
29
  name = aliases.shift
21
30
  instance_eval do
22
- attr_accessor name
31
+
32
+ attribute_types[name.to_s] = type.to_s
33
+
34
+ define_method(name) do
35
+ @attributes[name]
36
+ end
37
+
38
+ define_method("#{name}=") do |value|
39
+ @attributes[name] = value
40
+ end
41
+
23
42
  aliases.each do |ali|
24
43
  alias_method "#{ali}", name
25
44
  alias_method "#{ali}=", "#{name}="
26
45
  end
27
46
  end
28
47
  end
48
+
49
+ # Using static calls, create class method extracting attributes from raw records
50
+ attribute_extractor = attribute_types.map do |name, type|
51
+ case type
52
+ when /^[ct]/ # TODO: In future, read t AsLong and convert into DateTime
53
+ "rec.GetValAsString('#{name}')"
54
+ when /^i[14]/
55
+ "rec.GetValAsLong('#{name}')"
56
+ when /^i8/
57
+ "rec.GetValAsString('#{name}').to_i"
58
+ when /^[df]/
59
+ "rec.GetValAsString('#{name}').to_f"
60
+ else
61
+ raise "Unrecognized attribute type: #{name} => #{type}"
62
+ end
63
+ end.join(",\n")
64
+
65
+ extractor_body = "def self.extract_attributes rec
66
+ [#{attribute_extractor}]
67
+ end"
68
+
69
+ # puts "In #{self}:, #{extractor_body"
70
+ instance_eval extractor_body
71
+ end
72
+
73
+ def self.from_record rec
74
+ new *extract_attributes(rec)
75
+ end
76
+
77
+ # Unpacks attributes into appropriate Model subclass
78
+ def self.from_msg msg
79
+ class_id = msg.first
80
+ model_classes[class_id].new *msg[1..-1]
81
+ end
82
+
83
+ # Extracts attributes from record into a serializable format (Array)
84
+ # Returns an Array where 1st element is a model_class_id of our Model subclass,
85
+ # and second element is a list of arguments to its initialize. Class method!
86
+ def self.to_msg rec
87
+ extract_attributes(rec).unshift(model_class_id)
88
+ end
89
+
90
+ # Converts OBJECT attributes into a serializable format (Array)
91
+ # Returns an Array where 1st element is a model_class_id of our Model subclass,
92
+ # and second element is a list of arguments to its initialize. Instance method!
93
+ def to_msg
94
+ inject([self.class.model_class_id]) { |array, (name, _)| array << send(name) }
29
95
  end
30
96
 
31
- def initialize opts = {}
32
- opts.each { |key, value| send "#{key}=", value }
97
+ # TODO: Builder pattern, to avoid args Array creation on each initialize?
98
+ def initialize *args
99
+ @attributes = {}
100
+ opts = args.last.is_a?(Hash) ? args.pop : {}
101
+ each_with_index { |(name, _), i| send "#{name}=", args[i] } unless args.empty?
102
+ opts.each { |name, value| send "#{name}=", value }
33
103
  end
34
104
 
105
+ def each
106
+ if block_given?
107
+ self.class.attribute_types.each { |name, _| yield name, send(name) }
108
+ else
109
+ self.class.attribute_types.map { |name, _| [name, send(name)].to_enum }
110
+ end
111
+ end
112
+
113
+ alias each_property each
114
+
115
+ def inspect divider=','
116
+ map { |property, value| "#{property}=#{value}" }.join(divider)
117
+ end
118
+
119
+ # TODO: DRY principle: there should be one authoritative source for everything...
120
+ # TODO: Should such source be schema file, or Model code?
121
+ # TODO: Maybe, Model should just read from schema file at init time?
122
+
123
+ # All P2 records carry these properties
124
+ property [:replID, :repl_id] => :i8,
125
+ [:replRev, :repl_rev, :rev] => :i8,
126
+ [:replAct, :repl_act] => :i8
127
+
35
128
  def index
36
129
  object_id # TODO: @repl_id?
37
130
  end
131
+
132
+ # Fin::Model itself has model_class_id 0
133
+ model_class_id 0
38
134
  end
39
135
  end