tradingrobotdsl 0.0.1 → 0.0.2
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/History.txt +11 -0
- data/lib/dsl.rb +426 -156
- data/spec/contract_spec.rb +42 -42
- data/spec/robot_spec.rb +77 -77
- metadata +2 -2
data/History.txt
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
== 0.0.2 / 2007-11-22
|
2
|
+
* New features
|
3
|
+
* Plugin system for smarting
|
4
|
+
* IB online and history quotes plugins
|
5
|
+
* IB order placement plugin
|
6
|
+
* Techical analysis plugin new functions: SMA and EMA
|
7
|
+
* Bug fixes
|
8
|
+
* OpenTick plugins temporary removed for testing
|
9
|
+
|
10
|
+
* Bug fixes
|
11
|
+
* duration command
|
1
12
|
== 0.0.1 / 2007-11-18
|
2
13
|
|
3
14
|
* Birthday!
|
data/lib/dsl.rb
CHANGED
@@ -1,173 +1,443 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
require '
|
8
|
-
|
9
|
-
#add_stubs(@user, :password= => nil, :password_confirmation= => nil, :new_password= => nil)
|
1
|
+
|
2
|
+
=begin
|
3
|
+
Trading robot Domain specific language implementation
|
4
|
+
=end
|
5
|
+
|
6
|
+
|
7
|
+
require 'thread'
|
8
|
+
|
10
9
|
module DSL
|
11
10
|
|
12
|
-
|
11
|
+
VERSION='0.0.2'
|
12
|
+
|
13
13
|
class NoDataException < RuntimeError
|
14
14
|
attr :deep_level
|
15
15
|
def initialize(deep_level)
|
16
16
|
@deep_level = deep_level
|
17
17
|
end
|
18
|
-
end # class NoDataException
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
18
|
+
end # class NoDataException
|
19
|
+
|
20
|
+
############################### class Plugin ##$################################
|
21
|
+
|
22
|
+
module Plugin
|
23
|
+
def initialize(robot)
|
24
|
+
@robot = robot
|
25
|
+
@connection = nil
|
26
|
+
self.class.message.each do |name, command|
|
27
|
+
if name == :input
|
28
|
+
command.each do |name, command|
|
29
|
+
@robot.register_command(name, lambda {|*args| self.send(command, *args)}, self)
|
30
|
+
end
|
31
|
+
else
|
32
|
+
@robot.register_command(name, lambda {|*args| self.send(command, *args)})
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def run(hash)
|
38
|
+
raise "you must define own run method in subclasses"
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_params(hash)
|
42
|
+
hash.each do |param|
|
43
|
+
self.instance_variable_set "@" + param[0].to_s, param[1]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end # module Plugin
|
47
|
+
|
48
|
+
module IBPluginFunctions
|
49
|
+
# make structure Contract for ib-ruby module
|
50
|
+
def make_contract(contract)
|
51
|
+
IB::Datatypes::Contract.new(:symbol => contract.symbol.to_s, :exchange => contract.exchange.to_s,
|
52
|
+
:expiry => contract.expiry, :currency => contract.currency, :sec_type => contract.sec_type)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class IBHistoryPlugin
|
57
|
+
include Plugin
|
58
|
+
include IBPluginFunctions
|
59
|
+
@@ticker_id = 1
|
60
|
+
|
61
|
+
def self.message
|
62
|
+
{
|
63
|
+
:input => { :history => :run }
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def run(*args)
|
68
|
+
hash=args[0]
|
69
|
+
$:.push(File.dirname(__FILE__) + "/../../ib-ruby/lib/")
|
70
|
+
require 'ib'
|
71
|
+
require 'datatypes'
|
72
|
+
require 'messages'
|
73
|
+
require 'timeout'
|
74
|
+
|
75
|
+
@duration = 0
|
76
|
+
parse_params(hash)
|
77
|
+
@id = []
|
78
|
+
@connection = IB::IB.new({:client_id => "quotes"})
|
79
|
+
#@connection.dispatch(IB::OutgoingMessages::SetServerLoglevel.new(:loglevel => 5))
|
80
|
+
@connection.subscribe(IB::IncomingMessages::HistoricalData, lambda {|msg| on_quote(msg)})
|
81
|
+
@mutex = Mutex.new
|
82
|
+
@condition = ConditionVariable.new
|
83
|
+
end # run
|
84
|
+
|
85
|
+
def subscribe_contract(contract)
|
86
|
+
ib_contract = make_contract(contract)
|
87
|
+
msg = IB::OutgoingMessages::RequestHistoricalData.new(:ticker_id => @@ticker_id += 1,
|
88
|
+
:contract => ib_contract, :end_date_time => @end, :duration => @duration,
|
89
|
+
:bar_size => @size, :what_to_show => @show, :use_RTH => 1, :format_date => 2)
|
90
|
+
@id[@@ticker_id] = contract.queue
|
91
|
+
@connection.dispatch(msg)
|
92
|
+
end # subscribe_contract
|
93
|
+
|
94
|
+
def query()
|
95
|
+
while !@id.inject(true) {|s,x| s &&=x.nil? } do
|
96
|
+
#|| (Contract.list_by_value.inject(true) {|s,x| s &&= (x.queue.size >= @deep)})
|
97
|
+
@mutex.synchronize do
|
98
|
+
begin
|
99
|
+
@condition.wait(@mutex) unless @id.inject(true) {|s,x| x == nil ? s : s &&= x.size >= @robot.deep_level}
|
100
|
+
yield
|
101
|
+
rescue NoDataException => e
|
102
|
+
@robot.deep_level = e.deep_level
|
103
|
+
retry
|
104
|
+
end
|
105
|
+
end # synchronize
|
106
|
+
end # while
|
107
|
+
end
|
108
|
+
|
109
|
+
def on_quote(msg)
|
110
|
+
queue = @id[msg.data[:req_id]]
|
111
|
+
cond = @id.inject(true) {|s,x| x == nil ? s : s &&= x.size >= @robot.deep_level}
|
112
|
+
@mutex.synchronize do
|
113
|
+
msg.data[:history].each do |q|
|
114
|
+
queue.push q
|
115
|
+
end
|
116
|
+
end
|
117
|
+
@condition.signal if !cond && @id.inject(true) {|s,x| x == nil ? s : s &&= x.size >= @robot.deep_level}
|
118
|
+
end
|
119
|
+
end # class IBHistoryPlugin
|
120
|
+
|
121
|
+
class IBOnlinePlugin
|
122
|
+
include Plugin
|
123
|
+
include IBPluginFunctions
|
124
|
+
@@ticker_id = 1
|
125
|
+
|
126
|
+
def self.message
|
127
|
+
{
|
128
|
+
:input => { :online => :run }
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
def run(*args)
|
133
|
+
hash=args[0]
|
134
|
+
$:.push(File.dirname(__FILE__) + "/../../ib-ruby/lib/")
|
135
|
+
require 'ib'
|
136
|
+
require 'datatypes'
|
137
|
+
require 'messages'
|
138
|
+
require 'timeout'
|
139
|
+
|
140
|
+
@duration = 0
|
141
|
+
parse_params(hash)
|
142
|
+
@id = []
|
143
|
+
@data = []
|
144
|
+
@connection = IB::IB.new({:client_id => "quotes"})
|
145
|
+
#@connection.dispatch(IB::OutgoingMessages::SetServerLoglevel.new(:loglevel => 5))
|
146
|
+
@connection.subscribe(IB::IncomingMessages::TickPrice, lambda {|msg| on_quote(msg)})
|
147
|
+
# @connection.subscribe(IB::IncomingMessages::TickSize, lambda {|msg| on_trade(msg)})
|
148
|
+
@mutex = Mutex.new
|
149
|
+
@condition = ConditionVariable.new
|
150
|
+
end
|
151
|
+
|
152
|
+
def subscribe_contract(contract)
|
153
|
+
ib_contract = make_contract(contract)
|
154
|
+
msg = IB::OutgoingMessages::RequestMarketData.new(:ticker_id => @@ticker_id += 1,
|
155
|
+
:contract => ib_contract)
|
156
|
+
@id[@@ticker_id] = contract
|
157
|
+
@data[@@ticker_id] = {:open => 0, :high => 0, :low => 1e6, :close => 0}
|
158
|
+
@connection.dispatch(msg)
|
159
|
+
end
|
160
|
+
|
161
|
+
def filter
|
162
|
+
while true
|
163
|
+
begin
|
164
|
+
timeout(@duration) do
|
165
|
+
# fake loop
|
166
|
+
while true do
|
167
|
+
end
|
168
|
+
end
|
169
|
+
rescue TimeoutError
|
170
|
+
@id.size.times do |request_id|
|
171
|
+
next if @id[request_id].nil?
|
172
|
+
@id[request_id].queue.push :open => @data[request_id][:open],
|
173
|
+
:high => @data[request_id][:high],
|
174
|
+
:low => @data[request_id][:low],
|
175
|
+
:close => @data[request_id][:close]
|
176
|
+
@data[request_id][:open] = @data[request_id][:high] = @data[request_id][:low] = @data[request_id][:close]
|
177
|
+
end # times
|
178
|
+
@condition.signal if @id.inject(true) {|s,x| x == nil ? s : s &&= x.queue.size >= @robot.deep_level}
|
179
|
+
end # begin
|
180
|
+
end # while
|
181
|
+
end
|
182
|
+
|
183
|
+
def on_quote(msg)
|
184
|
+
return unless msg.data[:type] == :last
|
185
|
+
@mutex.synchronize do
|
186
|
+
case @duration
|
187
|
+
when 0:
|
188
|
+
@id[msg.data[:ticker_id]].queue.push :price => msg.data[:price].to_f, :size => msg.data[:size].to_i
|
189
|
+
@condition.signal
|
190
|
+
else
|
191
|
+
id = msg.data[:ticker_id]
|
192
|
+
price = msg.data[:price].to_f
|
193
|
+
@data[id][:open] = price if @data[id][:open] == 0
|
194
|
+
@data[id][:high] = price if price > @data[id][:high]
|
195
|
+
@data[id][:low] = price if price < @data[id][:low]
|
196
|
+
@data[id][:close] = price
|
197
|
+
end
|
198
|
+
end
|
199
|
+
# p "#{@id[msg.data[:ticker_id]].symbol.to_s} #{msg.data[:size]} #{msg.data[:price].to_digits}"
|
200
|
+
end
|
201
|
+
|
202
|
+
def query(*args)
|
203
|
+
Thread.abort_on_exception = true
|
204
|
+
@filter_thread = Thread.new do
|
205
|
+
self.filter
|
206
|
+
end
|
207
|
+
while true
|
208
|
+
@mutex.synchronize do
|
209
|
+
begin
|
210
|
+
@condition.wait(@mutex) unless @id.inject(true) {|s,x| x == nil ? s : s &&= x.queue.size >= @robot.deep_level}
|
211
|
+
yield
|
212
|
+
rescue NoDataException => e
|
213
|
+
@robot.deep_level = e.deep_level
|
214
|
+
retry
|
215
|
+
end
|
216
|
+
end # synchronize
|
217
|
+
end # while
|
218
|
+
end
|
219
|
+
|
220
|
+
end # IBOnlinePlugin
|
221
|
+
|
222
|
+
class IBOrderPlugin
|
223
|
+
include Plugin
|
224
|
+
include IBPluginFunctions
|
225
|
+
@@order_id = 1
|
226
|
+
|
227
|
+
def self.message
|
228
|
+
{ :order => :order }
|
229
|
+
end
|
230
|
+
|
231
|
+
def initialize(robot)
|
232
|
+
$:.push(File.dirname(__FILE__) + "/../../ib-ruby/lib/")
|
233
|
+
require 'ib'
|
234
|
+
require 'datatypes'
|
235
|
+
require 'messages'
|
236
|
+
|
237
|
+
super(robot)
|
238
|
+
@connection = IB::IB.new({:client_id => "orders"})
|
239
|
+
#@connection.dispatch(IB::OutgoingMessages::SetServerLoglevel.new(:loglevel => 5))
|
240
|
+
@connection.subscribe(IB::IncomingMessages::NextValidID, lambda {|msg| @@order_id = msg.data[:order_id]})
|
241
|
+
@robot.class.const_set "BUY", "BUY"
|
242
|
+
@robot.class.const_set "SELL", "SELL"
|
243
|
+
@robot.class.const_set "MKT", "MKT"
|
244
|
+
@robot.class.const_set "LMT", "LMT"
|
245
|
+
end # hash
|
246
|
+
|
247
|
+
# usage CONTRACT, ORDER_SIDE (BUY, SELL), ORDER_TYPE (MKT, LMT), SIZE, LIMIT_PRICE
|
248
|
+
def order(*args)
|
249
|
+
contract = args.shift
|
250
|
+
side = args.shift
|
251
|
+
type = args.shift
|
252
|
+
size = args.shift
|
253
|
+
order = {
|
254
|
+
:id => 1,
|
255
|
+
:action => side,
|
256
|
+
:order_type => type,
|
257
|
+
:total_quantity => size,
|
258
|
+
:transmit => "1"
|
259
|
+
}
|
260
|
+
case type
|
261
|
+
when "LMT":
|
262
|
+
order[:limit_price] = args.shift
|
263
|
+
end
|
264
|
+
@connection.dispatch(IB::OutgoingMessages::PlaceOrder.new(
|
265
|
+
:order_id => @@order_id += 1,
|
266
|
+
:contract => make_contract(contract),
|
267
|
+
:order => IB::Datatypes::Order.new(order)
|
268
|
+
))
|
269
|
+
end
|
270
|
+
end # IBOrderPlugin
|
271
|
+
|
272
|
+
class TAPlugin
|
273
|
+
include Plugin
|
274
|
+
def self.message
|
275
|
+
{ :sma => :sma,
|
276
|
+
:ema => :ema
|
277
|
+
}
|
278
|
+
end
|
279
|
+
|
280
|
+
def sma(contract, period)
|
281
|
+
raise NoDataException.new(period) if @robot.deep_level < period
|
282
|
+
(@robot.deep_level-period..@robot.deep_level-1).inject(0) {|s,x| s+=contract.price(x)} / period.to_f
|
283
|
+
end # def sma
|
284
|
+
|
285
|
+
def ema(contract, period, step = @robot.deep_level - period)
|
286
|
+
raise NoDataException.new(period) if @robot.deep_level < period
|
287
|
+
case period
|
288
|
+
when 1
|
289
|
+
return contract.queue[step][:close].to_f
|
290
|
+
else
|
291
|
+
k = 2 / ( 1 + period )
|
292
|
+
ema_prev = ema(contract, period - 1, step + 1)
|
293
|
+
return ema_prev + k * ( contract.price(step) - ema_prev )
|
294
|
+
end
|
295
|
+
end # def ema
|
296
|
+
end
|
297
|
+
|
298
|
+
############################### class Contract ##################################
|
299
|
+
|
300
|
+
class Contract
|
301
|
+
@@list_by_symbol={}
|
302
|
+
attr_reader :symbol, :exchange, :expiry, :sec_type, :currency, :queue
|
303
|
+
attr_accessor :request_id
|
304
|
+
|
305
|
+
def initialize(contract_symbol)
|
306
|
+
@symbol = contract_symbol
|
307
|
+
@queue = []
|
308
|
+
end
|
309
|
+
|
310
|
+
def self.list_by_symbol
|
311
|
+
@@list_by_symbol
|
312
|
+
end
|
313
|
+
|
314
|
+
def self.find(contract_symbol)
|
315
|
+
@@list_by_symbol[contract_symbol]=Contract.new(contract_symbol) unless @@list_by_symbol.key?(contract_symbol)
|
316
|
+
@@list_by_symbol[contract_symbol]
|
317
|
+
end
|
318
|
+
|
319
|
+
def method_missing(m, *args)
|
320
|
+
if m.to_s =~ /[A-Z]+/
|
321
|
+
@exchange = m unless instance_variable_defined?(:@exchange)
|
322
|
+
@sec_type = "STK" unless instance_variable_defined?(:@sec_type) || (args.size > 0 && @sec_type = args.shift)
|
323
|
+
@currency = "USD" unless instance_variable_defined?(:@currency) || (args.size > 0 && @currency = args.shift)
|
324
|
+
@expiry = "" unless instance_variable_defined?(:@expiry) || (args.size > 0 && @expiry = args.shift)
|
325
|
+
self
|
326
|
+
else
|
327
|
+
super
|
328
|
+
end
|
49
329
|
end
|
50
330
|
|
51
|
-
def price
|
52
|
-
@queue
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
331
|
+
def price(lag = 0)
|
332
|
+
f = @queue[lag]
|
333
|
+
return if f.nil?
|
334
|
+
if f.key?(:price)
|
335
|
+
f[:price]
|
336
|
+
else
|
337
|
+
f[:close].to_f #FIXME
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
62
341
|
end # class Contract
|
63
342
|
|
64
|
-
############################### class Robot ##################################
|
65
|
-
|
66
|
-
class Robot
|
67
|
-
attr_accessor :
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
@logged = false
|
343
|
+
############################### class Robot ##################################
|
344
|
+
|
345
|
+
class Robot
|
346
|
+
attr_accessor :deep_level
|
347
|
+
@@commands = {}
|
348
|
+
|
349
|
+
def initialize
|
72
350
|
@mutex = Mutex.new
|
73
351
|
@condition = ConditionVariable.new
|
74
|
-
@
|
75
|
-
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def query_history(*args)
|
116
|
-
@ot.subscribe(OpenTick::IncomingMessages::HistoryStreamResponse, lambda {|msg| on_history_quote(msg)})
|
117
|
-
args.each do |contract|
|
118
|
-
contract.subscribe(@ot, self)
|
119
|
-
end
|
120
|
-
begin
|
121
|
-
while (Contract.list_by_name.values.inject(0) {|s,x| s+=x.request_id } > 0) ||
|
122
|
-
(args.inject(true) {|s,x| s &&= (x.queue.size >= @deep)}) do
|
123
|
-
@mutex.synchronize do
|
124
|
-
begin
|
125
|
-
@condition.wait(@mutex) unless args.inject(true) {|s,x| s &&= (x.queue.size >= @deep)}
|
126
|
-
yield
|
127
|
-
rescue NoDataException => e
|
128
|
-
@deep = e.deep_level
|
129
|
-
retry
|
130
|
-
end
|
131
|
-
args.each {|contract|
|
132
|
-
contract.queue.shift
|
133
|
-
}
|
134
|
-
end # synchronize
|
135
|
-
end # while
|
136
|
-
end # begin
|
137
|
-
end # query_history
|
138
|
-
|
139
|
-
def avg(contract, period)
|
140
|
-
raise unless contract.is_a?(DSL::Contract)
|
141
|
-
raise unless period.is_a?(Fixnum)
|
142
|
-
raise NoDataException.new(period) if @deep < period
|
143
|
-
contract.queue[0..period-1].inject(0) {|s,x| s+=x[:closePrice]} / period
|
144
|
-
end
|
145
|
-
end # class Robot
|
146
|
-
|
147
|
-
end # module DSL
|
352
|
+
@deep_level = 1
|
353
|
+
end
|
354
|
+
|
355
|
+
def commands
|
356
|
+
@@commands
|
357
|
+
end
|
358
|
+
|
359
|
+
def method_missing(m, *args)
|
360
|
+
raise "unknown command #{m}" unless @@commands.key?(m)
|
361
|
+
@@commands[m].call(*args)
|
362
|
+
end
|
363
|
+
|
364
|
+
# load plugins
|
365
|
+
def plugin(*args)
|
366
|
+
args.each { |plugin| plugin.new(self) }
|
367
|
+
end # plugin
|
368
|
+
|
369
|
+
# query quotes from input_plugin
|
370
|
+
def query(*args)
|
371
|
+
args.each do |contract|
|
372
|
+
@input_plugin.subscribe_contract(contract)
|
373
|
+
end
|
374
|
+
@input_plugin.query do
|
375
|
+
yield
|
376
|
+
args.each {|contract|
|
377
|
+
contract.queue.shift
|
378
|
+
}
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# register plugin command in global commands list
|
383
|
+
def register_command(command, body, input_plugin=nil)
|
384
|
+
@@commands[command] = body
|
385
|
+
@input_plugin = input_plugin if input_plugin
|
386
|
+
end
|
387
|
+
end # class Robot
|
388
|
+
|
389
|
+
end # module DSL
|
148
390
|
|
149
391
|
############################### module Kernel ################################
|
150
|
-
|
151
|
-
module Kernel
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
end # Kernel
|
392
|
+
|
393
|
+
module Kernel
|
394
|
+
def robot(&blk)
|
395
|
+
DSL::Robot.new.instance_eval(&blk)
|
396
|
+
end
|
397
|
+
end # Kernel
|
157
398
|
|
158
399
|
############################### class Object #################################
|
159
|
-
|
160
|
-
def Object.const_missing(m)
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
end
|
400
|
+
|
401
|
+
def Object.const_missing(m)
|
402
|
+
if m.to_s =~ /[A-Z]+/
|
403
|
+
# FIXME workaround
|
404
|
+
case m
|
405
|
+
when :FUT
|
406
|
+
"FUT"
|
407
|
+
when :STK
|
408
|
+
"STK"
|
409
|
+
when :USD
|
410
|
+
"USD"
|
411
|
+
else
|
412
|
+
return DSL::Robot.const_get(m) if DSL::Robot.const_defined?(m)
|
413
|
+
DSL::Contract::find(m)
|
414
|
+
end
|
415
|
+
else
|
416
|
+
super
|
417
|
+
end
|
418
|
+
end # Object.const_missing
|
419
|
+
|
420
|
+
if $0 == __FILE__
|
421
|
+
robot do
|
422
|
+
|
423
|
+
# loading plugins, first must be plugin for query
|
424
|
+
plugin DSL::IBHistoryPlugin, DSL::IBOrderPlugin, DSL::TAPlugin
|
425
|
+
# input plugin configuration
|
426
|
+
#online :duration => 10
|
427
|
+
history :end => "20071101 00:00:00 GMT", :duration => "1 D",
|
428
|
+
:size => 10, :show => "TRADES"
|
429
|
+
# contract specifications
|
430
|
+
YM.ECBOT FUT, USD, 200712
|
431
|
+
# main loop
|
432
|
+
query YM do
|
433
|
+
if (ema YM, 9)/(ema YM, 25) - 1 > 0.05
|
434
|
+
order YM, BUY, MKT, 1
|
435
|
+
end
|
436
|
+
if (ewa YM, 9)/(ewa YM, 25) - 1 < -0.05
|
437
|
+
order YM, SELL, MKT, 1
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
|
442
|
+
end # robot
|
443
|
+
end
|
data/spec/contract_spec.rb
CHANGED
@@ -1,32 +1,32 @@
|
|
1
|
-
require File.join(File.expand_path(File.dirname(__FILE__)),"helper")
|
2
|
-
require "#{LIB_DIR}/dsl"
|
3
|
-
|
4
|
-
describe DSL::Contract, "before created" do
|
5
|
-
it "contract list_by_name should be empty" do
|
6
|
-
DSL::Contract.list_by_name.should be_empty
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__)),"helper")
|
2
|
+
require "#{LIB_DIR}/dsl"
|
3
|
+
|
4
|
+
describe DSL::Contract, "before created" do
|
5
|
+
it "contract list_by_name should be empty" do
|
6
|
+
DSL::Contract.list_by_name.should be_empty
|
7
7
|
end
|
8
8
|
it "contract list_by_id should be empty" do
|
9
9
|
DSL::Contract.list_by_id.should be_empty
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
describe DSL::Contract, "when created" do
|
14
|
-
before :each do
|
15
|
-
@contract = robot(nil) do
|
16
|
-
MSFT.Q
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
it "should exists" do
|
21
|
-
@contract.should_not be_nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe DSL::Contract, "when created" do
|
14
|
+
before :each do
|
15
|
+
@contract = robot(nil) do
|
16
|
+
MSFT.Q
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should exists" do
|
21
|
+
@contract.should_not be_nil
|
22
22
|
end
|
23
23
|
|
24
24
|
it "should be instance of DSL::Contract" do
|
25
|
-
@contract.should be_an_instance_of(DSL::Contract)
|
25
|
+
@contract.should be_an_instance_of(DSL::Contract)
|
26
26
|
end
|
27
|
-
|
28
|
-
it "should set contract name" do
|
29
|
-
@contract.name.should eql(:MSFT)
|
27
|
+
|
28
|
+
it "should set contract name" do
|
29
|
+
@contract.name.should eql(:MSFT)
|
30
30
|
end
|
31
31
|
|
32
32
|
it "should set exchange" do
|
@@ -37,23 +37,23 @@ describe DSL::Contract, "when created" do
|
|
37
37
|
pending "ror2ru question" do
|
38
38
|
@contract.public_methods.should_not include("subscribe")
|
39
39
|
end
|
40
|
-
end
|
41
|
-
|
42
|
-
after :each do
|
43
|
-
DSL::Contract.list_by_name.clear
|
44
|
-
DSL::Contract.list_by_id.clear
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
describe DSL::Contract, "in history mode" do
|
49
|
-
it "should create method alias subscribe->subscribe_query" do
|
50
|
-
@robot = DSL::Robot.new(nil)
|
51
|
-
@robot.history :duration => 1, :from => Time.now - 2, :to => Time.now
|
52
|
-
DSL::Contract.new("MSFT").public_methods.should include("subscribe")
|
53
|
-
end
|
54
|
-
|
55
|
-
it "should subscribe to history quotes" do
|
56
|
-
@robot = DSL::Robot.new(nil)
|
57
|
-
@ot = mock("OpenTick")
|
58
|
-
end
|
59
|
-
end
|
40
|
+
end
|
41
|
+
|
42
|
+
after :each do
|
43
|
+
DSL::Contract.list_by_name.clear
|
44
|
+
DSL::Contract.list_by_id.clear
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe DSL::Contract, "in history mode" do
|
49
|
+
it "should create method alias subscribe->subscribe_query" do
|
50
|
+
@robot = DSL::Robot.new(nil)
|
51
|
+
@robot.history :duration => 1, :from => Time.now - 2, :to => Time.now
|
52
|
+
DSL::Contract.new("MSFT").public_methods.should include("subscribe")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should subscribe to history quotes" do
|
56
|
+
@robot = DSL::Robot.new(nil)
|
57
|
+
@ot = mock("OpenTick")
|
58
|
+
end
|
59
|
+
end
|
data/spec/robot_spec.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
|
-
# robot_spec.rb
|
2
|
-
|
3
|
-
require File.join(File.expand_path(File.dirname(__FILE__)),"helper")
|
4
|
-
require "#{LIB_DIR}/dsl"
|
5
|
-
|
6
|
-
describe Kernel, "robot method" do
|
7
|
-
it "should change context" do
|
8
|
-
robot(nil) {self}.should be_a_kind_of(DSL::Robot)
|
9
|
-
end
|
10
|
-
end # robot method
|
11
|
-
|
12
|
-
describe DSL::Robot, "when created" do
|
13
|
-
before :each do
|
14
|
-
@ot = mock("OpenTick")
|
15
|
-
@robot = DSL::Robot.new(@ot)
|
16
|
-
end
|
17
|
-
|
18
|
-
it "should exists" do
|
19
|
-
@robot.should_not be_nil
|
20
|
-
end
|
1
|
+
# robot_spec.rb
|
2
|
+
|
3
|
+
require File.join(File.expand_path(File.dirname(__FILE__)),"helper")
|
4
|
+
require "#{LIB_DIR}/dsl"
|
5
|
+
|
6
|
+
describe Kernel, "robot method" do
|
7
|
+
it "should change context" do
|
8
|
+
robot(nil) {self}.should be_a_kind_of(DSL::Robot)
|
9
|
+
end
|
10
|
+
end # robot method
|
11
|
+
|
12
|
+
describe DSL::Robot, "when created" do
|
13
|
+
before :each do
|
14
|
+
@ot = mock("OpenTick")
|
15
|
+
@robot = DSL::Robot.new(@ot)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should exists" do
|
19
|
+
@robot.should_not be_nil
|
20
|
+
end
|
21
21
|
|
22
22
|
it "should be instance of DSL::Robot" do
|
23
23
|
@robot.should be_an_instance_of(DSL::Robot)
|
@@ -30,59 +30,59 @@ describe DSL::Robot, "when created" do
|
|
30
30
|
it "should not have query method alias" do
|
31
31
|
@robot.public_methods.should_not include("query")
|
32
32
|
end
|
33
|
-
|
34
|
-
it "should connect to opentick server on login" do
|
35
|
-
@ot.should_receive(:login).
|
36
|
-
once.
|
37
|
-
with(an_instance_of(String), an_instance_of(String))
|
38
|
-
|
33
|
+
|
34
|
+
it "should connect to opentick server on login" do
|
35
|
+
@ot.should_receive(:login).
|
36
|
+
once.
|
37
|
+
with(an_instance_of(String), an_instance_of(String))
|
38
|
+
|
39
39
|
@robot.login("test_opentick", "123123")
|
40
|
-
|
41
|
-
@robot.login?.should be_true
|
42
|
-
end
|
43
|
-
end # when created
|
44
|
-
|
45
|
-
describe DSL::Robot, "in history mode" do
|
46
|
-
before :each do
|
47
|
-
@robot = DSL::Robot.new(nil)
|
48
|
-
end
|
49
|
-
|
50
|
-
it "should set @duration, @from and @to" do
|
51
|
-
now = Time.now
|
52
|
-
@robot.history :duration => 300, :from => now-24*3600, :to => now
|
53
|
-
@robot.duration.should eql(300)
|
54
|
-
@robot.from.should eql(now-24*3600)
|
55
|
-
@robot.to.should eql(now)
|
56
|
-
end
|
57
|
-
|
58
|
-
it "should raise exception on parameter absence" do
|
59
|
-
lambda { @robot.history({}) }.should raise_error
|
60
|
-
end
|
61
|
-
|
62
|
-
it "should check @to - @from > @duration" do
|
63
|
-
lambda {@robot.history :duration => 300, :from => 1, :to => 2 }.should raise_error
|
64
|
-
end
|
65
|
-
|
66
|
-
it "should create method alias query->query_history" do
|
67
|
-
@robot.history :duration => 300, :from => Time.now-24*3600, :to => Time.now
|
68
|
-
@robot.public_methods.should include("query")
|
69
|
-
end
|
70
|
-
end # in history mode
|
71
|
-
|
72
|
-
describe DSL::Robot, "on query command" do
|
73
|
-
before :each do
|
40
|
+
|
41
|
+
@robot.login?.should be_true
|
42
|
+
end
|
43
|
+
end # when created
|
44
|
+
|
45
|
+
describe DSL::Robot, "in history mode" do
|
46
|
+
before :each do
|
47
|
+
@robot = DSL::Robot.new(nil)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should set @duration, @from and @to" do
|
51
|
+
now = Time.now
|
52
|
+
@robot.history :duration => 300, :from => now-24*3600, :to => now
|
53
|
+
@robot.duration.should eql(300)
|
54
|
+
@robot.from.should eql(now-24*3600)
|
55
|
+
@robot.to.should eql(now)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should raise exception on parameter absence" do
|
59
|
+
lambda { @robot.history({}) }.should raise_error
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should check @to - @from > @duration" do
|
63
|
+
lambda {@robot.history :duration => 300, :from => 1, :to => 2 }.should raise_error
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should create method alias query->query_history" do
|
67
|
+
@robot.history :duration => 300, :from => Time.now-24*3600, :to => Time.now
|
68
|
+
@robot.public_methods.should include("query")
|
69
|
+
end
|
70
|
+
end # in history mode
|
71
|
+
|
72
|
+
describe DSL::Robot, "on query command" do
|
73
|
+
before :each do
|
74
74
|
@ot = mock("OpenTick")
|
75
|
-
@contract = mock("DSL::Contract")
|
76
|
-
@robot = robot(@ot) do
|
77
|
-
history :duration => 1, :from => 2, :to => 5
|
78
|
-
query @contract do
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
it "should subscribe history quotes" do
|
84
|
-
@ot.should_receive(:subscribe).once
|
85
|
-
#with(OpenTick::IncomingMessages::HistoryStreamResponse, an_instance_of(Proc))
|
75
|
+
@contract = mock("DSL::Contract")
|
76
|
+
@robot = robot(@ot) do
|
77
|
+
history :duration => 1, :from => 2, :to => 5
|
78
|
+
query @contract do
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should subscribe history quotes" do
|
84
|
+
@ot.should_receive(:subscribe).once
|
85
|
+
#with(OpenTick::IncomingMessages::HistoryStreamResponse, an_instance_of(Proc))
|
86
86
|
end
|
87
87
|
|
88
88
|
it "should send subscribe message to contracts" do
|
@@ -94,9 +94,9 @@ describe DSL::Robot, "on query command" do
|
|
94
94
|
after :each do
|
95
95
|
DSL::Contract.list_by_name.clear
|
96
96
|
DSL::Contract.list_by_id.clear
|
97
|
-
end
|
98
|
-
end # on query command
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
97
|
+
end
|
98
|
+
end # on query command
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
|
|
3
3
|
specification_version: 1
|
4
4
|
name: tradingrobotdsl
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2007-11-
|
6
|
+
version: 0.0.2
|
7
|
+
date: 2007-11-24 00:00:00 +03:00
|
8
8
|
summary: Domain specific language for automated trading (robots)
|
9
9
|
require_paths:
|
10
10
|
- lib
|