honeymaker 0.9.7 → 0.9.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 632a9bc63075c21713722ff28baece102104084e83a6b20675c7a0604c0a49e6
4
- data.tar.gz: 4d2d63654b5fedd397945f72dd21eca95ca2762b1f658f73cf80b02cb1a7285b
3
+ metadata.gz: ed9b0709acd948d270d3d3a56c6ebc829f85e89d22705bf7df6701f73cdff8c5
4
+ data.tar.gz: be9a09290fec662246e8e8577290d360cd79fd508ade8714f3b907f5fb0ee034
5
5
  SHA512:
6
- metadata.gz: 3a5f15c7d5163ef0013998fa7cccb81aa661018ff65561d7d55e606c7e70969e4172fca70ec3cfdc1529ad03c4637c69b6bff2eefaa9586627710aefe15710a1
7
- data.tar.gz: 15b53a301be6c936a90d3ac96c505ebe9946448783d07fbfb4832d5e294e6ca2cd44bd726e86f7fa34eda0074b1febf24142c2206f0e743d3edda9609e091f26
6
+ metadata.gz: 307536ee43e2d581eb48fcc265d2c30907a55531d272a526469f16e22486ad2d10955c5aa61b9c9c81c4c1b7134fcad0b44a4ba60a04456fd4522cff77e699da
7
+ data.tar.gz: e39fe73a3b4ca2a418428efa29053a9e552345c331c0ab476db5cfc92ebbe91ee02d624403c74e9ae7ae6f497b48ab2ac5d32d2a411adfb564a2800b9bd38421
@@ -121,6 +121,47 @@ module Honeymaker
121
121
  })
122
122
  end
123
123
 
124
+ # Reconcile orders that QueryOrders has already dropped (Kraken removes terminal
125
+ # orders within hours) against TradesHistory, which is authoritative for executions
126
+ # and effectively unbounded in retention. Returns ONLY orders that actually executed,
127
+ # keyed by ordertxid, each as a per-order executed aggregate. Orders with no trades
128
+ # (never filled / truly gone) are simply absent from the result.
129
+ #
130
+ # `start` should be a unix timestamp bounding the lookback (e.g. the order's creation
131
+ # time) so we don't page the user's entire trade history.
132
+ def closed_orders_from_trades(order_ids:, start: nil, max_pages: 20)
133
+ wanted = Array(order_ids)
134
+ return Result::Success.new({}) if wanted.empty?
135
+
136
+ by_order = Hash.new { |h, k| h[k] = [] }
137
+ offset = 0
138
+ pages = 0
139
+ loop do
140
+ result = get_trades_history(start: start, ofs: offset)
141
+ return result if result.failure?
142
+
143
+ errors = result.data["error"]
144
+ return Result::Failure.new(*errors) if errors.is_a?(Array) && errors.any?
145
+
146
+ trades = result.data.dig("result", "trades") || {}
147
+ break if trades.empty?
148
+
149
+ trades.each_value do |t|
150
+ otxid = t["ordertxid"]
151
+ by_order[otxid] << t if wanted.include?(otxid)
152
+ end
153
+
154
+ # Do NOT early-exit when each id has been *seen* — a partial fill can have trades on
155
+ # later pages. Page the whole [start, now] window (bounded by `start` + max_pages).
156
+ pages += 1
157
+ offset += trades.size
158
+ count = result.data.dig("result", "count").to_i
159
+ break if offset >= count || pages >= max_pages
160
+ end
161
+
162
+ Result::Success.new(by_order.transform_values { |trades| aggregate_trades(trades) })
163
+ end
164
+
124
165
  def get_withdraw_addresses(asset: nil, method: nil)
125
166
  post_private("/0/private/WithdrawAddresses", { nonce: nonce, asset: asset, method: method })
126
167
  end
@@ -145,6 +186,29 @@ module Honeymaker
145
186
 
146
187
  private
147
188
 
189
+ def aggregate_trades(trades)
190
+ first = trades.first
191
+ vol = trades.sum { |t| BigDecimal(t["vol"].to_s) }
192
+ cost = trades.sum { |t| BigDecimal(t["cost"].to_s) }
193
+ fee = trades.sum { |t| BigDecimal(t["fee"].to_s) }
194
+ {
195
+ order_id: first["ordertxid"],
196
+ status: :closed, # only executed orders reach here
197
+ side: first["type"]&.to_sym, # buy / sell
198
+ order_type: parse_order_type(first["ordertype"]),
199
+ price: vol.zero? ? nil : cost / vol, # VWAP across (partial) fills
200
+ amount: nil, # original order size unknown from trades
201
+ quote_amount: nil,
202
+ amount_exec: vol,
203
+ quote_amount_exec: cost,
204
+ fee: fee,
205
+ pair: first["pair"],
206
+ trade_count: trades.size,
207
+ last_trade_at: trades.map { |t| t["time"].to_f }.max,
208
+ raw: { "trades" => trades }
209
+ }
210
+ end
211
+
148
212
  def normalize_order(order_id, raw)
