fix-protocol 0.0.3
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.
- checksums.yaml +7 -0
- data/LICENSE +17 -0
- data/README.md +4 -0
- data/lib/fix/protocol.rb +75 -0
- data/lib/fix/protocol/field.rb +129 -0
- data/lib/fix/protocol/grammar.treetop +79 -0
- data/lib/fix/protocol/grammar_extensions/message.rb +57 -0
- data/lib/fix/protocol/message.rb +49 -0
- data/lib/fix/protocol/message_class_mapping.rb +62 -0
- data/lib/fix/protocol/message_header.rb +34 -0
- data/lib/fix/protocol/message_part.rb +175 -0
- data/lib/fix/protocol/messages.rb +11 -0
- data/lib/fix/protocol/messages/heartbeat.rb +15 -0
- data/lib/fix/protocol/messages/instrument.rb +17 -0
- data/lib/fix/protocol/messages/logon.rb +31 -0
- data/lib/fix/protocol/messages/logout.rb +17 -0
- data/lib/fix/protocol/messages/market_data_request.rb +42 -0
- data/lib/fix/protocol/messages/md_entry_type.rb +34 -0
- data/lib/fix/protocol/messages/reject.rb +17 -0
- data/lib/fix/protocol/messages/resend_request.rb +27 -0
- data/lib/fix/protocol/messages/test_request.rb +15 -0
- data/lib/fix/protocol/parse_failure.rb +19 -0
- data/lib/fix/protocol/repeating_message_part.rb +63 -0
- data/lib/fix/protocol/type_conversions.rb +54 -0
- data/lib/fix/protocol/version.rb +10 -0
- metadata +180 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b7794c49ae6633b51af2c60befb621831ecff826
|
4
|
+
data.tar.gz: 95ebd5ecd54b776d0f799c4aaf87e6e091ae4d88
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 373a1c89bda40efec6577efeb641870ef02f4e5e5444734ec745717821947aac54b659b508ae844a99f97fcafe510be229f69368f1e9bc0e4fbf3553112cb639
|
7
|
+
data.tar.gz: fb095eff99fac138baaa9c50f5d8c007b0ae6bba593d854de5ab996608bb83f93737fc4e985a090b801e4c9169c4e05c9e7a54b1f876156143670d85382f69da
|
data/LICENSE
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
2
|
+
of this software and associated documentation files (the "Software"), to deal
|
3
|
+
in the Software without restriction, including without limitation the rights
|
4
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
5
|
+
copies of the Software, and to permit persons to whom the Software is
|
6
|
+
furnished to do so, subject to the following conditions:
|
7
|
+
|
8
|
+
The above copyright notice and this permission notice shall be included in
|
9
|
+
all copies or substantial portions of the Software.
|
10
|
+
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
12
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
13
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
14
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
15
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
16
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
17
|
+
THE SOFTWARE.
|
data/README.md
ADDED
data/lib/fix/protocol.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
#require 'treetop'
|
2
|
+
|
3
|
+
require 'fix/protocol/version'
|
4
|
+
require 'fix/protocol/message'
|
5
|
+
require 'fix/protocol/message_class_mapping'
|
6
|
+
require 'fix/protocol/parse_failure'
|
7
|
+
|
8
|
+
#
|
9
|
+
# Main Fix namespace
|
10
|
+
#
|
11
|
+
module Fix
|
12
|
+
|
13
|
+
#
|
14
|
+
# Main protocol namespace
|
15
|
+
#
|
16
|
+
module Protocol
|
17
|
+
|
18
|
+
#
|
19
|
+
# Parses a string into a Fix::Protocol::Message instance
|
20
|
+
#
|
21
|
+
# @param str [String] A FIX message string
|
22
|
+
# @return [Fix::Protocol::Message] A +Fix::Protocol::Message+ instance, or a +Fix::Protocol::ParseFailure+ in case of failure
|
23
|
+
#
|
24
|
+
def self.parse(str)
|
25
|
+
errors = []
|
26
|
+
msg_type = str.match(/^8\=[^\x01]+\x019\=[^\x01]+\x0135\=([^\x01]+)\x01/)
|
27
|
+
|
28
|
+
unless str.match(/^8\=[^\x01]+\x019\=[^\x01]+\x0135\=[^\x01]+\x01.+10\=[^\x01]+\x01/)
|
29
|
+
FP::ParseFailure.new("Malformed message <#{str}>")
|
30
|
+
else
|
31
|
+
|
32
|
+
klass = MessageClassMapping.get(msg_type[1])
|
33
|
+
|
34
|
+
unless klass
|
35
|
+
errors << "Unknown message type <#{msg_type[1]}>"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Check message length
|
39
|
+
length = str.gsub(/10\=[^\x01]+\x01$/, '').gsub(/^8\=[^\x01]+\x019\=([^\x01]+)\x01/, '').length
|
40
|
+
if length != $1.to_i
|
41
|
+
errors << "Incorrect body length"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Check checksum
|
45
|
+
checksum = str.match(/10\=([^\x01]+)\x01/)[1]
|
46
|
+
if checksum != ('%03d' % (str.gsub(/10\=[^\x01]+\x01/, '').bytes.inject(&:+) % 256))
|
47
|
+
errors << "Incorrect checksum"
|
48
|
+
end
|
49
|
+
|
50
|
+
if errors.empty?
|
51
|
+
msg = klass.parse(str)
|
52
|
+
|
53
|
+
if msg.valid?
|
54
|
+
msg
|
55
|
+
else
|
56
|
+
FP::ParseFailure.new(msg.errors)
|
57
|
+
end
|
58
|
+
else
|
59
|
+
FP::ParseFailure.new(errors)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Alias the +Fix::Protocol+ namespace to +FP+ if possible
|
66
|
+
#
|
67
|
+
def self.alias_namespace!
|
68
|
+
Object.const_set(:FP, Protocol) unless Object.const_defined?(:FP)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
Fix::Protocol.alias_namespace!
|
75
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'fix/protocol/type_conversions'
|
2
|
+
|
3
|
+
module Fix
|
4
|
+
module Protocol
|
5
|
+
|
6
|
+
#
|
7
|
+
# A FIX message field
|
8
|
+
#
|
9
|
+
class Field
|
10
|
+
|
11
|
+
include TypeConversions
|
12
|
+
|
13
|
+
@@attrs = [:tag, :name, :default, :type, :required, :parse_failure, :mapping]
|
14
|
+
@@attrs.each { |attr| attr_accessor(attr) }
|
15
|
+
|
16
|
+
def initialize(node)
|
17
|
+
@@attrs.each { |attr| node[attr] && send("#{attr}=", node[attr]) }
|
18
|
+
self.value ||= (default.is_a?(Proc) ? default.call(self) : default)
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Returns the field as a message fragment
|
23
|
+
#
|
24
|
+
# @return [String] A message fragment terminated by the separator byte
|
25
|
+
#
|
26
|
+
def dump
|
27
|
+
"#{tag}=#{@value}\x01"
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Parses the value for this field from the beginning of the string passed as parameter
|
32
|
+
# and return the rest of the string. The field value is assigned to the +@value+ instance variable
|
33
|
+
#
|
34
|
+
# @param str [String] A string starting with the field to parse
|
35
|
+
# @return [String] The same string with the field stripped off
|
36
|
+
#
|
37
|
+
def parse(str)
|
38
|
+
if str.match(/^#{tag}\=([^\x01]+)\x01/)
|
39
|
+
@value = $1
|
40
|
+
str.gsub(/^[^\x01]+\x01/, '')
|
41
|
+
elsif required
|
42
|
+
self.parse_failure = "Expected <#{str}> to start with a <#{tag}=...|> required field"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Returns the type-casted value of the field, according to its type or mapping definition
|
48
|
+
#
|
49
|
+
# @return [Object] The type-casted value
|
50
|
+
#
|
51
|
+
def value
|
52
|
+
to_type(@value)
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Assigns a typed value to the field, it is cast according to its type or mapping definition
|
57
|
+
#
|
58
|
+
# @param v [Object] An object of the defined type for this field
|
59
|
+
#
|
60
|
+
def value=(v)
|
61
|
+
@value = from_type(v)
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Returns the string representing this value as it would appear in a FIX message without
|
66
|
+
# any kind of type conversion or mapping
|
67
|
+
#
|
68
|
+
# @return [String] The raw field value
|
69
|
+
#
|
70
|
+
def raw_value
|
71
|
+
@value
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Assigns a string directly to the field value without type casting or mapping it
|
76
|
+
#
|
77
|
+
# @param v [String] The string value to assign
|
78
|
+
#
|
79
|
+
def raw_value=(v)
|
80
|
+
@value = v
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# Returns the errors for this field, if any
|
85
|
+
#
|
86
|
+
# @return [Array] The errors for this field
|
87
|
+
#
|
88
|
+
def errors
|
89
|
+
if required && !@value
|
90
|
+
"Missing value for <#{name}> field"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Performs the actual mapping or type casting by converting an object to a string
|
96
|
+
# or a symbol to a mapped string
|
97
|
+
#
|
98
|
+
# @param obj [Object] The mapping key or object to convert
|
99
|
+
# @return [String] The FIX field value
|
100
|
+
#
|
101
|
+
def from_type(obj)
|
102
|
+
if obj && type && !mapping
|
103
|
+
send("dump_#{type}", obj)
|
104
|
+
elsif obj && mapping && mapping.has_key?(obj)
|
105
|
+
mapping[obj]
|
106
|
+
else
|
107
|
+
obj
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Maps a string to an object or a mapped symbol
|
113
|
+
#
|
114
|
+
# @param str [String] The string to cast or map
|
115
|
+
# @return [Object] An object of the defined type or a mapped symbol
|
116
|
+
#
|
117
|
+
def to_type(str)
|
118
|
+
if str && type && !mapping
|
119
|
+
send("parse_#{type}", str)
|
120
|
+
elsif str && mapping && mapping.values.map(&:to_s).include?(str)
|
121
|
+
mapping.find { |k,v| v.to_s == str.to_s }[0]
|
122
|
+
else
|
123
|
+
str
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'fix/protocol/grammar_extensions/message'
|
2
|
+
|
3
|
+
module Fix
|
4
|
+
grammar Fix
|
5
|
+
|
6
|
+
rule main
|
7
|
+
message <GrammarExtensions::Message>
|
8
|
+
end
|
9
|
+
|
10
|
+
rule message
|
11
|
+
header body trailer
|
12
|
+
end
|
13
|
+
|
14
|
+
rule header
|
15
|
+
begin_string body_length msg_type field 4..4
|
16
|
+
end
|
17
|
+
|
18
|
+
rule body
|
19
|
+
field*
|
20
|
+
end
|
21
|
+
|
22
|
+
rule trailer
|
23
|
+
checksum_tag equal value soh
|
24
|
+
end
|
25
|
+
|
26
|
+
rule begin_string
|
27
|
+
"8" equal value soh
|
28
|
+
end
|
29
|
+
|
30
|
+
rule body_length
|
31
|
+
"9" equal value soh
|
32
|
+
end
|
33
|
+
|
34
|
+
rule msg_type
|
35
|
+
"35" equal value soh
|
36
|
+
end
|
37
|
+
|
38
|
+
rule sender_comp_id
|
39
|
+
"49" equal value soh
|
40
|
+
end
|
41
|
+
|
42
|
+
rule target_comp_id
|
43
|
+
"56" equal value soh
|
44
|
+
end
|
45
|
+
|
46
|
+
rule msg_seq_num
|
47
|
+
"34" equal value soh
|
48
|
+
end
|
49
|
+
|
50
|
+
rule sending_time
|
51
|
+
"52" equal value soh
|
52
|
+
end
|
53
|
+
|
54
|
+
rule field
|
55
|
+
tag equal value soh
|
56
|
+
end
|
57
|
+
|
58
|
+
rule tag
|
59
|
+
!"10=" [1-9] [0-9]*
|
60
|
+
end
|
61
|
+
|
62
|
+
rule checksum_tag
|
63
|
+
"10"
|
64
|
+
end
|
65
|
+
|
66
|
+
rule equal
|
67
|
+
"="
|
68
|
+
end
|
69
|
+
|
70
|
+
rule value
|
71
|
+
[^\x01]+
|
72
|
+
end
|
73
|
+
|
74
|
+
rule soh
|
75
|
+
[\x01]
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Fix
|
2
|
+
|
3
|
+
#
|
4
|
+
# Namespace for Treetop grammar extensions
|
5
|
+
#
|
6
|
+
module GrammarExtensions
|
7
|
+
|
8
|
+
#
|
9
|
+
# Extends the message component to return the header, body,
|
10
|
+
# message type, and checksum from an AST
|
11
|
+
#
|
12
|
+
module Message
|
13
|
+
|
14
|
+
#
|
15
|
+
# Returns the FIX message header as a fields array
|
16
|
+
#
|
17
|
+
# @return <Array> The message header fields
|
18
|
+
#
|
19
|
+
def header
|
20
|
+
unless @header
|
21
|
+
hdr = elements[0].elements
|
22
|
+
last_fields = hdr.pop
|
23
|
+
|
24
|
+
@header = hdr.map do |e|
|
25
|
+
[ e.elements[0].text_value.to_i, e.elements[2].text_value ]
|
26
|
+
end
|
27
|
+
|
28
|
+
last_fields.elements.inject(@header) do |h, e|
|
29
|
+
h << [ e.elements[0].text_value.to_i, e.elements[2].text_value ]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
@header
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Returns the FIX message body as a fields array
|
38
|
+
#
|
39
|
+
# @return <Array> The message body fields
|
40
|
+
#
|
41
|
+
def body
|
42
|
+
@fields ||= elements[1].elements.map do |e|
|
43
|
+
[ e.elements[0].text_value.to_i, e.elements[2].text_value ]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Returns the FIX message type code
|
49
|
+
#
|
50
|
+
# @return <String> The message type code
|
51
|
+
#
|
52
|
+
def msg_type
|
53
|
+
header.find { |f| f[0] == 35 }[1]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'polyglot'
|
2
|
+
require 'treetop'
|
3
|
+
|
4
|
+
require 'fix/protocol/message_part'
|
5
|
+
require 'fix/protocol/repeating_message_part'
|
6
|
+
require 'fix/protocol/message_header'
|
7
|
+
|
8
|
+
module Fix
|
9
|
+
module Protocol
|
10
|
+
|
11
|
+
#
|
12
|
+
# Represents an instance of a FIX message
|
13
|
+
#
|
14
|
+
class Message < MessagePart
|
15
|
+
|
16
|
+
part :header, klass: MessageHeader
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
super
|
20
|
+
header.msg_type = MessageClassMapping.reverse_get(self.class)
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Dumps this message as a FIX protocol message, it will automatically
|
25
|
+
# calculate the body length and and checksum
|
26
|
+
#
|
27
|
+
# @return [String] The FIX message
|
28
|
+
#
|
29
|
+
def dump
|
30
|
+
if valid?
|
31
|
+
dumped = super
|
32
|
+
header.body_length = dumped.gsub(/^8=[^\x01]+\x019=[^\x01]+\x01/, '').length
|
33
|
+
dumped = super
|
34
|
+
"#{dumped}10=#{'%03d' % (dumped.bytes.inject(&:+) % 256)}\x01"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Whether this instance is ready to be dumped as a valid FIX message
|
40
|
+
#
|
41
|
+
# @return [Boolean] Whether there are errors present for this instance
|
42
|
+
#
|
43
|
+
def valid?
|
44
|
+
(errors.nil? || errors.empty?) && parse_failure.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'fix/protocol/messages'
|
2
|
+
|
3
|
+
module Fix
|
4
|
+
module Protocol
|
5
|
+
|
6
|
+
#
|
7
|
+
# Maps the FIX message type codes to message classes
|
8
|
+
#
|
9
|
+
module MessageClassMapping
|
10
|
+
|
11
|
+
# The actual code <-> class mapping
|
12
|
+
MAPPING = {
|
13
|
+
'0' => :heartbeat,
|
14
|
+
'A' => :logon,
|
15
|
+
'1' => :test_request,
|
16
|
+
'2' => :resend_request,
|
17
|
+
'3' => :reject,
|
18
|
+
'5' => :logout,
|
19
|
+
'V' => :market_data_request
|
20
|
+
}
|
21
|
+
|
22
|
+
#
|
23
|
+
# Returns the message class associated to a message code
|
24
|
+
#
|
25
|
+
# @param msg_type [Integer] The FIX message type code
|
26
|
+
# @return [Class] The FIX message class
|
27
|
+
#
|
28
|
+
def self.get(msg_type)
|
29
|
+
Messages.const_get(camelcase(MAPPING[msg_type])) if MAPPING.has_key?(msg_type)
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Returns the message code associated to a message class
|
34
|
+
#
|
35
|
+
# @param klass [Class] The FIX message class
|
36
|
+
# @return [Integer] The FIX message type code
|
37
|
+
#
|
38
|
+
def self.reverse_get(klass)
|
39
|
+
key = klass.name.split('::').last.gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase.to_sym
|
40
|
+
MAPPING.find { |p| p[1] == key }[0]
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Formats a symbol as a proper class name
|
45
|
+
#
|
46
|
+
# @param s [Symbol] A name to camelcase
|
47
|
+
# @return [Symbol] A camelcased class name
|
48
|
+
#
|
49
|
+
def self.camelcase(s)
|
50
|
+
s.to_s.split(' ').map { |str| str.split('_') }.flatten.map(&:capitalize).join.to_sym
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Mark all the message classes for autoloading
|
55
|
+
#
|
56
|
+
MAPPING.values.each do |klass|
|
57
|
+
Messages.autoload(camelcase(klass), "fix/protocol/messages/#{klass}")
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'fix/protocol/message_part'
|
2
|
+
|
3
|
+
module Fix
|
4
|
+
module Protocol
|
5
|
+
|
6
|
+
#
|
7
|
+
# The standard FIX message header
|
8
|
+
#
|
9
|
+
class MessageHeader < MessagePart
|
10
|
+
|
11
|
+
field :version, tag: 8, required: true, default: 'FIX.4.4'
|
12
|
+
field :body_length, tag: 9
|
13
|
+
field :msg_type, tag: 35, required: true
|
14
|
+
field :sender_comp_id, tag: 49, required: true
|
15
|
+
field :target_comp_id, tag: 56, required: true
|
16
|
+
field :msg_seq_num, tag: 34, required: true, type: :integer
|
17
|
+
field :sending_time, tag: 52, required: true, type: :timestamp, default: proc { Time.now }
|
18
|
+
|
19
|
+
#
|
20
|
+
# Returns the errors relevant to the message header
|
21
|
+
#
|
22
|
+
# @return [Array<String>] The errors on the message header
|
23
|
+
#
|
24
|
+
def errors
|
25
|
+
if version == 'FIX.4.4'
|
26
|
+
super
|
27
|
+
else
|
28
|
+
[super, "Unsupported version: <#{version}>"].flatten
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'fix/protocol/field'
|
2
|
+
|
3
|
+
module Fix
|
4
|
+
module Protocol
|
5
|
+
|
6
|
+
#
|
7
|
+
# Basic building block for messages. Message parts can define fields, sub-parts and collections
|
8
|
+
#
|
9
|
+
class MessagePart
|
10
|
+
|
11
|
+
attr_accessor :parse_failure, :name
|
12
|
+
|
13
|
+
def initialize(opts = {})
|
14
|
+
self.name = opts[:name]
|
15
|
+
self.class.structure.each { |node| initialize_node(node) }
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Inherits the +@structure+ class instance variable but allows
|
20
|
+
# the child to modify it without impacting the parent, this allows
|
21
|
+
# for message structure refinement
|
22
|
+
#
|
23
|
+
def self.inherited(klass)
|
24
|
+
klass.send(:instance_variable_set, :@structure, structure.dup)
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Dumps this message part as a FIX message fragment
|
29
|
+
#
|
30
|
+
# @return [String] A FIX message fragment
|
31
|
+
#
|
32
|
+
def dump
|
33
|
+
nodes.map(&:dump).join
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Parses a full or partial FIX message string into the message part nodes
|
38
|
+
#
|
39
|
+
# @return [String] The string part that wasn't consumed during the parsing
|
40
|
+
#
|
41
|
+
def parse(str)
|
42
|
+
left_to_parse = str
|
43
|
+
|
44
|
+
nodes.each do |node|
|
45
|
+
unless parse_failure
|
46
|
+
left_to_parse = node.parse(left_to_parse)
|
47
|
+
self.parse_failure = node.parse_failure
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
left_to_parse
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Initializes a node depending on its type, this is called when initializing a message
|
56
|
+
# part instance by the constructor. Usually one node is initialized for each structure element
|
57
|
+
#
|
58
|
+
def initialize_node(node)
|
59
|
+
if node[:node_type] == :part
|
60
|
+
nodes << node[:klass].new(node)
|
61
|
+
elsif node[:node_type] == :field
|
62
|
+
nodes << FP::Field.new(node)
|
63
|
+
elsif node[:node_type] == :collection
|
64
|
+
nodes << FP::RepeatingMessagePart.new(node)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# The message part nodes, they'll be either a +FP::Field+, an +FP::RepeatingMessagePart+ or a +FP::MessagePart+
|
70
|
+
#
|
71
|
+
# @return [Array] The nodes for this message part
|
72
|
+
#
|
73
|
+
def nodes
|
74
|
+
@nodes ||= []
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Searches the immediate hierarchy by node name to return the requested node
|
79
|
+
#
|
80
|
+
# @param n [String] The node name to look for
|
81
|
+
# @return [Object] The found node, if any
|
82
|
+
#
|
83
|
+
def node_for_name(n)
|
84
|
+
nodes.find { |node| node.name.to_s == n.to_s }
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Class-level shortcut to directly parse an instance of a specific message part subclass
|
89
|
+
#
|
90
|
+
def self.parse(str)
|
91
|
+
instce = new
|
92
|
+
instce.parse(str)
|
93
|
+
instce
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Defines a collection as a part of this message part, collections typically have a counter and a repeating element
|
98
|
+
#
|
99
|
+
# @param name [String] The collection name, this will be the name of a dynamically created accessor on the message part
|
100
|
+
# @param opts [Hash] The required options are +:counter_tag+ and +:klass+
|
101
|
+
#
|
102
|
+
def self.collection(name, opts = {})
|
103
|
+
structure << { node_type: :collection, name: name, counter_tag: opts[:counter_tag], klass: opts[:klass] }
|
104
|
+
|
105
|
+
define_method(name) do
|
106
|
+
node_for_name(name)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Defines a reusable message part as element of this particular class
|
112
|
+
#
|
113
|
+
# @param name [String] The part name, this will be the name of a dynamically created accessor on the message part
|
114
|
+
# @param opts [Hash] Options hash
|
115
|
+
#
|
116
|
+
def self.part(name, opts = {})
|
117
|
+
structure << { node_type: :part, name: name }.merge(opts)
|
118
|
+
|
119
|
+
define_method(name) do
|
120
|
+
node_for_name(name)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Defines a field as part of the structure for this class
|
126
|
+
#
|
127
|
+
# @param name [String] The field name, this will be the base name of a set of methods created on the class instances
|
128
|
+
# @param opts [Hash] Options hash
|
129
|
+
#
|
130
|
+
def self.field(name, opts)
|
131
|
+
structure << { node_type: :field, name: name }.merge(opts)
|
132
|
+
|
133
|
+
# Getter
|
134
|
+
define_method(name) do
|
135
|
+
node_for_name(name).value
|
136
|
+
end
|
137
|
+
|
138
|
+
# Setter
|
139
|
+
define_method("#{name}=") do |val|
|
140
|
+
node_for_name(name).value = val
|
141
|
+
end
|
142
|
+
|
143
|
+
if opts[:mapping]
|
144
|
+
define_method("raw_#{name}") do
|
145
|
+
node_for_name(name).raw_value
|
146
|
+
end
|
147
|
+
|
148
|
+
define_method("raw_#{name}=") do |val|
|
149
|
+
node_for_name(name).raw_value = val
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# Returns this message part class' structure
|
156
|
+
#
|
157
|
+
# @return [Array] The message structure
|
158
|
+
#
|
159
|
+
def self.structure
|
160
|
+
@structure ||= []
|
161
|
+
end
|
162
|
+
|
163
|
+
#
|
164
|
+
# Returns the errors for this instance
|
165
|
+
#
|
166
|
+
# @return [Array] The errors for this instance
|
167
|
+
#
|
168
|
+
def errors
|
169
|
+
[nodes.map(&:errors), nodes.map(&:parse_failure)].flatten.compact
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Fix
|
2
|
+
module Protocol
|
3
|
+
module Messages
|
4
|
+
|
5
|
+
#
|
6
|
+
# An Instrument component, see http://www.onixs.biz/fix-dictionary/4.4/compBlock_Instrument.html
|
7
|
+
#
|
8
|
+
class Instrument < MessagePart
|
9
|
+
|
10
|
+
field :symbol, tag: 55, required: true
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Fix
|
2
|
+
module Protocol
|
3
|
+
module Messages
|
4
|
+
|
5
|
+
#
|
6
|
+
# A FIX logon message
|
7
|
+
#
|
8
|
+
class Logon < Message
|
9
|
+
|
10
|
+
field :encrypt_method, tag: 98, required: true, type: :integer, default: 0
|
11
|
+
field :heart_bt_int, tag: 108, required: true, type: :integer, default: 30
|
12
|
+
field :username, tag: 553, required: true
|
13
|
+
field :reset_seq_num_flag, tag: 141
|
14
|
+
|
15
|
+
#
|
16
|
+
# Returns the logon-specific errors
|
17
|
+
#
|
18
|
+
# @return [Array] The error messages
|
19
|
+
#
|
20
|
+
def errors
|
21
|
+
e = []
|
22
|
+
e << "Encryption is not supported, the transport level should handle it" unless (encrypt_method == 0)
|
23
|
+
e << "Heartbeat interval should be between 10 and 60 seconds" unless heart_bt_int && heart_bt_int <= 60 && heart_bt_int >= 10
|
24
|
+
[super, e].flatten
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'fix/protocol/messages/md_entry_type'
|
2
|
+
require 'fix/protocol/messages/instrument'
|
3
|
+
|
4
|
+
module Fix
|
5
|
+
module Protocol
|
6
|
+
module Messages
|
7
|
+
|
8
|
+
#
|
9
|
+
# A FIX market data request message
|
10
|
+
#
|
11
|
+
class MarketDataRequest < Message
|
12
|
+
|
13
|
+
# SUBSCRIPTION_TYPES = {
|
14
|
+
# snapshot: 1,
|
15
|
+
# updates: 2,
|
16
|
+
# unsubscribe: 3
|
17
|
+
# }
|
18
|
+
#
|
19
|
+
# MKT_DPTH_TYPES = {
|
20
|
+
# full: 0,
|
21
|
+
# top: 1
|
22
|
+
# }
|
23
|
+
#
|
24
|
+
# UPDATE_TYPES = {
|
25
|
+
# full: 0,
|
26
|
+
# incremental: 1
|
27
|
+
# }
|
28
|
+
|
29
|
+
field :md_req_id, tag: 262, required: true
|
30
|
+
field :subscription_request_type, tag: 263, required: true, type: :integer #, mapping: SUBSCRIPTION_TYPES
|
31
|
+
field :market_depth, tag: 264, required: true, type: :integer #, mapping: MKT_DPTH_TYPES
|
32
|
+
field :md_update_type, tag: 265, required: true, type: :integer #, mapping: UPDATE_TYPES
|
33
|
+
|
34
|
+
collection :md_entry_types, counter_tag: 267, klass: FP::Messages::MdEntryType
|
35
|
+
collection :instruments, counter_tag: 146, klass: FP::Messages::Instrument
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Fix
|
2
|
+
module Protocol
|
3
|
+
module Messages
|
4
|
+
|
5
|
+
#
|
6
|
+
# A market data entry type component, see http://www.onixs.biz/fix-dictionary/4.4/tagNum_269.html
|
7
|
+
#
|
8
|
+
class MdEntryType < MessagePart
|
9
|
+
|
10
|
+
#
|
11
|
+
# The MD entry type mapping
|
12
|
+
#
|
13
|
+
MD_ENTRY_TYPES = {
|
14
|
+
bid: 0,
|
15
|
+
ask: 1,
|
16
|
+
offer: 1,
|
17
|
+
trade: 2,
|
18
|
+
index: 3,
|
19
|
+
open: 4,
|
20
|
+
close: 5,
|
21
|
+
settlement: 6,
|
22
|
+
high: 7,
|
23
|
+
low: 8,
|
24
|
+
vwap: 9,
|
25
|
+
volume: 'B'
|
26
|
+
}
|
27
|
+
|
28
|
+
field :md_entry_type, tag: 269, required: true, mapping: MD_ENTRY_TYPES
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Fix
|
2
|
+
module Protocol
|
3
|
+
module Messages
|
4
|
+
|
5
|
+
#
|
6
|
+
# A FIX session reject message
|
7
|
+
#
|
8
|
+
class Reject < Message
|
9
|
+
|
10
|
+
has_field :ref_seq_num, tag: 45, position: 0, required: true, type: :integer
|
11
|
+
has_field :text, tag: 58, position: 1, required: true
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Fix
|
2
|
+
module Protocol
|
3
|
+
module Messages
|
4
|
+
|
5
|
+
#
|
6
|
+
# A FIX resend request message, see http://www.onixs.biz/fix-dictionary/4.4/msgType_2_2.html
|
7
|
+
#
|
8
|
+
class ResendRequest < Message
|
9
|
+
|
10
|
+
field :begin_seq_no, tag: 7, required: true, type: :integer
|
11
|
+
field :end_seq_no, tag: 16, required: true, type: :integer, default: 0
|
12
|
+
|
13
|
+
#
|
14
|
+
# Returns the logon-specific errors
|
15
|
+
#
|
16
|
+
# @return [Array] The error messages
|
17
|
+
#
|
18
|
+
def errors
|
19
|
+
e = []
|
20
|
+
e << "EndSeqNo must either be 0 (inifinity) or be >= BeginSeqNo" unless (end_seq_no.zero? || (end_seq_no >= begin_seq_no))
|
21
|
+
[super, e].flatten
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Fix
|
2
|
+
module Protocol
|
3
|
+
|
4
|
+
#
|
5
|
+
# Represents a failure to parse a message, the +errors+ collection
|
6
|
+
# should contain the specific error messages
|
7
|
+
#
|
8
|
+
class ParseFailure
|
9
|
+
|
10
|
+
attr_accessor :errors, :message
|
11
|
+
|
12
|
+
def initialize(errs, msg = nil)
|
13
|
+
@errors = [errs].flatten.compact
|
14
|
+
@message = msg
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'fix/protocol/message_part'
|
2
|
+
|
3
|
+
module Fix
|
4
|
+
module Protocol
|
5
|
+
|
6
|
+
#
|
7
|
+
# Represents a portion of a FIX message consisting of a similar repeating structure
|
8
|
+
# preceded by a counter element
|
9
|
+
#
|
10
|
+
class RepeatingMessagePart < MessagePart
|
11
|
+
|
12
|
+
extend Forwardable
|
13
|
+
def_delegators :nodes, :[], :first, :last, :length, :size, :each
|
14
|
+
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
attr_accessor :counter_tag, :element_klass
|
18
|
+
|
19
|
+
def initialize(opts = {})
|
20
|
+
@counter_tag = opts[:counter_tag]
|
21
|
+
@element_klass = opts[:klass]
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Appends a new blank node as the last element of the collection
|
27
|
+
#
|
28
|
+
def build
|
29
|
+
nodes << element_klass.new
|
30
|
+
|
31
|
+
if block_given?
|
32
|
+
yield(nodes.last)
|
33
|
+
end
|
34
|
+
|
35
|
+
nodes.last
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Dumps the message fragment as the set of dumped elements preceded by the relevant counter tag
|
40
|
+
#
|
41
|
+
# @return [String] The part of the initial string that didn't get consumed during the parsing
|
42
|
+
#
|
43
|
+
def dump
|
44
|
+
"#{counter_tag}=#{length}\x01#{super}"
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Parses an arbitrary number of nodes according to the found counter tag
|
49
|
+
#
|
50
|
+
def parse(str)
|
51
|
+
if str.match(/^#{counter_tag}\=([^\x01]+)\x01/)
|
52
|
+
len = $1.to_i
|
53
|
+
@nodes = []
|
54
|
+
len.times { @nodes << element_klass.new }
|
55
|
+
super(str.gsub(/^#{counter_tag}\=[^\x01]+\x01/, ''))
|
56
|
+
else
|
57
|
+
self.parse_failure = "Expected <#{str}> to begin with <#{counter_tag}=...|>"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Fix
|
2
|
+
module Protocol
|
3
|
+
|
4
|
+
#
|
5
|
+
# Defines helper methods to convert to and from FIX data types
|
6
|
+
#
|
7
|
+
module TypeConversions
|
8
|
+
|
9
|
+
#
|
10
|
+
# Parses a FIX-formatted timestamp into a Time instance, milliseconds are discarded
|
11
|
+
#
|
12
|
+
# @param str [String] A FIX-formatted timestamp
|
13
|
+
# @return [Time] An UTC date and time
|
14
|
+
#
|
15
|
+
def parse_timestamp(str)
|
16
|
+
if m = str.match(/\A([0-9]{4})([0-9]{2})([0-9]{2})-([0-9]{2}):([0-9]{2}):([0-9]{2})(.[0-9]{3})?\Z/)
|
17
|
+
elts = m.to_a.map(&:to_i)
|
18
|
+
Time.new(elts[1], elts[2], elts[3], elts[4], elts[5], elts[6], 0)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Outputs a DateTime object as a FIX-formatted timestamp
|
24
|
+
#
|
25
|
+
# @param dt [DateTime] An UTC date and time
|
26
|
+
# @return [String] A FIX-formatted timestamp
|
27
|
+
#
|
28
|
+
def dump_timestamp(dt)
|
29
|
+
dt.utc.strftime('%Y%m%d-%H:%M:%S')
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Parses an integer
|
34
|
+
#
|
35
|
+
# @param str [String] An integer as a string
|
36
|
+
# @return [Fixnum] The parsed integer
|
37
|
+
#
|
38
|
+
def parse_integer(str)
|
39
|
+
str && str.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Dumps an integer to a string
|
44
|
+
#
|
45
|
+
# @param i [Fixnum] An integer
|
46
|
+
# @return [String] It's string representation
|
47
|
+
#
|
48
|
+
def dump_integer(i)
|
49
|
+
i.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fix-protocol
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David François
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: yard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: redcarpet
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: treetop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: polyglot
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: FIX message classes, parsing and generation
|
126
|
+
email:
|
127
|
+
- david.francois@paymium.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- lib/fix/protocol/field.rb
|
133
|
+
- lib/fix/protocol/grammar.treetop
|
134
|
+
- lib/fix/protocol/grammar_extensions/message.rb
|
135
|
+
- lib/fix/protocol/message.rb
|
136
|
+
- lib/fix/protocol/message_class_mapping.rb
|
137
|
+
- lib/fix/protocol/message_header.rb
|
138
|
+
- lib/fix/protocol/message_part.rb
|
139
|
+
- lib/fix/protocol/messages/heartbeat.rb
|
140
|
+
- lib/fix/protocol/messages/instrument.rb
|
141
|
+
- lib/fix/protocol/messages/logon.rb
|
142
|
+
- lib/fix/protocol/messages/logout.rb
|
143
|
+
- lib/fix/protocol/messages/market_data_request.rb
|
144
|
+
- lib/fix/protocol/messages/md_entry_type.rb
|
145
|
+
- lib/fix/protocol/messages/reject.rb
|
146
|
+
- lib/fix/protocol/messages/resend_request.rb
|
147
|
+
- lib/fix/protocol/messages/test_request.rb
|
148
|
+
- lib/fix/protocol/messages.rb
|
149
|
+
- lib/fix/protocol/parse_failure.rb
|
150
|
+
- lib/fix/protocol/repeating_message_part.rb
|
151
|
+
- lib/fix/protocol/type_conversions.rb
|
152
|
+
- lib/fix/protocol/version.rb
|
153
|
+
- lib/fix/protocol.rb
|
154
|
+
- LICENSE
|
155
|
+
- README.md
|
156
|
+
homepage: https://github.com/paymium/fix-protocol
|
157
|
+
licenses: []
|
158
|
+
metadata: {}
|
159
|
+
post_install_message:
|
160
|
+
rdoc_options: []
|
161
|
+
require_paths:
|
162
|
+
- lib
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - '>='
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - '>='
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 1.3.6
|
173
|
+
requirements: []
|
174
|
+
rubyforge_project:
|
175
|
+
rubygems_version: 2.0.3
|
176
|
+
signing_key:
|
177
|
+
specification_version: 4
|
178
|
+
summary: FIX message classes, parsing and generation
|
179
|
+
test_files: []
|
180
|
+
has_rdoc:
|