cotcube-helpers 0.1.9.2 → 0.2.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
4
+
5
+ module Cotcube
6
+ module Helpers
7
+ # A proxyclient is a wrapper that allows communication with cotcube-orderproxy and cotcube-dataproxy. It fulfills
8
+ # registration and provides the opportunity to implement the logic to respond do events.
9
+ # (orderproxy and dataproxy are separate gems creating a layer between tws/ib-ruby and cotcube-based
10
+ # applications)
11
+ #
12
+ # NOTE: Whats here is a provisionally version
13
+ #
14
+ class DataClient # rubocop:disable Metrics/ClassLength
15
+ attr_reader :power, :ticksize, :multiplier, :average, :account
16
+
17
+ # The constructor takes a lot of arguments:
18
+ def initialize(
19
+ debug: false,
20
+ contract: ,
21
+ serverport: 24001,
22
+ serveraddr: '127.0.0.1',
23
+ client:,
24
+ bars: true,
25
+ ticks: false,
26
+ bar_size: 5,
27
+ spawn_timeout: 15
28
+ )
29
+ require 'json' unless Hash.new.respond_to? :to_json
30
+ require 'socket' unless defined? TCPSocket
31
+
32
+ puts 'PROXYCLIENT: Debug enabled' if @debug
33
+
34
+ @contract = contract.upcase
35
+ %w[debug serverport serveraddr client bars ticks bar_size].each {|var| eval("@#{var} = #{var}")}
36
+
37
+ @position = 0
38
+ @account = 0
39
+ @average = 0
40
+
41
+ exit_on_startup(':client must be in range 24001..24999') if @client.nil? || (@client / 1000 != 24) || (@client == 24_000)
42
+
43
+ res = send_command({ command: 'register', contract: @contract, date: @date,
44
+ ticks: @ticks, bars: @bars, bar_size: @bar_size })
45
+
46
+ # spawn_server has to be called separately after initialization.
47
+ print "Waiting #{spawn_timeout} seconds on server_thread to spawn..."
48
+ Thread.new do
49
+ begin
50
+ Timeout.timeout(spawn_timeout) { sleep(0.1) while @server_thread.nil? }
51
+ rescue Timeout::Error
52
+ puts 'Could not get server_thread, has :spawn_server been called?'
53
+ shutdown
54
+ end
55
+ end
56
+
57
+ unless res['error'].zero?
58
+ exit_on_startup("Unable to register on orderproxy, exiting")
59
+ end
60
+ end
61
+
62
+ def exit_on_startup(msg = '')
63
+ puts "Cannot startup client, exiting during startup: '#{msg}'"
64
+ shutdown
65
+ defined?(::IRB) ? (raise) : (exit 1)
66
+ end
67
+
68
+ def send_command(req)
69
+ req[:client_id] = @client
70
+ res = nil
71
+ puts "Connecting to #{@serveraddr}:#{@serverport} to send '#{req}'." if @debug
72
+
73
+ TCPSocket.open(@serveraddr, @serverport) do |proxy|
74
+ proxy.puts(req.to_json)
75
+ raw = proxy.gets
76
+ begin
77
+ res = JSON.parse(raw)
78
+ rescue StandardError
79
+ puts 'Error while parsing response'
80
+ return false
81
+ end
82
+ if @debug
83
+ # rubocop:disable Style/FormatStringToken, Style/FormatString
84
+ res.each do |k, v|
85
+ case v
86
+ when Array
87
+ (v.size < 2) ? puts(format '%-15s', "#{k}:") : print(format '%-15s', "#{k}:")
88
+ v.each_with_index { |x, i| i.zero? ? (puts x) : (puts " #{x}") }
89
+ else
90
+ puts "#{format '%-15s', "#{k}:"}#{v}"
91
+ end
92
+ end
93
+ # rubocop:enable Style/FormatStringToken, Style/FormatString
94
+ end
95
+ puts "ERROR on command: #{res['msg']}" unless res['error'].nil? or res['error'].zero?
96
+ end
97
+ puts res.to_s if @debug
98
+ res
99
+ end
100
+
101
+ # #shutdown ends the @server_thread and --if :close is set-- closes the current position attached to the client
102
+ def shutdown(close: true)
103
+ return if @shutdown
104
+ @shutdown = true
105
+
106
+ if @position.abs.positive? && close
107
+ send_command({ command: 'order', action: 'create', type: 'market',
108
+ side: (@position.positive? ? 'sell' : 'buy'), size: @position.abs })
109
+ end
110
+ sleep 3
111
+ result = send_command({ command: 'unregister' })
112
+ puts "FINAL ACCOUNT: #{@account}"
113
+ result['executions']&.each do |x|
114
+ x.delete('msg_type')
115
+ puts x.to_s
116
+ end
117
+ @server_thread.exit if @server_thread.respond_to? :exit
118
+ end
119
+
120
+ def spawn_server(
121
+ execution_proc: nil,
122
+ orderstate_proc: nil,
123
+ tick_proc: nil,
124
+ depth_proc: nil,
125
+ order_proc: nil,
126
+ bars_proc: nil
127
+ ) # rubocop:disable Metrics/MethodLength
128
+
129
+ %w[execution_proc orderstate_proc tick_proc depth_proc order_proc bars_proc].each {|var| eval("@#{var} = #{var}") }
130
+
131
+ if @bars
132
+ @bars_proc ||= lambda {|msg| puts msg.inspect }
133
+ end
134
+
135
+ if @ticks
136
+ @ticks_proc ||= lambda {|msg| puts msg.inspect }
137
+ end
138
+
139
+ if @shutdown
140
+ puts "Cannot spawn server on proxyclient that has been already shut down."
141
+ return
142
+ end
143
+ if @server_thread
144
+ puts "Cannot spawn server more than once."
145
+ return
146
+ end
147
+
148
+ @server_thread = Thread.new do # rubocop:disable Metrics/BlockLength
149
+ puts 'Spawning RECEIVER'
150
+ server = TCPServer.open(@serveraddr, @client)
151
+ loop do # rubocop:disable Metrics/BlockLength
152
+ Thread.start(server.accept) do |client| # rubocop:disable Metrics/BlockLength
153
+ while (response = client.gets)
154
+ response = JSON.parse(response)
155
+
156
+ case response['msg_type']
157
+
158
+ when 'alert'
159
+ case response['code']
160
+ when 2104
161
+ puts 'ALERT: data farm connection resumed __ignored__'.light_black
162
+ when 2108
163
+ puts 'ALERT: data farm connection suspended __ignored__'.light_black
164
+ when 2109
165
+ # Order Event Warning:Attribute 'Outside Regular Trading Hours' is ignored
166
+ # based on the order type and destination. PlaceOrder is now being processed.
167
+ puts 'ALERT: outside_rth __ignored__'.light_black
168
+ when 2100
169
+ puts 'ALERT: Account_info unsubscribed __ignored__'.light_black
170
+ when 202
171
+ puts 'ALERT: order cancelled'
172
+ else
173
+ puts '-------------ALERT------------------------------'
174
+ puts response.to_s
175
+ puts '------------------------------------------------'
176
+ end
177
+
178
+ when 'tick'
179
+ @tick_proc&.call(response)
180
+
181
+ when 'depth'
182
+ @depth_proc&.call(response)
183
+
184
+ when 'realtimebar'
185
+ @bars_proc&.call(response)
186
+
187
+ else
188
+ puts "ERROR: #{response}"
189
+
190
+ end
191
+ end
192
+ end
193
+ rescue StandardError => e
194
+ backtrace = e.backtrace.join("\r\n")
195
+ puts "======= ERROR: '#{e.class}', MESSAGE: '#{e.message}'\n#{backtrace}"
196
+ end
197
+ end
198
+ puts '@server_thread spawned'
199
+ end
200
+ end
201
+ end
202
+ end
203
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -0,0 +1,20 @@
1
+ module Cotcube
2
+ module Helpers
3
+
4
+ def instance_inspect(obj, keylength: 20, &block)
5
+ obj.instance_variables.map do |var|
6
+ if block_given?
7
+ block.call(var, obj.instance_variable_get(var))
8
+ else
9
+ puts "#{format "%-#{keylength}s", var
10
+ }: #{obj.instance_variable_get(var).inspect.scan(/.{1,120}/).join( "\n" + ' '*(keylength+2))
11
+ }"
12
+ end
13
+ end
14
+ end
15
+
16
+ module_function :instance_inspect
17
+
18
+ end
19
+ end
20
+