tradingrobotdsl 0.0.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.
- data/History.txt +5 -0
- data/Manifest.txt +8 -0
- data/README.txt +79 -0
- data/Rakefile +18 -0
- data/lib/dsl.rb +173 -0
- data/spec/contract_spec.rb +59 -0
- data/spec/helper.rb +4 -0
- data/spec/robot_spec.rb +102 -0
- metadata +64 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
tradingrobotdsl: Domain specific language for automated trading (robot)
|
2
|
+
Copyright (C) 2007 by Timur Adigamov (timur at adigamov dot com)
|
3
|
+
http://www.tradeindexfuture.com
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Domain specific language library for programming trading robots.
|
8
|
+
Quotes get from opentick server (through opentick-ruby module)
|
9
|
+
Order place to Trader Workstation socket API (through ib-ruby module)
|
10
|
+
|
11
|
+
This is an alpha test quality release. I specifically DO NOT recommend
|
12
|
+
that it be used for live trading under any circumstances. It has not
|
13
|
+
been tested. I am releasing it in the hopes that the many eyes of the
|
14
|
+
community will help with enhancing it, so that we all will have a
|
15
|
+
robust and reliable library to use.
|
16
|
+
|
17
|
+
== FEATURES/PROBLEMS:
|
18
|
+
|
19
|
+
* get quotes from opentick servers
|
20
|
+
* simple average indicator realized :)
|
21
|
+
* order placement (buy/sell) doesn't work
|
22
|
+
|
23
|
+
== SYNOPSIS:
|
24
|
+
|
25
|
+
robot do
|
26
|
+
login 'test_opentick', '123123'
|
27
|
+
history :duration => 300, :from => Time.now-10*24*3600, :to => Time.now
|
28
|
+
query MSFT do
|
29
|
+
if (avg MSFT, 9)/(avg MSFT, 25) - 1 > 0.5
|
30
|
+
puts "buy MSFT"
|
31
|
+
end
|
32
|
+
if (avg MSFT, 9)/(avg MSFT, 25) - 1 < - 0.5
|
33
|
+
puts "sell MSFT"
|
34
|
+
end
|
35
|
+
end # query
|
36
|
+
end # robot
|
37
|
+
|
38
|
+
== REQUIREMENTS:
|
39
|
+
|
40
|
+
* ib-ruby
|
41
|
+
* opentick-ruby
|
42
|
+
|
43
|
+
== INSTALL:
|
44
|
+
|
45
|
+
* sudo gem install ib-ruby
|
46
|
+
* sudo gem install opentick-ruby
|
47
|
+
* sudo gem install tradingrobotdsl
|
48
|
+
|
49
|
+
== LICENSE:
|
50
|
+
|
51
|
+
This library is free software; you can redistribute it and/or modify
|
52
|
+
it under the terms of the GNU Lesser General Public License as
|
53
|
+
published by the Free Software Foundation; either version 2.1 of the
|
54
|
+
License, or (at your option) any later version.
|
55
|
+
|
56
|
+
This library is distributed in the hope that it will be useful, but
|
57
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
58
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
59
|
+
Lesser General Public License for more details.
|
60
|
+
|
61
|
+
You should have received a copy of the GNU Lesser General Public
|
62
|
+
License along with this library; if not, write to the Free Software
|
63
|
+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
64
|
+
02110-1301 USA
|
65
|
+
|
66
|
+
|
67
|
+
== Use At Your Own Risk
|
68
|
+
|
69
|
+
As the license indicates, this code is provided AS IS, WITH NO
|
70
|
+
WARRANTY WHATSOEVER, not even an implied warranty of merchantability
|
71
|
+
or fitness for a particular purpose.
|
72
|
+
|
73
|
+
*ANY USE YOU MAKE OF THIS CODE IS ENTIRELY AT YOUR OWN RISK.*
|
74
|
+
|
75
|
+
This code may contain any number of errors or bugs, both known and
|
76
|
+
unknown. Use of this code may result in monetary loss due to known or
|
77
|
+
unknown bugs and errors. In no event shall the author be liable or
|
78
|
+
responsible for any loss whatsoever, direct or indirect, that may
|
79
|
+
occur as a result of your use of this code.
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
require './lib/dsl.rb'
|
6
|
+
|
7
|
+
Hoe.new('tradingrobotdsl', DSL::VERSION) do |p|
|
8
|
+
p.rubyforge_name = 'tradingrobotdsl'
|
9
|
+
p.author = 'Timur Adigamov'
|
10
|
+
p.email = 'timur at adigamov dot com'
|
11
|
+
p.summary = 'Domain specific language for automated trading (robots)'
|
12
|
+
p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
|
13
|
+
p.url = "http://www.tradeindexfuture.com"
|
14
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
15
|
+
p.remote_rdoc_dir = ''
|
16
|
+
end
|
17
|
+
|
18
|
+
# vim: syntax=Ruby
|
data/lib/dsl.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
=begin
|
2
|
+
Trading robot Domain specific language implementation
|
3
|
+
=end
|
4
|
+
|
5
|
+
$:.push(File.dirname(__FILE__) + "/../../opentick-ruby/lib/")
|
6
|
+
|
7
|
+
require 'opentick.rb'
|
8
|
+
require 'thread'
|
9
|
+
#add_stubs(@user, :password= => nil, :password_confirmation= => nil, :new_password= => nil)
|
10
|
+
module DSL
|
11
|
+
|
12
|
+
VERSION='0.0.1'
|
13
|
+
class NoDataException < RuntimeError
|
14
|
+
attr :deep_level
|
15
|
+
def initialize(deep_level)
|
16
|
+
@deep_level = deep_level
|
17
|
+
end
|
18
|
+
end # class NoDataException
|
19
|
+
|
20
|
+
class Contract
|
21
|
+
@@list_by_name={}
|
22
|
+
@@list_by_id=[]
|
23
|
+
attr_reader :name, :exchange, :queue
|
24
|
+
attr_accessor :request_id
|
25
|
+
|
26
|
+
def initialize(contract_name, exchange=nil)
|
27
|
+
@name = contract_name
|
28
|
+
@exchange = exchange
|
29
|
+
@request_id = 0
|
30
|
+
@queue = []
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.list_by_name
|
34
|
+
@@list_by_name
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.list_by_id
|
38
|
+
@@list_by_id
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.find(contract_name)
|
42
|
+
@@list_by_name[contract_name]=Contract.new(contract_name) unless @@list_by_name.key?(contract_name)
|
43
|
+
@@list_by_name[contract_name]
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_missing(m, *args)
|
47
|
+
@exchange = m
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def price
|
52
|
+
@queue.first[:closePrice] unless @queue.first.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
def subscribe_history(ot, robot)
|
56
|
+
msg = OpenTick::OutgoingMessages::HistoryStreamRequest.new(:exchange => @exchange.to_s,
|
57
|
+
:ticker => @name.to_s, :from => robot.from.to_i, :to => robot.to.to_i, :type => 4, :interval => 1)
|
58
|
+
ot.dispatch(msg)
|
59
|
+
@request_id = msg.reqId
|
60
|
+
@@list_by_id[@request_id] = self
|
61
|
+
end
|
62
|
+
end # class Contract
|
63
|
+
|
64
|
+
############################### class Robot ##################################
|
65
|
+
|
66
|
+
class Robot
|
67
|
+
attr_accessor :duration, :from, :to, :deep
|
68
|
+
|
69
|
+
def initialize(opentick_connection)
|
70
|
+
@ot = opentick_connection
|
71
|
+
@logged = false
|
72
|
+
@mutex = Mutex.new
|
73
|
+
@condition = ConditionVariable.new
|
74
|
+
@deep = 1
|
75
|
+
end
|
76
|
+
|
77
|
+
def login(username, password)
|
78
|
+
@ot.login(username, password)
|
79
|
+
@logged = true
|
80
|
+
end # login
|
81
|
+
|
82
|
+
def login?
|
83
|
+
@logged
|
84
|
+
end
|
85
|
+
|
86
|
+
def history(options)
|
87
|
+
raise "parameters absence" unless options.key?(:duration) && options.key?(:from) && options.key?(:to)
|
88
|
+
raise "duration > to - from" unless options[:duration] < options[:to] - options[:from]
|
89
|
+
@duration = options[:duration]
|
90
|
+
@from = options[:from]
|
91
|
+
@to = options[:to]
|
92
|
+
class << self
|
93
|
+
alias_method :query, :query_history
|
94
|
+
end
|
95
|
+
DSL::Contract.class_eval {
|
96
|
+
alias_method :subscribe, :subscribe_history
|
97
|
+
}
|
98
|
+
end # history
|
99
|
+
|
100
|
+
def on_history_quote(msg)
|
101
|
+
contract = Contract.list_by_id[msg.reqId]
|
102
|
+
cond =Contract.list_by_name.values.inject(true) {|s,x| s &&= (x.queue.size >= @deep)}
|
103
|
+
@mutex.synchronize do
|
104
|
+
msg.data.each {|q|
|
105
|
+
if q[:type]==0 && q[:time].nil?
|
106
|
+
contract.request_id = 0
|
107
|
+
else
|
108
|
+
contract.queue.push q
|
109
|
+
end
|
110
|
+
}
|
111
|
+
@condition.signal if !cond && Contract.list_by_name.values.inject(true) {|s,x| s &&= (x.queue.size >= @deep)}
|
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
|
148
|
+
|
149
|
+
############################### module Kernel ################################
|
150
|
+
|
151
|
+
module Kernel
|
152
|
+
# change context
|
153
|
+
def robot(*args, &blk)
|
154
|
+
DSL::Robot.new(*args).instance_eval(&blk)
|
155
|
+
end
|
156
|
+
end # Kernel
|
157
|
+
|
158
|
+
############################### class Object #################################
|
159
|
+
|
160
|
+
def Object.const_missing(m)
|
161
|
+
DSL::Contract::find(m)
|
162
|
+
end # Object.const_missing
|
163
|
+
|
164
|
+
if $0 == __FILE__
|
165
|
+
robot OpenTick::OpenTick.new("feed1.opentick.com", 10010) do
|
166
|
+
login 'test_opentick', '123123'
|
167
|
+
history :duration => 300, :from => Time.now-10*24*3600, :to => Time.now
|
168
|
+
query GOOG.Q,CSCO.Q do
|
169
|
+
p avg GOOG.Q, 3
|
170
|
+
p avg CSCO.Q, 3
|
171
|
+
end # query
|
172
|
+
end # robot
|
173
|
+
end
|
@@ -0,0 +1,59 @@
|
|
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
|
+
end
|
8
|
+
it "contract list_by_id should be empty" do
|
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
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should be instance of DSL::Contract" do
|
25
|
+
@contract.should be_an_instance_of(DSL::Contract)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should set contract name" do
|
29
|
+
@contract.name.should eql(:MSFT)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should set exchange" do
|
33
|
+
@contract.exchange.should eql(:Q)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should not have subscribe method alias" do
|
37
|
+
pending "ror2ru question" do
|
38
|
+
@contract.public_methods.should_not include("subscribe")
|
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
|
data/spec/helper.rb
ADDED
data/spec/robot_spec.rb
ADDED
@@ -0,0 +1,102 @@
|
|
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
|
+
|
22
|
+
it "should be instance of DSL::Robot" do
|
23
|
+
@robot.should be_an_instance_of(DSL::Robot)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should not be logged in" do
|
27
|
+
@robot.login?.should be_false
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not have query method alias" do
|
31
|
+
@robot.public_methods.should_not include("query")
|
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
|
+
|
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
|
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))
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should send subscribe message to contracts" do
|
89
|
+
@contract.should_receive(:subscribe)
|
90
|
+
#once
|
91
|
+
#with(an_instance_of(OpenTick::IncomingMessages::HistoryStreamResponse), an_instance_of(Proc))
|
92
|
+
end
|
93
|
+
|
94
|
+
after :each do
|
95
|
+
DSL::Contract.list_by_name.clear
|
96
|
+
DSL::Contract.list_by_id.clear
|
97
|
+
end
|
98
|
+
end # on query command
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: tradingrobotdsl
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2007-11-19 00:00:00 +03:00
|
8
|
+
summary: Domain specific language for automated trading (robots)
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: timur at adigamov dot com
|
12
|
+
homepage: http://www.tradeindexfuture.com
|
13
|
+
rubyforge_project: tradingrobotdsl
|
14
|
+
description: "This is an alpha test quality release. I specifically DO NOT recommend that it be used for live trading under any circumstances. It has not been tested. I am releasing it in the hopes that the many eyes of the community will help with enhancing it, so that we all will have a robust and reliable library to use. == FEATURES/PROBLEMS: * get quotes from opentick servers * simple average indicator realized :) * order placement (buy/sell) doesn't work == SYNOPSIS: robot do login 'test_opentick', '123123' history :duration => 300, :from => Time.now-10*24*3600, :to => Time.now query MSFT do if (avg MSFT, 9)/(avg MSFT, 25) - 1 > 0.5 puts \"buy MSFT\" end if (avg MSFT, 9)/(avg MSFT, 25) - 1 < - 0.5 puts \"sell MSFT\" end end # query end # robot"
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Timur Adigamov
|
31
|
+
files:
|
32
|
+
- History.txt
|
33
|
+
- Manifest.txt
|
34
|
+
- README.txt
|
35
|
+
- Rakefile
|
36
|
+
- lib/dsl.rb
|
37
|
+
- spec/contract_spec.rb
|
38
|
+
- spec/helper.rb
|
39
|
+
- spec/robot_spec.rb
|
40
|
+
test_files: []
|
41
|
+
|
42
|
+
rdoc_options:
|
43
|
+
- --main
|
44
|
+
- README.txt
|
45
|
+
extra_rdoc_files:
|
46
|
+
- History.txt
|
47
|
+
- Manifest.txt
|
48
|
+
- README.txt
|
49
|
+
executables: []
|
50
|
+
|
51
|
+
extensions: []
|
52
|
+
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
dependencies:
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: hoe
|
58
|
+
version_requirement:
|
59
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 1.3.0
|
64
|
+
version:
|