cotcube-level 0.3.3 → 0.3.4.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 +4 -4
- data/CHANGELOG.md +18 -0
- data/VERSION +1 -1
- data/bin/iswaps.rb +45 -0
- data/bin/swaps.rb +45 -0
- data/cotcube-level.gemspec +1 -1
- data/lib/cotcube-level/eod_stencil.rb +17 -10
- data/lib/cotcube-level/helpers.rb +97 -31
- data/lib/cotcube-level/intraday_stencil.rb +125 -71
- data/lib/cotcube-level.rb +2 -0
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a77d2be53fcf299f94403671818d6b31a8f0a3bf5bf59e24ac2ab55986fca91
|
4
|
+
data.tar.gz: 84f71776307891edfee1a60fed2c8ae20def673f698becf7b6ad64b817f19971
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f8620c5666ace4d7cea949f0b3cb28a73acfc9ba3167f4d14742e8637daccb5edf50e37aa62008f966941c36ddd7c3167dd88cb9937e7d4500980affcdd77e4
|
7
|
+
data.tar.gz: e63bb887f9b6616aa3f43b7704541094334f79e2398f76c95d7751df58961a844378f5462d2ff3096bc7bf061a3dc98b34355c608d8784f215e0b08b436c2415
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
## 0.3.4.3 (December 23, 2021)
|
2
|
+
- gemspec: raised activesupport to v7
|
3
|
+
- intraday_stencil: changed naming scheme for cachefile
|
4
|
+
- eod_stencil: added :timezone to self.provide_eod_stencil
|
5
|
+
|
6
|
+
## 0.3.4.2 (November 28, 2021)
|
7
|
+
- added 2 executables to bin to display eod- and intraday-swaps
|
8
|
+
- helpers: adding ignorance to load_swaps and other, adding .mark_ignored
|
9
|
+
- intraday_stencil: allowing absence of :zero in #use
|
10
|
+
|
11
|
+
## 0.3.4.1 (October 09, 2021)
|
12
|
+
- intraday_stencil: fixing @index, that did not work when used outside active hours
|
13
|
+
|
14
|
+
## 0.3.4 (October 06, 2021)
|
15
|
+
- intraday_stencil: major rework resp. rebuild ... now beta-ready
|
16
|
+
- helpers: preparing puts_swap and get_jsonl_name to work intraday
|
17
|
+
- puts_swap: changed output scheme to provide exceedance as NOTE
|
18
|
+
|
1
19
|
## 0.3.3 (October 05, 2021)
|
2
20
|
- helpers::load_swaps: added :exceed to allow 1(sic) swap to be exceeded while loading
|
3
21
|
- tritangulate: added :manual for feature of manual swap creation with base of 2 members
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.3
|
1
|
+
0.3.4.3
|
data/bin/iswaps.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/cotcube-level.rb'
|
4
|
+
|
5
|
+
HELP = <<HEREDOC
|
6
|
+
Display current intraday swaps.
|
7
|
+
> USAGE: iswaps.rb <contract> [json]
|
8
|
+
> contract a contract known to the system
|
9
|
+
> json switch to toggle json output instead of human readable
|
10
|
+
HEREDOC
|
11
|
+
if ARGV.empty?
|
12
|
+
puts HELP
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
|
16
|
+
contract = ARGV[0].nil? ? nil : ARGV[0].upcase
|
17
|
+
json = ARGV.include? 'json'
|
18
|
+
|
19
|
+
sym = Cotcube::Helpers.get_id_set(contract: contract) rescue "Could not determine contract #{contract}"
|
20
|
+
if sym.is_a? Sring; puts sym; puts HELP; exit 1; end
|
21
|
+
|
22
|
+
swaps = Cotcube::Level::load_swaps(interval: 30.minutes, swap_type: :full, contract: contract, sym: sym).
|
23
|
+
select{|swap| not(swap[:empty]) and
|
24
|
+
not(swap[:ignored]) and
|
25
|
+
not(swap[:exceeded].presence ? (swap[:exceeded] < DateTime.now - 2.days) : false)
|
26
|
+
}
|
27
|
+
stencil = Cotcube::Level::Intraday_Stencil.new( interval: 30.minutes, swap_type: :full, asset: contract[..1])
|
28
|
+
swaps.map!{|swap| stencil.use with: swap, sym: sym}
|
29
|
+
|
30
|
+
if json
|
31
|
+
puts swaps.to_json
|
32
|
+
else
|
33
|
+
puts '<none>' if swaps.empty?
|
34
|
+
swaps.each {|swap|
|
35
|
+
notice = if swap[:exceeded]
|
36
|
+
"EXCEEDED #{swap[:exceeded]}"
|
37
|
+
elsif swap[:ignored]
|
38
|
+
'IGNORED'
|
39
|
+
else
|
40
|
+
"Current: #{format sym[:format], swap[:current_value]}"
|
41
|
+
end
|
42
|
+
Cotcube::Level.puts_swap(swap, format: sym[:format], notice: notice)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
data/bin/swaps.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/cotcube-level.rb'
|
4
|
+
|
5
|
+
HELP = <<HEREDOC
|
6
|
+
swaps.rb: Display current eod swaps.
|
7
|
+
> USAGE: swaps.rb <contract> [json]
|
8
|
+
> contract a contract known to the system
|
9
|
+
> json switch to toggle json output instead of human readable
|
10
|
+
HEREDOC
|
11
|
+
if ARGV.empty?
|
12
|
+
puts HELP
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
contract = ARGV[0].nil? ? nil : ARGV[0].upcase
|
18
|
+
json = ARGV.include? 'json'
|
19
|
+
|
20
|
+
sym = Cotcube::Helpers.get_id_set(contract: contract) rescue "ERROR: Could not determine contract '#{contract}'."
|
21
|
+
|
22
|
+
if sym.is_a? String; puts sym; puts HELP; exit 1; end
|
23
|
+
|
24
|
+
swaps = Cotcube::Level::load_swaps(interval: :daily, swap_type: :full, contract: contract, quiet: true).
|
25
|
+
select{|swap| not(swap[:empty]) and
|
26
|
+
not(swap[:ignored]) and
|
27
|
+
not(swap[:exceeded].presence ? (swap[:exceeded] < DateTime.now - 2.days) : false)
|
28
|
+
}
|
29
|
+
stencil = Cotcube::Level::EOD_Stencil.new( interval: :daily, swap_type: :full)
|
30
|
+
swaps.map!{|swap| stencil.use with: swap, sym: sym}
|
31
|
+
if json
|
32
|
+
puts swaps.to_json
|
33
|
+
else
|
34
|
+
puts '<none>' if swaps.empty?
|
35
|
+
swaps.each {|swap|
|
36
|
+
notice = if swap[:exceeded]
|
37
|
+
"EXCEEDED #{swap[:exceeded].strftime('%Y-%m-%d')}"
|
38
|
+
elsif swap[:ignored]
|
39
|
+
'IGNORED'
|
40
|
+
else
|
41
|
+
"Current: #{format sym[:format], swap[:current_value]}"
|
42
|
+
end
|
43
|
+
Cotcube::Level.puts_swap(swap, format: sym[:format], notice: notice)
|
44
|
+
}
|
45
|
+
end
|
data/cotcube-level.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
27
|
spec.require_paths = ['lib']
|
28
28
|
|
29
|
-
spec.add_dependency 'activesupport', '~>
|
29
|
+
spec.add_dependency 'activesupport', '~> 7'
|
30
30
|
spec.add_dependency 'colorize', '~> 0.8'
|
31
31
|
spec.add_dependency 'cotcube-helpers', '~> 0.1'
|
32
32
|
spec.add_dependency 'yaml', '~> 0.1'
|
@@ -13,7 +13,7 @@ module Cotcube
|
|
13
13
|
#
|
14
14
|
# Current daily stencils contain dates from 2020-01-01 to 2023-12-31
|
15
15
|
#
|
16
|
-
def self.provide_raw_stencil(type:, interval: :daily, version: nil)
|
16
|
+
def self.provide_raw_stencil(type:, interval: :daily, version: nil, timezone: Cotcube::Helpers::CHICAGO)
|
17
17
|
loading = lambda do |typ|
|
18
18
|
file_base = "/var/cotcube/level/stencils/stencil_#{interval.to_s}_#{typ.to_s}.csv_"
|
19
19
|
if Dir["#{file_base}?*"].empty?
|
@@ -27,7 +27,7 @@ module Cotcube
|
|
27
27
|
raise ArgumentError, "Cannot open stencil from non-existant file #{file}."
|
28
28
|
end
|
29
29
|
end
|
30
|
-
CSV.read(file).map{|x| { datetime:
|
30
|
+
CSV.read(file).map{|x| { datetime: timezone.parse(x.first).freeze, x: x.last.to_i.freeze } }
|
31
31
|
end
|
32
32
|
unless const_defined? :RAW_STENCILS
|
33
33
|
const_set :RAW_STENCILS, { daily:
|
@@ -80,7 +80,7 @@ module Cotcube
|
|
80
80
|
raise ArgumentError, "Each stencil members should contain at least :datetime and :x" unless stencil.nil? or
|
81
81
|
stencil.map{|x| ([:datetime, :x] - x.keys).empty? and [ActiveSupport::TimeWithZone, Day].include?( x[:datetime] ) and x[:x].is_a?(Integer)}.reduce(:&)
|
82
82
|
|
83
|
-
base = stencil || EOD_Stencil.provide_raw_stencil(type: stencil_type, interval: :daily, version: version)
|
83
|
+
base = stencil || EOD_Stencil.provide_raw_stencil(type: stencil_type, interval: :daily, version: version, timezone: timezone)
|
84
84
|
|
85
85
|
# fast rewind to previous trading day
|
86
86
|
date = timezone.parse(date) unless [NilClass, Date, ActiveSupport::TimeWithZone].include? date.class
|
@@ -111,7 +111,12 @@ module Cotcube
|
|
111
111
|
end
|
112
112
|
|
113
113
|
def zero
|
114
|
-
|
114
|
+
index(0)
|
115
|
+
end
|
116
|
+
|
117
|
+
def index(offset = 0)
|
118
|
+
@index ||= @base.index{|b| b[:x].zero? }
|
119
|
+
@base[@index + offset]
|
115
120
|
end
|
116
121
|
|
117
122
|
def apply(to: )
|
@@ -136,7 +141,7 @@ module Cotcube
|
|
136
141
|
to.reject!{|x| x[:x].nil? }
|
137
142
|
end
|
138
143
|
|
139
|
-
def use(with:, sym:, zero
|
144
|
+
def use(with:, sym:, zero: nil, grace: -2)
|
140
145
|
# todo: validate with (check if vslid swap
|
141
146
|
# sym (check keys)
|
142
147
|
# zero (ohlc with x.zero?)
|
@@ -147,14 +152,16 @@ module Cotcube
|
|
147
152
|
start = base.find{|x| swap[:datetime] == x[:datetime]}
|
148
153
|
swap[:current_change] = (swap[:tpi] * start[:x]).round(8)
|
149
154
|
swap[:current_value] = swap[:members].last[ ohlc ] + swap[:current_change] * sym[:ticksize]
|
150
|
-
|
151
|
-
|
152
|
-
|
155
|
+
unless zero.nil?
|
156
|
+
swap[:current_diff] = (swap[:current_value] - zero[ohlc]) * (high ? 1 : -1 )
|
157
|
+
swap[:current_dist] = (swap[:current_diff] / sym[:ticksize]).to_i
|
158
|
+
swap[:exceeded] = zero[:datetime] if swap[:current_dist] < grace
|
159
|
+
end
|
153
160
|
swap
|
154
161
|
end
|
155
|
-
|
162
|
+
end
|
156
163
|
|
157
|
-
|
164
|
+
end
|
158
165
|
|
159
166
|
end
|
160
167
|
|
@@ -23,7 +23,7 @@ module Cotcube
|
|
23
23
|
|
24
24
|
# human readable output
|
25
25
|
# please note the format must be given, that should be taken from :sym
|
26
|
-
def member_to_human(member,side: ,format:, daily: false)
|
26
|
+
def member_to_human(member,side: ,format:, daily: false, tws: false)
|
27
27
|
high = (side == :upper)
|
28
28
|
"#{ member[:datetime].strftime("%a, %Y-%m-%d#{daily ? "" :" %I:%M%p"}")
|
29
29
|
} x: #{format '%-4d', member[:x]
|
@@ -40,37 +40,80 @@ module Cotcube
|
|
40
40
|
# format: e.g. sym[:format]
|
41
41
|
# short: print one line / less verbose
|
42
42
|
# notice: add this to output as well
|
43
|
-
def puts_swap(swap, format: , short: true, notice: nil, hash: 3)
|
44
|
-
return if swap[:empty]
|
45
|
-
|
46
|
-
|
43
|
+
def puts_swap(swap, format: , short: true, notice: nil, hash: 3, tws: false)
|
44
|
+
return '' if swap[:empty]
|
45
|
+
|
46
|
+
# if presenting swaps from json, the datetimes need to be parsed first
|
47
|
+
swap[:datetime] = DateTime.parse(swap[:datetime]) if swap[:datetime].is_a? String
|
48
|
+
swap[:exceeded] = DateTime.parse(swap[:exceeded]) if swap[:exceeded].is_a? String
|
49
|
+
swap[:ignored] = DateTime.parse(swap[:ignored]) if swap[:ignored ].is_a? String
|
50
|
+
swap[:side] = swap[:side].to_sym
|
51
|
+
swap[:members].each do |mem|
|
52
|
+
mem[:datetime] = DateTime.parse(mem[:datetime]) if mem[:datetime].is_a? String
|
53
|
+
end
|
54
|
+
|
55
|
+
# TODO: create config-entry to contain 1.hour -- set of contracts ; 7.hours -- set of contracts [...]
|
56
|
+
# instead of hard-coding in here
|
57
|
+
# TODO: add also to :member_to_human
|
58
|
+
# then commit
|
59
|
+
if tws
|
60
|
+
case swap[:contract][0...2]
|
61
|
+
when *%w[ GC SI PL PA HG NG CL HO RB ]
|
62
|
+
delta_datetime = 1.hour
|
63
|
+
when *%w[ GG DX ]
|
64
|
+
delta_datetime = 7.hours
|
65
|
+
else
|
66
|
+
delta_datetime = 0
|
67
|
+
end
|
68
|
+
else
|
69
|
+
delta_datetime = 0
|
70
|
+
end
|
71
|
+
daily = %i[ continuous daily ].include?(swap[:interval].to_sym) rescue false
|
72
|
+
datetime_format = daily ? '%Y-%m-%d' : '%Y-%m-%d %I:%M %p'
|
47
73
|
high = swap[:side] == :high
|
48
74
|
ohlc = high ? :high : :low
|
75
|
+
if notice.nil? and swap[:exceeded]
|
76
|
+
notice = "exceeded #{(swap[:exceeded] + delta_datetime).strftime(datetime_format)}"
|
77
|
+
end
|
78
|
+
if swap[:ignored]
|
79
|
+
notice += " IGNORED"
|
80
|
+
end
|
49
81
|
if short
|
50
|
-
|
51
|
-
|
52
|
-
}
|
53
|
-
}
|
54
|
-
|
55
|
-
}
|
56
|
-
}
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
82
|
+
res ="#{format '%7s', swap[:digest][...hash]
|
83
|
+
} #{ swap[:contract]
|
84
|
+
} #{ swap[:side].to_s
|
85
|
+
}".colorize( swap[:side] == :upper ? :light_green : :light_red ) +
|
86
|
+
" (#{ format '%4d', swap[:length]
|
87
|
+
},#{ format '%4d', swap[:rating]
|
88
|
+
},#{ format '%4d', swap[:depth]
|
89
|
+
}) P: #{ format '%6s', (format '%4.2f', swap[:ppi])
|
90
|
+
} #{
|
91
|
+
if swap[:current_value].nil?
|
92
|
+
"I: #{ format '%8s', (format format, swap[:members].last[ ohlc ]) }"
|
93
|
+
else
|
94
|
+
"C: #{ format '%8s', (format format, swap[:current_value]) } "
|
95
|
+
end
|
96
|
+
} [#{ (swap[:members].first[:datetime] + delta_datetime).strftime(datetime_format)
|
97
|
+
} - #{ (swap[:members].last[:datetime] + delta_datetime).strftime(datetime_format)
|
98
|
+
}]#{" NOTE: #{notice}" unless notice.nil?
|
99
|
+
}".colorize(swap[:color] || :white )
|
100
|
+
puts res
|
61
101
|
else
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
swap[:members].each {|x|
|
102
|
+
res = ["side: #{swap[:side] }\tlen: #{swap[:length]} \trating: #{swap[:rating]}".colorize(swap[:color] || :white )]
|
103
|
+
res << "diff: #{swap[:ticks]}\tdif: #{swap[:diff].round(7)}\tdepth: #{swap[:depth]}".colorize(swap[:color] || :white )
|
104
|
+
res << "tpi: #{swap[:tpi] }\tppi: #{swap[:ppi]}".colorize(swap[:color] || :white )
|
105
|
+
res << "NOTE: #{notice}".colorize(:light_white) unless notice.nil?
|
106
|
+
swap[:members].each {|x| res << member_to_human(x, side: swap[:side], format: format, daily: daily) }
|
107
|
+
res = res.join("\n")
|
108
|
+
puts res
|
67
109
|
end
|
110
|
+
res
|
68
111
|
end
|
69
112
|
|
70
113
|
# create a standardized name for the cache files
|
71
114
|
# and, on-the-fly, create these files plus their directory
|
72
115
|
def get_jsonl_name(interval:, swap_type:, contract:, sym: nil)
|
73
|
-
raise "Interval #{interval } is not supported, please choose from #{INTERVALS}" unless INTERVALS.include? interval
|
116
|
+
raise "Interval #{interval } is not supported, please choose from #{INTERVALS}" unless INTERVALS.include?(interval) || interval.is_a?(Integer)
|
74
117
|
raise "Swaptype #{swap_type} is not supported, please choose from #{SWAPTYPES}" unless SWAPTYPES.include? swap_type
|
75
118
|
sym ||= Cotcube::Helpers.get_id_set(contract: contract)
|
76
119
|
root = '/var/cotcube/level'
|
@@ -79,7 +122,9 @@ module Cotcube
|
|
79
122
|
`mkdir -p #{dir}` unless File.exist?(dir)
|
80
123
|
`ln -s #{dir} #{symlink}` unless File.exist?(symlink)
|
81
124
|
file = "#{dir}/#{contract}_#{interval.to_s}_#{swap_type.to_s}.jsonl"
|
82
|
-
|
125
|
+
unless File.exist? file
|
126
|
+
`touch #{file}`
|
127
|
+
end
|
83
128
|
file
|
84
129
|
end
|
85
130
|
|
@@ -115,7 +160,9 @@ module Cotcube
|
|
115
160
|
|
116
161
|
# loading of swaps is also straight forward
|
117
162
|
# it takes few more efforts to normalize the values to their expected format
|
118
|
-
|
163
|
+
#
|
164
|
+
# it is not too nice that some actual interactive process is done here in the load section
|
165
|
+
def load_swaps(interval:, swap_type:, contract:, sym: nil, datetime: nil, recent: false, digest: nil, quiet: false, exceed: false, keep_ignored: false)
|
119
166
|
file = get_jsonl_name(interval: interval, swap_type: swap_type, contract: contract, sym: sym)
|
120
167
|
jsonl = File.read(file)
|
121
168
|
data = jsonl.
|
@@ -126,12 +173,13 @@ module Cotcube
|
|
126
173
|
tap do |sw|
|
127
174
|
sw[:datetime] = DateTime.parse(sw[:datetime]) rescue nil
|
128
175
|
(sw[:exceeded] = DateTime.parse(sw[:exceeded]) rescue nil) if sw[:exceeded]
|
176
|
+
(sw[:ignored] = DateTime.parse(sw[:ignored]) rescue nil) if sw[:ignored]
|
129
177
|
sw[:interval] = interval
|
130
178
|
sw[:swap_type] = swap_type
|
131
179
|
sw[:contract] = contract
|
132
180
|
%i[ side ].each {|key| sw[key] = sw[key].to_sym rescue false }
|
133
|
-
unless sw[:empty] or sw[:exceeded]
|
134
|
-
sw[:color] = sw[:color].to_sym
|
181
|
+
unless sw[:empty] or sw[:exceeded] or sw[:ignored]
|
182
|
+
sw[:color] = sw[:color].to_sym
|
135
183
|
sw[:members].map{|mem| mem[:datetime] = DateTime.parse(mem[:datetime]) }
|
136
184
|
end
|
137
185
|
end
|
@@ -142,13 +190,20 @@ module Cotcube
|
|
142
190
|
raise RuntimeError, "Inconsistent history for '#{exc}'. Origin not found." if swap.nil?
|
143
191
|
swap[:exceeded] = exc[:exceeded]
|
144
192
|
end
|
193
|
+
# assign ignorance data to actual swaps
|
194
|
+
data.select{|swap| swap[:ignored] }.each do |ign|
|
195
|
+
swap = data.find{|ref| ref[:digest] == ign[:ref]}
|
196
|
+
raise RuntimeError, "Inconsistent history for '#{ign}'. Origin not found." if swap.nil?
|
197
|
+
swap[:ignored] = ign[:ignored]
|
198
|
+
end
|
145
199
|
# do not return bare exceedance information
|
146
|
-
data.reject!{|swap| swap[:exceeded] and swap[:members].nil? }
|
200
|
+
data.reject!{|swap| (swap[:ignored] or swap[:exceeded]) and swap[:members].nil? }
|
147
201
|
# do not return swaps that are found 'later'
|
148
202
|
data.reject!{|swap| swap[:datetime] > datetime } unless datetime.nil?
|
149
203
|
# do not return exceeded swaps, that are exceeded in the past
|
150
204
|
recent = 7.days if recent.is_a? TrueClass
|
151
205
|
recent += 5.hours if recent
|
206
|
+
data.reject!{|swap| swap[:ignored] } unless keep_ignored
|
152
207
|
data.reject!{|swap| swap[:exceeded] and swap[:exceeded] < datetime - (recent ? recent : 0) } unless datetime.nil?
|
153
208
|
# remove exceedance information that is found 'later'
|
154
209
|
data.map{|swap| swap.delete(:exceeded) if swap[:exceeded] and swap[:exceeded] > datetime} unless datetime.nil?
|
@@ -200,20 +255,31 @@ module Cotcube
|
|
200
255
|
save_swaps to_save, interval: swap[:interval], swap_type: swap[:swap_type], contract: contract, sym: sym, quiet: (not debug)
|
201
256
|
swap[:exceeded] = update[:exceeded]
|
202
257
|
end
|
203
|
-
%i[ current_change current_value current_diff current_dist ].map{|key| swap[key] = update[key] }
|
258
|
+
%i[ current_change current_value current_diff current_dist alert].map{|key| swap[key] = update[key] }
|
204
259
|
swap
|
205
260
|
end.compact
|
206
261
|
end
|
207
262
|
|
208
|
-
def mark_exceeded(swap:, datetime:, debug: false)
|
263
|
+
def mark_exceeded(swap:, datetime:, debug: false, sym: nil)
|
209
264
|
to_save = {
|
210
265
|
datetime: datetime,
|
211
266
|
ref: swap[:digest],
|
212
267
|
side: swap[:side],
|
213
268
|
exceeded: datetime
|
214
269
|
}
|
215
|
-
|
216
|
-
swap[:
|
270
|
+
sym ||= Cotcube::Helpers.get_id_set(contract: swap[:contract])
|
271
|
+
save_swaps to_save, interval: swap[:interval], swap_type: swap[:swap_type], sym: sym, contract: swap[:contract], quiet: (not debug)
|
272
|
+
swap
|
273
|
+
end
|
274
|
+
|
275
|
+
def mark_ignored(swap:, datetime: DateTime.now, sym: , debug: true)
|
276
|
+
to_save = {
|
277
|
+
datetime: datetime,
|
278
|
+
ref: swap[:digest],
|
279
|
+
side: swap[:side],
|
280
|
+
ignored: datetime
|
281
|
+
}
|
282
|
+
save_swaps to_save, interval: swap[:interval], swap_type: swap[:swap_type], sym: sym, contract: swap[:contract], quiet: (not debug)
|
217
283
|
swap
|
218
284
|
end
|
219
285
|
|
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Cotcube
|
2
4
|
module Level
|
3
|
-
|
5
|
+
|
4
6
|
class Intraday_Stencil
|
5
7
|
|
6
|
-
GLOBAL_SOW = { 'CT' => '0000-1700' }
|
7
|
-
GLOBAL_EOW = { 'CT' => '1700-0000' }
|
8
|
-
GLOBAL_EOD = { 'CT' => '1600-1700' }
|
9
8
|
|
9
|
+
# Class method that loads the (latest) shiftset for given asset
|
10
|
+
# These raw stencils are located in /var/cotcube/level/stencils/shiftsets.csv
|
11
|
+
#
|
10
12
|
|
11
13
|
def self.shiftset(asset:, sym: nil)
|
12
14
|
shiftset_file = '/var/cotcube/level/stencils/shiftsets.csv'
|
@@ -18,45 +20,66 @@ module Cotcube
|
|
18
20
|
sym ||= Cotcube::Helpers.get_id_set(symbol: asset)
|
19
21
|
current_set = shiftsets.find{|s| s[:symbols] =~ /#{sym[:type]}/ }
|
20
22
|
return current_set.tap{|s| headers.map{|h| s[h] = nil if s[h] == '---------' }; s[:rth5] ||= s[:rth]; s[:mpost5] ||= s[:mpost] } unless current_set.nil?
|
21
|
-
raise "Cannot get shiftset for #{sym[:type]}: #{asset}, please prepare #{shiftset_file} before!"
|
23
|
+
raise "Cannot get shiftset for #{sym[:type]}: #{asset}, please prepare #{shiftset_file} before!"
|
22
24
|
end
|
23
25
|
|
24
26
|
attr_reader :base, :shiftset, :timezone, :datetime, :zero, :index
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
@
|
42
|
-
@
|
43
|
-
|
28
|
+
|
29
|
+
|
30
|
+
def initialize(
|
31
|
+
asset:,
|
32
|
+
interval: 30.minutes,
|
33
|
+
swap_type: :full,
|
34
|
+
datetime: nil,
|
35
|
+
debug: false,
|
36
|
+
weeks: 6,
|
37
|
+
future: 2,
|
38
|
+
measuring: false,
|
39
|
+
version: nil, # when referring to a specicic version of the stencil
|
40
|
+
stencil: nil, # instead of preparing, use this one if set
|
41
|
+
warnings: true # be more quiet
|
42
|
+
)
|
43
|
+
@shiftset = Intraday_Stencil.shiftset(asset: asset)
|
44
|
+
@timezone = Cotcube::Level::TIMEZONES[@shiftset[:tz]]
|
45
|
+
@debug = debug
|
46
|
+
@interval = interval
|
47
|
+
@swap_type = swap_type
|
48
|
+
@warnings = warnings
|
49
|
+
datetime ||= DateTime.now
|
50
|
+
datetime = @timezone.at(datetime.to_i) unless datetime.is_a? ActiveSupport::TimeWithZone
|
51
|
+
@datetime = datetime.beginning_of_day
|
52
|
+
@datetime += interval while @datetime <= datetime - interval
|
53
|
+
@datetime -= interval
|
54
|
+
|
55
|
+
now = DateTime.now
|
56
|
+
measure = lambda {|x| puts "\nMeasured #{(Time.now - now).to_f.round(2)}: ".colorize(:light_yellow) + x + "\n\n" if measuring }
|
57
|
+
|
58
|
+
measure.call "Starting initialization for asset '#{asset}' "
|
59
|
+
|
60
|
+
const = "RAW_INTRA_STENCIL_#{@shiftset[:nr]}_#{interval.in_minutes.to_i}_#{weeks}_#{future}".to_sym
|
61
|
+
cachefile = "/var/cotcube/level/stencils/cache/#{asset.to_s}_#{swap_type.to_s}_#{interval}_#{@datetime.strftime('%Y-%m-%d-%H-%M')}.json"
|
62
|
+
|
44
63
|
if Object.const_defined? const
|
64
|
+
measure.call 'getting cached base from memory'
|
45
65
|
@base = (Object.const_get const).map{|z| z.dup}
|
66
|
+
elsif File.exist? cachefile
|
67
|
+
measure.call 'getting cached base from file'
|
68
|
+
@base = JSON.parse(File.read(cachefile), symbolize_names: true).map{|z| z[:datetime] = DateTime.parse(z[:datetime]); z[:type] = z[:type].to_sym; z }
|
46
69
|
else
|
47
|
-
|
70
|
+
measure.call 'creating base from shiftset'
|
48
71
|
start_time = lambda {|x| @shiftset[x].split('-').first rescue '' }
|
49
72
|
start_hours = lambda {|x| @shiftset[x].split('-').first[ 0.. 1].to_i.send(:hours) rescue 0 }
|
50
73
|
start_minutes = lambda {|x| @shiftset[x].split('-').first[-2..-1].to_i.send(:minutes) rescue 0 }
|
51
74
|
end_time = lambda {|x| @shiftset[x].split('-').last rescue '' }
|
52
75
|
end_hours = lambda {|x| @shiftset[x].split('-').last [ 0.. 1].to_i.send(:hours) rescue 0 }
|
53
|
-
end_minutes = lambda {|x| @shiftset[x].split('-').last [-2..-1].to_i.send(:minutes) rescue 0 }
|
76
|
+
end_minutes = lambda {|x| @shiftset[x].split('-').last [-2..-1].to_i.send(:minutes) rescue 0 }
|
54
77
|
|
55
|
-
runner = (@datetime -
|
78
|
+
runner = (@datetime -
|
56
79
|
weeks * 7.days).beginning_of_week(:sunday)
|
57
80
|
tm_runner = lambda { runner.strftime('%H%M') }
|
58
|
-
@base = []
|
59
|
-
(weeks+future).times do
|
81
|
+
@base = []
|
82
|
+
(weeks+future).times do
|
60
83
|
while tm_runner.call < GLOBAL_SOW[@shiftset[:tz]].split('-').last
|
61
84
|
# if daylight is switched, this phase will be shorter or longer
|
62
85
|
@base << { datetime: runner, type: :sow }
|
@@ -81,10 +104,10 @@ module Cotcube
|
|
81
104
|
yet_rth = true
|
82
105
|
end
|
83
106
|
end
|
84
|
-
while ((sophase > eophase) ? (tm_runner.call >= sophase or tm_runner.call < eophase) : (tm_runner.call < eophase))
|
107
|
+
while ((sophase > eophase) ? (tm_runner.call >= sophase or tm_runner.call < eophase) : (tm_runner.call < eophase))
|
85
108
|
current = { datetime: runner, type: phase }
|
86
109
|
if phase == :rth and not yet_rth
|
87
|
-
current[:block] = true
|
110
|
+
current[:block] = true
|
88
111
|
yet_rth = true
|
89
112
|
end
|
90
113
|
@base << current
|
@@ -100,80 +123,111 @@ module Cotcube
|
|
100
123
|
while runner < end_of_week
|
101
124
|
@base << { datetime: runner, type: :eow }
|
102
125
|
runner += interval
|
103
|
-
end
|
126
|
+
end
|
104
127
|
end
|
105
128
|
Object.const_set(const, @base.map{|z| z.dup})
|
129
|
+
File.open(cachefile, 'w'){|f| f.write(@base.to_json)}
|
130
|
+
end
|
131
|
+
measure.call "base created with #{@base.size} records"
|
132
|
+
|
133
|
+
case swap_type
|
134
|
+
when :full
|
135
|
+
@base.select!{|x| %i[ pre rth post ].include?(x[:type]) }
|
136
|
+
when :rth
|
137
|
+
@base.select!{|x| x[:type] == :rth }
|
138
|
+
when :flow
|
139
|
+
@base.reject!{|x| %i[ sow eow mpost mpost ].include?(x[:type]) }
|
140
|
+
@base.
|
141
|
+
map{ |x|
|
142
|
+
[:high, :low, :volume].map{|z| x[z] = nil} unless x[:type] == :rth
|
143
|
+
}
|
144
|
+
when :run
|
145
|
+
@base.select!{|x| %i[ pre rth post ].include? x[:type]}
|
146
|
+
else
|
147
|
+
raise ArgumentError, "Unknown stencil/swap type '#{swap_type}'"
|
106
148
|
end
|
107
|
-
|
108
|
-
@
|
109
|
-
|
149
|
+
measure.call "swaptype #{swap_type} applied"
|
150
|
+
@base.map!{|z| z.dup}
|
151
|
+
measure.call 'base dupe\'d'
|
152
|
+
|
153
|
+
# zero is, were either x[:datetime] == @datetime (when we are intraday)
|
154
|
+
# or otherwise {x[:datetime] <= @datetime}.last (when on maintenance)
|
155
|
+
selector = @base.select{|y| y[:datetime] <= @datetime }.last
|
156
|
+
@index = @base.index{|x| x == selector }
|
157
|
+
measure.call 'index selected'
|
158
|
+
@index -= 1 while %i[sow sod mpre mpost eod eow].include? @base[@index][:type]
|
159
|
+
measure.call 'index adjusted'
|
110
160
|
@datetime = @base[@index][:datetime]
|
111
161
|
@zero = @base[@index]
|
112
|
-
counter = 0
|
162
|
+
counter = 0
|
163
|
+
measure.call "Applying counter to past"
|
113
164
|
while @base[@index - counter] and @index - counter >= 0
|
114
165
|
@base[@index - counter][:x] = counter
|
115
166
|
counter += 1
|
116
167
|
end
|
117
|
-
counter = 0
|
168
|
+
counter = 0
|
169
|
+
measure.call "Applying counter to future"
|
118
170
|
while @base[@index + counter] and @index + counter < @base.length
|
119
171
|
@base[@index + counter][:x] = -counter
|
120
172
|
counter += 1
|
121
173
|
end
|
122
|
-
|
174
|
+
measure.call 'initialization finished'
|
175
|
+
end
|
176
|
+
|
177
|
+
def zero
|
178
|
+
index(0)
|
123
179
|
end
|
124
180
|
|
125
|
-
def
|
126
|
-
|
181
|
+
def index(offset = 0)
|
182
|
+
@index ||= @base.index{|b| b[:x].zero? }
|
183
|
+
@base[@index + offset]
|
127
184
|
end
|
128
185
|
|
129
|
-
|
130
|
-
def apply(to
|
186
|
+
|
187
|
+
def apply(to: )
|
131
188
|
offset = 0
|
132
|
-
|
189
|
+
@base.each_index do |i|
|
133
190
|
begin
|
134
|
-
offset += 1 while
|
135
|
-
puts "#{i}\t#{offset}\t#{@base[i+offset][:datetime]} < #{to[i][:datetime]}" if debug
|
191
|
+
offset += 1 while to[i+offset][:datetime] < @base[i][:datetime]
|
136
192
|
rescue
|
137
193
|
# appending
|
138
|
-
|
139
|
-
@base << to[i]
|
194
|
+
to << @base[i]
|
140
195
|
next
|
141
196
|
end
|
142
|
-
if
|
197
|
+
if to[i+offset][:datetime] > @base[i][:datetime]
|
143
198
|
# skipping
|
144
|
-
puts "skipping #{i}\t#{offset}\t#{@base[i+offset][:datetime]} < #{to[i][:datetime]}" if debug
|
145
199
|
offset -= 1
|
146
200
|
next
|
147
201
|
end
|
148
202
|
# merging
|
149
|
-
|
150
|
-
|
151
|
-
puts "MERGED:\t#{i}\t#{offset}\t#{@base[j]}" if debug
|
203
|
+
to[i+offset][:x] = @base[i][:x]
|
204
|
+
to[i+offset][:type] = @base[i][:type]
|
152
205
|
end
|
153
206
|
# finally remove all bars that do not belong to the stencil (i.e. holidays)
|
154
|
-
|
155
|
-
when :full
|
156
|
-
@base.select!{|x| %i[ pre rth post ].include?(x[:type]) }
|
157
|
-
when :rth
|
158
|
-
@base.select!{|x| x[:type] == :rth }
|
159
|
-
# to.map{ |x| [:high, :low, :volume].map{|z| x[z] = nil} if x[:block] }
|
160
|
-
when :flow
|
161
|
-
@base.reject!{|x| %i[ meow postmm postmm5 ].include?(x[:type]) }
|
162
|
-
@base.
|
163
|
-
map{ |x|
|
164
|
-
[:high, :low, :volume].map{|z| x[z] = nil} unless x[:type] == :rth
|
165
|
-
# [:high, :low, :volume].map{|z| x[z] = nil} if x[:block]
|
166
|
-
}
|
167
|
-
when :run
|
168
|
-
@base.select!{|x| %i[ premarket rth postmarket ].include? x[:type]}
|
169
|
-
else
|
170
|
-
raise ArgumentError, "Unknown stencil/swap type '#{type}'"
|
171
|
-
end
|
172
|
-
@base.map!{|z| z.dup}
|
207
|
+
to.reject!{|x| x[:x].nil? }
|
173
208
|
end
|
174
209
|
|
210
|
+
def use(with:, sym:, zero: nil, grace: -2)
|
211
|
+
# todo: validate with (check if vslid swap
|
212
|
+
# sym (check keys)
|
213
|
+
# zero (ohlc with x.zero?)
|
214
|
+
# side ( upper or lower)
|
215
|
+
swap = with.dup
|
216
|
+
high = swap[:side] == :upper
|
217
|
+
ohlc = high ? :high : :low
|
218
|
+
start = base.find{|x| swap[:datetime] == x[:datetime]}
|
219
|
+
swap[:current_change] = (swap[:tpi] * start[:x]).round(8)
|
220
|
+
swap[:current_value] = swap[:members].last[ ohlc ] + swap[:current_change] * sym[:ticksize]
|
221
|
+
unless zero.nil?
|
222
|
+
swap[:current_diff] = (swap[:current_value] - zero[ohlc]) * (high ? 1 : -1 )
|
223
|
+
swap[:current_dist] = (swap[:current_diff] / sym[:ticksize]).to_i
|
224
|
+
swap[:alert] = (swap[:current_diff] / zero[:atr5]).round(2)
|
225
|
+
swap[:exceeded] = zero[:datetime] if swap[:current_dist] < grace
|
226
|
+
end
|
227
|
+
swap
|
228
|
+
end
|
175
229
|
end
|
176
230
|
|
177
|
-
Intraday_Stencils = Intraday_Stencil
|
178
231
|
end
|
232
|
+
|
179
233
|
end
|
data/lib/cotcube-level.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cotcube-level
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.3
|
4
|
+
version: 0.3.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamin L. Tischendorf
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '7'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: colorize
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -120,6 +120,8 @@ files:
|
|
120
120
|
- Gemfile
|
121
121
|
- README.md
|
122
122
|
- VERSION
|
123
|
+
- bin/iswaps.rb
|
124
|
+
- bin/swaps.rb
|
123
125
|
- cotcube-level.gemspec
|
124
126
|
- lib/cotcube-level.rb
|
125
127
|
- lib/cotcube-level/detect_slope.rb
|
@@ -149,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
151
|
- !ruby/object:Gem::Version
|
150
152
|
version: '0'
|
151
153
|
requirements: []
|
152
|
-
rubygems_version: 3.1.
|
154
|
+
rubygems_version: 3.1.6
|
153
155
|
signing_key:
|
154
156
|
specification_version: 4
|
155
157
|
summary: A gem to shear a time series
|