mirror of
https://github.com/we-promise/sure.git
synced 2026-05-25 05:24:57 +00:00
* feat(providers): add Kraken exchange sync Adds family-scoped Kraken API-key connections, read-only balance and trade import, account setup/linking flows, provider status wiring, and focused test coverage. Closes #1758 * test(providers): avoid Kraken sample secret false positive * fix(providers): address Kraken review findings * fix(providers): address Kraken review cleanup * test(imports): stabilize transaction import ordering
123 lines
3.7 KiB
Ruby
123 lines
3.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class KrakenAccount::Processor
|
|
include KrakenAccount::UsdConverter
|
|
|
|
attr_reader :kraken_account
|
|
|
|
def initialize(kraken_account)
|
|
@kraken_account = kraken_account
|
|
end
|
|
|
|
def process
|
|
return unless kraken_account.current_account.present?
|
|
|
|
KrakenAccount::HoldingsProcessor.new(kraken_account).process
|
|
process_account!
|
|
process_trades
|
|
end
|
|
|
|
private
|
|
|
|
def target_currency
|
|
kraken_account.kraken_item&.family&.currency
|
|
end
|
|
|
|
def process_account!
|
|
account = kraken_account.current_account
|
|
amount, stale, rate_date = convert_from_usd((kraken_account.current_balance || 0).to_d, date: Date.current)
|
|
|
|
account.update!(
|
|
balance: amount,
|
|
cash_balance: 0,
|
|
currency: target_currency
|
|
)
|
|
|
|
kraken_account.update!(extra: kraken_account.extra.to_h.deep_merge(build_stale_extra(stale, rate_date, Date.current)))
|
|
end
|
|
|
|
def process_trades
|
|
raw_trades.each do |txid, trade|
|
|
process_trade(txid, trade)
|
|
end
|
|
rescue StandardError => e
|
|
Rails.logger.error "KrakenAccount::Processor - trade processing failed: #{e.message}"
|
|
end
|
|
|
|
def raw_trades
|
|
kraken_account.raw_transactions_payload&.dig("trades") || {}
|
|
end
|
|
|
|
def process_trade(txid, trade)
|
|
account = kraken_account.current_account
|
|
return unless account
|
|
|
|
external_id = "kraken_trade_#{txid}"
|
|
return if account.entries.exists?(external_id: external_id, source: "kraken")
|
|
|
|
type = trade["type"].to_s.downcase
|
|
return unless %w[buy sell].include?(type)
|
|
|
|
pair = trade["pair"].to_s
|
|
base_symbol, quote_symbol = infer_pair_symbols(pair, trade)
|
|
return if base_symbol.blank?
|
|
|
|
qty = trade["vol"].to_d
|
|
return if qty.zero?
|
|
|
|
price = trade["price"].to_d
|
|
cost = trade["cost"].presence&.to_d
|
|
cost ||= (qty * price).round(8)
|
|
fee = trade["fee"].presence&.to_d || 0
|
|
currency = quote_symbol.presence || "USD"
|
|
date = Time.zone.at(trade["time"].to_d).to_date
|
|
security = KrakenAccount::SecurityResolver.resolve("CRYPTO:#{base_symbol}", base_symbol)
|
|
return unless security
|
|
|
|
entry_amount = type == "buy" ? -cost : cost
|
|
trade_qty = type == "buy" ? qty : -qty
|
|
label = type == "buy" ? "Buy" : "Sell"
|
|
|
|
account.entries.create!(
|
|
date: date,
|
|
name: "#{label} #{qty.round(8)} #{base_symbol}",
|
|
amount: entry_amount,
|
|
currency: currency,
|
|
external_id: external_id,
|
|
source: "kraken",
|
|
notes: trade["ordertxid"].presence,
|
|
entryable: Trade.new(
|
|
security: security,
|
|
qty: trade_qty,
|
|
price: price,
|
|
currency: currency,
|
|
fee: fee,
|
|
investment_activity_label: label
|
|
)
|
|
)
|
|
rescue StandardError => e
|
|
Rails.logger.error "KrakenAccount::Processor - failed to process trade #{txid}: #{e.message}"
|
|
end
|
|
|
|
def infer_pair_symbols(pair, trade)
|
|
pair_metadata = kraken_account.raw_payload&.dig("pair_metadata") || {}
|
|
metadata = pair_metadata[pair] || pair_metadata.values.find { |candidate| candidate["altname"].to_s == pair }
|
|
normalizer = KrakenAccount::AssetNormalizer.new(kraken_account.raw_payload&.dig("asset_metadata") || {})
|
|
|
|
if metadata
|
|
base = normalizer.normalize(metadata["base"])[:symbol]
|
|
quote = normalizer.normalize(metadata["quote"])[:symbol]
|
|
return [ base, quote ]
|
|
end
|
|
|
|
altname = trade["pair"].to_s
|
|
%w[USDT USDC USD EUR GBP BTC ETH].each do |quote|
|
|
next unless altname.end_with?(quote)
|
|
|
|
return [ normalizer.normalize(altname.delete_suffix(quote))[:symbol], quote ]
|
|
end
|
|
|
|
[ altname, "USD" ]
|
|
end
|
|
end
|