149
213
  descr = raw["descr"] || {}
150
214
  order_type = parse_order_type(descr["ordertype"])
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Honeymaker
4
- VERSION = "0.9.7"
4
+ VERSION = "0.9.9"
5
5
  end
@@ -126,6 +126,53 @@ class Honeymaker::Clients::KrakenTest < Minitest::Test
126
126
  assert_equal first_key_first_nonce, second_key_first_nonce
127
127
  end
128
128
 
129
+ def test_closed_orders_from_trades_aggregates_partial_fills
130
+ page = { "error" => [], "result" => { "count" => 2, "trades" => {
131
+ "TID-1" => { "ordertxid" => "OABC", "pair" => "XXBTZUSD", "type" => "buy",
132
+ "ordertype" => "limit", "price" => "60000.0", "cost" => "6.0",
133
+ "vol" => "0.0001", "fee" => "0.01", "time" => 1_700_000_000.0 },
134
+ "TID-2" => { "ordertxid" => "OABC", "pair" => "XXBTZUSD", "type" => "buy",
135
+ "ordertype" => "limit", "price" => "61000.0", "cost" => "4.0",
136
+ "vol" => "0.00006557", "fee" => "0.006", "time" => 1_700_000_100.0 } } } }
137
+ stub_connection(:post, page)
138
+ result = @client.closed_orders_from_trades(order_ids: ["OABC"], start: 1_699_999_000)
139
+ assert result.success?
140
+ agg = result.data["OABC"]
141
+ assert_equal :closed, agg[:status]
142
+ assert_equal :buy, agg[:side]
143
+ assert_equal :limit, agg[:order_type]
144
+ assert_equal BigDecimal("10.0"), agg[:quote_amount_exec] # 6 + 4
145
+ assert_equal BigDecimal("0.00016557"), agg[:amount_exec] # summed vol
146
+ assert_equal BigDecimal("0.016"), agg[:fee] # 0.01 + 0.006
147
+ assert_equal BigDecimal("10.0") / BigDecimal("0.00016557"), agg[:price] # VWAP = cost/vol
148
+ assert_equal "XXBTZUSD", agg[:pair]
149
+ end
150
+
151
+ def test_closed_orders_from_trades_ignores_unrelated_orders
152
+ stub_connection(:post, { "error" => [], "result" => { "count" => 1, "trades" => {
153
+ "TID-9" => { "ordertxid" => "OOTHER", "pair" => "XXBTZUSD", "type" => "buy",
154
+ "ordertype" => "limit", "price" => "1", "cost" => "1", "vol" => "1",
155
+ "fee" => "0", "time" => 1_700_000_000.0 } } } })
156
+ result = @client.closed_orders_from_trades(order_ids: ["OABC"], start: 1)
157
+ assert result.success?
158
+ assert_empty result.data
159
+ end
160
+
161
+ def test_closed_orders_from_trades_aggregates_partial_fills_across_pages
162
+ page1 = Honeymaker::Result::Success.new({ "error" => [], "result" => { "count" => 2, "trades" => {
163
+ "TID-1" => { "ordertxid" => "OABC", "pair" => "XXBTZUSD", "type" => "buy", "ordertype" => "limit",
164
+ "price" => "60000.0", "cost" => "6.0", "vol" => "0.0001", "fee" => "0.01", "time" => 1.0 } } } })
165
+ page2 = Honeymaker::Result::Success.new({ "error" => [], "result" => { "count" => 2, "trades" => {
166
+ "TID-2" => { "ordertxid" => "OABC", "pair" => "XXBTZUSD", "type" => "buy", "ordertype" => "limit",
167
+ "price" => "61000.0", "cost" => "4.0", "vol" => "0.00006557", "fee" => "0.006", "time" => 2.0 } } } })
168
+ @client.stubs(:get_trades_history).returns(page1, page2) # Mocha returns successive values per call
169
+ result = @client.closed_orders_from_trades(order_ids: ["OABC"], start: 1, max_pages: 5)
170
+ assert result.success?
171
+ agg = result.data["OABC"]
172
+ assert_equal BigDecimal("10.0"), agg[:quote_amount_exec] # 6 + 4 across both pages
173
+ assert_equal BigDecimal("0.00016557"), agg[:amount_exec]
174
+ end
175
+
129
176
  private
130
177
 
131
178
  def assert_strictly_increasing(values)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: honeymaker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.7
4
+ version: 0.9.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Deltabadger