From 087faf34cdeb5aede8d1d2e9ba875d58f6a5eae5 Mon Sep 17 00:00:00 2001 From: Daniel Sienkiewicz Date: Wed, 22 Nov 2023 00:45:35 +0100 Subject: [PATCH 1/8] Better handling of message handlers, Jason encoder for messages --- .gitignore | 3 + lib/xtb_client/messages.ex | 79 ---------------- lib/xtb_client/messages/balance_info.ex | 42 +-------- lib/xtb_client/messages/calendar_info.ex | 20 ++-- lib/xtb_client/messages/calendar_infos.ex | 17 +--- lib/xtb_client/messages/candle.ex | 93 +++++++++++-------- lib/xtb_client/messages/candles.ex | 20 ---- lib/xtb_client/messages/chart_last.ex | 7 +- lib/xtb_client/messages/chart_range.ex | 13 +-- .../messages/commission_definition.ex | 13 +-- lib/xtb_client/messages/date_range.ex | 1 - lib/xtb_client/messages/day.ex | 2 +- lib/xtb_client/messages/keep_alive.ex | 13 +-- lib/xtb_client/messages/margin_mode.ex | 2 +- lib/xtb_client/messages/margin_trade.ex | 13 +-- lib/xtb_client/messages/messages.ex | 83 +++++++++++++++++ lib/xtb_client/messages/news_info.ex | 42 ++++----- lib/xtb_client/messages/news_infos.ex | 21 ++--- lib/xtb_client/messages/operation.ex | 4 +- lib/xtb_client/messages/period.ex | 18 ++-- lib/xtb_client/messages/profit_calculation.ex | 34 +++---- lib/xtb_client/messages/profit_info.ex | 13 +-- lib/xtb_client/messages/profit_mode.ex | 4 +- lib/xtb_client/messages/quotations.ex | 31 +------ lib/xtb_client/messages/quote.ex | 7 +- lib/xtb_client/messages/quote_id.ex | 2 +- lib/xtb_client/messages/rate_infos.ex | 22 ++--- lib/xtb_client/{ => messages}/rate_limit.ex | 1 + lib/xtb_client/messages/server_time.ex | 13 +-- lib/xtb_client/messages/step.ex | 3 +- lib/xtb_client/messages/step_rule.ex | 5 +- lib/xtb_client/messages/step_rules.ex | 18 +--- lib/xtb_client/messages/symbol_info.ex | 85 ++++++++--------- lib/xtb_client/messages/symbol_infos.ex | 17 +--- lib/xtb_client/messages/symbol_volume.ex | 5 +- lib/xtb_client/messages/tick_price.ex | 19 ++-- lib/xtb_client/messages/tick_prices.ex | 35 +++---- lib/xtb_client/messages/trade_info.ex | 25 ++--- lib/xtb_client/messages/trade_infos.ex | 38 +++----- lib/xtb_client/messages/trade_status.ex | 19 ++-- lib/xtb_client/messages/trade_transaction.ex | 34 +++---- .../messages/trade_transaction_status.ex | 50 +++++----- lib/xtb_client/messages/trade_type.ex | 4 +- lib/xtb_client/messages/trades.ex | 3 +- lib/xtb_client/messages/trading_hour.ex | 9 +- lib/xtb_client/messages/trading_hours.ex | 34 +++---- lib/xtb_client/messages/transaction_status.ex | 2 +- lib/xtb_client/messages/user.ex | 39 ++++---- lib/xtb_client/messages/version.ex | 10 +- 49 files changed, 423 insertions(+), 664 deletions(-) delete mode 100644 lib/xtb_client/messages.ex create mode 100644 lib/xtb_client/messages/messages.ex rename lib/xtb_client/{ => messages}/rate_limit.ex (98%) diff --git a/.gitignore b/.gitignore index 5188c2e..c5b9e00 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,6 @@ npm-debug.log # VSCode .vscode/ + +# PLTs +priv/plts/ diff --git a/lib/xtb_client/messages.ex b/lib/xtb_client/messages.ex deleted file mode 100644 index 25ba1bb..0000000 --- a/lib/xtb_client/messages.ex +++ /dev/null @@ -1,79 +0,0 @@ -defmodule XtbClient.Messages do - alias XtbClient.Messages.{ - BalanceInfo, - CalendarInfos, - Candles, - CommissionDefinition, - KeepAlive, - MarginTrade, - NewsInfos, - Period, - ProfitCalculation, - ProfitInfo, - Quotations, - RateInfos, - ServerTime, - StepRules, - SymbolInfo, - SymbolInfos, - TickPrices, - TradeInfos, - TradeStatus, - TradeTransaction, - TradeTransactionStatus, - TradingHours, - UserInfo, - Version - } - - def format_period(period) do - Period.format(period) - end - - def parse_period(value) do - Period.parse(value) - end - - @message_handlers [ - BalanceInfo, - CalendarInfos, - Candles, - CommissionDefinition, - KeepAlive, - MarginTrade, - NewsInfos, - ProfitCalculation, - ProfitInfo, - Quotations, - RateInfos, - ServerTime, - StepRules, - SymbolInfo, - SymbolInfos, - TickPrices, - TradeInfos, - TradeStatus, - TradeTransaction, - TradeTransactionStatus, - TradingHours, - UserInfo, - Version - ] - - def decode_message(method, data) do - result = - @message_handlers - |> Enum.map(& &1.match(method, data)) - |> Enum.find(fn x -> - case x do - {:ok, _} = res -> res - {:no_match} -> false - end - end) - - case result do - {:ok, mapped_result} -> mapped_result - _ -> {:error, "No handler found for method `#{method}` with data `#{inspect(data)}`."} - end - end -end diff --git a/lib/xtb_client/messages/balance_info.ex b/lib/xtb_client/messages/balance_info.ex index 15f36b9..b84e5bb 100644 --- a/lib/xtb_client/messages/balance_info.ex +++ b/lib/xtb_client/messages/balance_info.ex @@ -47,7 +47,6 @@ defmodule XtbClient.Messages.BalanceInfo do :stock_lock, :stock_value ] - @derive Jason.Encoder defstruct balance: 0.0, cash_stock_value: 0.0, @@ -74,37 +73,6 @@ defmodule XtbClient.Messages.BalanceInfo do "stockLock" => stock_lock, "stockValue" => stock_value }) - when is_number(balance) and is_number(cash_stock_value) and is_number(credit) and - is_binary(currency) and is_number(equity) and is_number(equity_fx) and - is_number(margin) and is_number(margin_free) and is_number(margin_level) and - is_number(stock_lock) and is_number(stock_value) do - %__MODULE__{ - balance: balance, - cash_stock_value: cash_stock_value, - credit: credit, - currency: currency, - equity: equity, - equity_fx: equity_fx, - margin: margin, - margin_free: margin_free, - margin_level: margin_level, - stock_lock: stock_lock, - stock_value: stock_value - } - end - - def new(%{ - "balance" => balance, - "cashStockValue" => cash_stock_value, - "credit" => credit, - "equity" => equity, - "equityFX" => equity_fx, - "margin" => margin, - "marginFree" => margin_free, - "marginLevel" => margin_level, - "stockLock" => stock_lock, - "stockValue" => stock_value - }) when is_number(balance) and is_number(cash_stock_value) and is_number(credit) and is_number(equity) and is_number(equity_fx) and is_number(margin) and is_number(margin_free) and is_number(margin_level) and @@ -113,7 +81,7 @@ defmodule XtbClient.Messages.BalanceInfo do balance: balance, cash_stock_value: cash_stock_value, credit: credit, - currency: "", + currency: currency || "", equity: equity, equity_fx: equity_fx, margin: margin, @@ -123,12 +91,4 @@ defmodule XtbClient.Messages.BalanceInfo do stock_value: stock_value } end - - def match(method, data) when method in ["getBalance", "getMarginLevel"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/calendar_info.ex b/lib/xtb_client/messages/calendar_info.ex index 734b931..63d6b0c 100644 --- a/lib/xtb_client/messages/calendar_info.ex +++ b/lib/xtb_client/messages/calendar_info.ex @@ -25,7 +25,6 @@ defmodule XtbClient.Messages.CalendarInfo do } @enforce_keys [:country, :current, :forecast, :impact, :period, :previous, :time, :title] - @derive Jason.Encoder defstruct country: "", current: "", @@ -45,19 +44,16 @@ defmodule XtbClient.Messages.CalendarInfo do "previous" => previous, "time" => time_value, "title" => title - }) - when is_binary(country) and is_binary(current) and is_binary(forecast) and - is_binary(impact) and is_binary(period) and is_binary(previous) and - is_number(time_value) and is_binary(title) do + }) do %__MODULE__{ - country: country, - current: current, - forecast: forecast, - impact: impact, - period: period, - previous: previous, + country: country || "", + current: current || "", + forecast: forecast || "", + impact: impact || "", + period: period || "", + previous: previous || "", time: DateTime.from_unix!(time_value, :millisecond), - title: title + title: title || "" } end end diff --git a/lib/xtb_client/messages/calendar_infos.ex b/lib/xtb_client/messages/calendar_infos.ex index d758bf4..7131fdd 100644 --- a/lib/xtb_client/messages/calendar_infos.ex +++ b/lib/xtb_client/messages/calendar_infos.ex @@ -1,21 +1,22 @@ defmodule XtbClient.Messages.CalendarInfos do - alias XtbClient.Messages.{CalendarInfo} - @moduledoc """ Query result for list of `XtbClient.Messages.CalendarInfo`s. - + ## Parameters - `data` array or results. - + ## Handled Api methods - `getCalendar` """ + alias XtbClient.Messages.CalendarInfo + @type t :: %__MODULE__{ data: [CalendarInfo.t()] } @enforce_keys [:data] + @derive Jason.Encoder defstruct data: [] def new(data) when is_list(data) do @@ -25,12 +26,4 @@ defmodule XtbClient.Messages.CalendarInfos do |> Enum.map(&CalendarInfo.new(&1)) } end - - def match(method, data) when method in ["getCalendar"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/candle.ex b/lib/xtb_client/messages/candle.ex index 33309b0..0297e4b 100644 --- a/lib/xtb_client/messages/candle.ex +++ b/lib/xtb_client/messages/candle.ex @@ -1,6 +1,4 @@ defmodule XtbClient.Messages.Candle do - alias XtbClient.Messages.QuoteId - @moduledoc """ Info representing aggregated price & volume values for candle. @@ -18,6 +16,8 @@ defmodule XtbClient.Messages.Candle do - `symbol` symbol name. """ + alias XtbClient.Messages.QuoteId + @type t :: %__MODULE__{ open: float(), high: float(), @@ -41,7 +41,6 @@ defmodule XtbClient.Messages.Candle do :quote_id, :symbol ] - @derive Jason.Encoder defstruct open: 0.0, high: 0.0, @@ -55,29 +54,30 @@ defmodule XtbClient.Messages.Candle do def new( %{ - "open" => open, - "high" => high, - "low" => low, - "close" => close, "vol" => vol, "ctm" => ctm_value, - "ctmString" => ctm_string - }, - digits + "ctmString" => ctm_string, + "symbol" => symbol + } = args ) - when is_number(open) and is_number(high) and is_number(low) and is_number(close) and - is_number(vol) and is_number(ctm_value) and is_binary(ctm_string) and - is_number(digits) do - %__MODULE__{ - open: to_base_currency(open, digits), - high: to_base_currency(open + high, digits), - low: to_base_currency(open + low, digits), - close: to_base_currency(open + close, digits), - vol: vol, - ctm: DateTime.from_unix!(ctm_value, :millisecond), - ctm_string: ctm_string, - quote_id: nil, - symbol: "" + when is_number(vol) and is_number(ctm_value) do + value = args |> Map.delete(["vol", "ctm", "ctmString", "symbol"]) |> new() + + %{ + value + | vol: vol, + ctm: DateTime.from_unix!(ctm_value, :millisecond), + ctm_string: ctm_string || "", + symbol: symbol || "" + } + end + + def new(%{"quoteId" => quote_id} = args) when is_integer(quote_id) do + value = args |> Map.delete(["quoteId"]) |> new() + + %{ + value + | quote_id: QuoteId.parse(quote_id) } end @@ -85,28 +85,43 @@ defmodule XtbClient.Messages.Candle do "open" => open, "high" => high, "low" => low, - "close" => close, - "vol" => vol, - "ctm" => ctm_value, - "ctmString" => ctm_string, - "quoteId" => quote_id, - "symbol" => symbol + "close" => close }) - when is_number(open) and is_number(high) and is_number(low) and is_number(close) and - is_number(vol) and - is_number(ctm_value) and is_binary(ctm_string) and - is_integer(quote_id) and - is_binary(symbol) do + when is_number(open) and is_number(high) and is_number(low) and is_number(close) do %__MODULE__{ open: open, high: high, low: low, close: close, - vol: vol, - ctm: DateTime.from_unix!(ctm_value, :millisecond), - ctm_string: ctm_string, - quote_id: QuoteId.parse(quote_id), - symbol: symbol + vol: 0.0, + ctm: nil, + ctm_string: nil, + quote_id: nil, + symbol: nil + } + end + + def new( + %{ + "open" => open, + "high" => high, + "low" => low, + "close" => close + }, + digits + ) + when is_number(open) and is_number(high) and is_number(low) and is_number(close) and + is_number(digits) do + %__MODULE__{ + open: to_base_currency(open, digits), + high: to_base_currency(open + high, digits), + low: to_base_currency(open + low, digits), + close: to_base_currency(open + close, digits), + vol: 0.0, + ctm: nil, + ctm_string: nil, + quote_id: nil, + symbol: nil } end diff --git a/lib/xtb_client/messages/candles.ex b/lib/xtb_client/messages/candles.ex index 70689e3..af8eb82 100644 --- a/lib/xtb_client/messages/candles.ex +++ b/lib/xtb_client/messages/candles.ex @@ -12,7 +12,6 @@ defmodule XtbClient.Messages.Candles do } @enforce_keys [:symbol] - @derive Jason.Encoder defstruct symbol: "" @@ -20,23 +19,4 @@ defmodule XtbClient.Messages.Candles do %__MODULE__{symbol: symbol} end end - - alias XtbClient.Messages.{Candle} - - @moduledoc """ - Query result for `XtbClient.Messages.Candle`s. - - Returns one `XtbClient.Messages.Candle` at a time. - - ## Handled Api methods - - `getCandles` - """ - - def match(method, data) when method in ["getCandles"] do - {:ok, Candle.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/chart_last.ex b/lib/xtb_client/messages/chart_last.ex index bc9d21d..053dc91 100644 --- a/lib/xtb_client/messages/chart_last.ex +++ b/lib/xtb_client/messages/chart_last.ex @@ -1,7 +1,5 @@ defmodule XtbClient.Messages.ChartLast do defmodule Query do - alias XtbClient.Messages.Period - @moduledoc """ Parameters for last chart query. @@ -11,6 +9,8 @@ defmodule XtbClient.Messages.ChartLast do - `symbol` symbol name. """ + alias XtbClient.Messages.Period + @type t :: %__MODULE__{ period: Period.minute_period(), start: integer(), @@ -18,7 +18,6 @@ defmodule XtbClient.Messages.ChartLast do } @enforce_keys [:period, :start, :symbol] - @derive Jason.Encoder defstruct period: :h1, start: 0, @@ -30,7 +29,7 @@ defmodule XtbClient.Messages.ChartLast do @spec new(%{ :period => Period.t(), :start => Calendar.datetime(), - :symbol => binary + :symbol => String.t() }) :: XtbClient.Messages.ChartLast.Query.t() def new(%{period: period, start: start, symbol: symbol}) when is_atom(period) and not is_nil(start) and is_binary(symbol) do diff --git a/lib/xtb_client/messages/chart_range.ex b/lib/xtb_client/messages/chart_range.ex index 2e7a9c6..8976274 100644 --- a/lib/xtb_client/messages/chart_range.ex +++ b/lib/xtb_client/messages/chart_range.ex @@ -1,7 +1,5 @@ defmodule XtbClient.Messages.ChartRange do defmodule Query do - alias XtbClient.Messages.{DateRange, Period} - @moduledoc """ Parameters for chart range query. @@ -20,6 +18,8 @@ defmodule XtbClient.Messages.ChartRange do It is possible for API to return fewer chart candles than set in tick field. """ + alias XtbClient.Messages.{DateRange, Period} + @type t :: %__MODULE__{ start: integer(), end: integer(), @@ -28,8 +28,7 @@ defmodule XtbClient.Messages.ChartRange do ticks: integer() } - @enforce_keys [:start, :end, :period, :symbol] - + @enforce_keys [:start, :end, :period, :symbol, :ticks] @derive Jason.Encoder defstruct start: nil, end: nil, @@ -39,7 +38,8 @@ defmodule XtbClient.Messages.ChartRange do def new(%{ticks: ticks} = args) when is_number(ticks) do - value = __MODULE__.new(Map.delete(args, :ticks)) + value = args |> Map.delete(:ticks) |> new() + %{value | ticks: ticks} end @@ -53,7 +53,8 @@ defmodule XtbClient.Messages.ChartRange do start: start, end: end_value, period: Period.format(period), - symbol: symbol + symbol: symbol, + ticks: 0 } end end diff --git a/lib/xtb_client/messages/commission_definition.ex b/lib/xtb_client/messages/commission_definition.ex index d0fc081..bb2098d 100644 --- a/lib/xtb_client/messages/commission_definition.ex +++ b/lib/xtb_client/messages/commission_definition.ex @@ -1,11 +1,11 @@ defmodule XtbClient.Messages.CommissionDefinition do @moduledoc """ Query result for commission definition. - + ## Parameters - `commission` calculated commission in account currency, - `rate_of_exchange` rate of exchange between account currency and instrument base currency. - + ## Handled Api methods - `getCommissionDef` """ @@ -16,7 +16,6 @@ defmodule XtbClient.Messages.CommissionDefinition do } @enforce_keys [:commission, :rate_of_exchange] - @derive Jason.Encoder defstruct commission: 0.0, rate_of_exchange: 0.0 @@ -28,12 +27,4 @@ defmodule XtbClient.Messages.CommissionDefinition do rate_of_exchange: rate_of_exchange } end - - def match(method, data) when method in ["getCommissionDef"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/date_range.ex b/lib/xtb_client/messages/date_range.ex index 5c00d60..13c07af 100644 --- a/lib/xtb_client/messages/date_range.ex +++ b/lib/xtb_client/messages/date_range.ex @@ -9,7 +9,6 @@ defmodule XtbClient.Messages.DateRange do } @enforce_keys [:start, :end] - @derive Jason.Encoder defstruct start: nil, end: nil diff --git a/lib/xtb_client/messages/day.ex b/lib/xtb_client/messages/day.ex index be2ffc0..2a8569b 100644 --- a/lib/xtb_client/messages/day.ex +++ b/lib/xtb_client/messages/day.ex @@ -17,7 +17,7 @@ defmodule XtbClient.Messages.Day do @doc """ Parse an integer value as a valid atom representing day of week. """ - @spec parse(day_code()) :: t() + @spec parse(value :: day_code()) :: t() def parse(value) when is_integer(value) and value in 1..7 do parse_day(value) end diff --git a/lib/xtb_client/messages/keep_alive.ex b/lib/xtb_client/messages/keep_alive.ex index d8bde54..be3570a 100644 --- a/lib/xtb_client/messages/keep_alive.ex +++ b/lib/xtb_client/messages/keep_alive.ex @@ -1,10 +1,10 @@ defmodule XtbClient.Messages.KeepAlive do @moduledoc """ Info representing response from the server sent to keep alive command. - + ## Parameters - `timestamp` current timestamp. - + ## Handled Api methods - `getKeepAlive` """ @@ -14,7 +14,6 @@ defmodule XtbClient.Messages.KeepAlive do } @enforce_keys [:timestamp] - @derive Jason.Encoder defstruct timestamp: nil @@ -23,12 +22,4 @@ defmodule XtbClient.Messages.KeepAlive do timestamp: DateTime.from_unix!(timestamp_value, :millisecond) } end - - def match(method, data) when method in ["getKeepAlive"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/margin_mode.ex b/lib/xtb_client/messages/margin_mode.ex index ce687e3..9341f7a 100644 --- a/lib/xtb_client/messages/margin_mode.ex +++ b/lib/xtb_client/messages/margin_mode.ex @@ -9,7 +9,7 @@ defmodule XtbClient.Messages.MarginMode do @doc """ Parse an integer value as a valid atom for margin mode. """ - @spec parse(margin_code()) :: t() + @spec parse(value :: margin_code()) :: t() def parse(value) when is_number(value) and value in [101, 102, 103, 104] do parse_margin_mode(value) end diff --git a/lib/xtb_client/messages/margin_trade.ex b/lib/xtb_client/messages/margin_trade.ex index ff76d45..e1f725e 100644 --- a/lib/xtb_client/messages/margin_trade.ex +++ b/lib/xtb_client/messages/margin_trade.ex @@ -1,10 +1,10 @@ defmodule XtbClient.Messages.MarginTrade do @moduledoc """ Info about calculated margin in account currency. - + ## Properties - `margin` value of margin. - + ## Handled Api methods - `getMarginTrade` """ @@ -14,7 +14,6 @@ defmodule XtbClient.Messages.MarginTrade do } @enforce_keys [:margin] - @derive Jason.Encoder defstruct margin: 0.0 @@ -23,12 +22,4 @@ defmodule XtbClient.Messages.MarginTrade do margin: margin } end - - def match(method, data) when method in ["getMarginTrade"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/messages.ex b/lib/xtb_client/messages/messages.ex new file mode 100644 index 0000000..b1117c5 --- /dev/null +++ b/lib/xtb_client/messages/messages.ex @@ -0,0 +1,83 @@ +defmodule XtbClient.Messages do + @moduledoc """ + Module for handling messages from XTB Api. + """ + + alias XtbClient.Messages.{ + BalanceInfo, + CalendarInfos, + Candle, + CommissionDefinition, + KeepAlive, + MarginTrade, + NewsInfos, + ProfitCalculation, + ProfitInfo, + RateInfos, + ServerTime, + StepRules, + SymbolInfo, + SymbolInfos, + TickPrice, + TickPrices, + TradeInfos, + TradeStatus, + TradeTransaction, + TradeTransactionStatus, + TradingHours, + UserInfo, + Version + } + + def decode_message("getBalance", data), do: BalanceInfo.new(data) + def decode_message("getMarginLevel", data), do: BalanceInfo.new(data) + + def decode_message("getCalendar", data), do: CalendarInfos.new(data) + + def decode_message("getCandles", data), do: Candle.new(data) + + def decode_message("getCommissionDef", data), do: CommissionDefinition.new(data) + + def decode_message("getKeepAlive", data), do: KeepAlive.new(data) + + def decode_message("getMarginTrade", data), do: MarginTrade.new(data) + + def decode_message("getNews", data), do: NewsInfos.new(data) + + def decode_message("getProfitCalculation", data), do: ProfitCalculation.new(data) + + def decode_message("getProfits", data), do: ProfitInfo.new(data) + + def decode_message("getChartLastRequest", data), do: RateInfos.new(data) + def decode_message("getChartRangeRequest", data), do: RateInfos.new(data) + + def decode_message("getServerTime", data), do: ServerTime.new(data) + + def decode_message("getStepRules", data), do: StepRules.new(data) + + def decode_message("getSymbol", data), do: SymbolInfo.new(data) + + def decode_message("getAllSymbols", data), do: SymbolInfos.new(data) + + def decode_message("getTickPrices", data) when is_map(data) and map_size(data) > 1, + do: TickPrice.new(data) + + def decode_message("getTickPrices", %{"quotations" => data}) when is_list(data), + do: TickPrices.new(data) + + def decode_message("getTradeRecords", data), do: TradeInfos.new(data) + def decode_message("getTrades", data), do: TradeInfos.new(data) + def decode_message("getTradesHistory", data), do: TradeInfos.new(data) + + def decode_message("getTradeStatus", data), do: TradeStatus.new(data) + + def decode_message("tradeTransactionStatus", data), do: TradeTransactionStatus.new(data) + + def decode_message("tradeTransaction", data), do: TradeTransaction.new(data) + + def decode_message("getTradingHours", data), do: TradingHours.new(data) + + def decode_message("getCurrentUserData", data), do: UserInfo.new(data) + + def decode_message("getVersion", data), do: Version.new(data) +end diff --git a/lib/xtb_client/messages/news_info.ex b/lib/xtb_client/messages/news_info.ex index e3e777b..e92ac95 100644 --- a/lib/xtb_client/messages/news_info.ex +++ b/lib/xtb_client/messages/news_info.ex @@ -21,7 +21,6 @@ defmodule XtbClient.Messages.NewsInfo do } @enforce_keys [:body, :body_length, :key, :time, :time_string, :title] - @derive Jason.Encoder defstruct body: "", body_length: 0, @@ -30,24 +29,19 @@ defmodule XtbClient.Messages.NewsInfo do time_string: "", title: "" - def new(%{ - "body" => body, - "bodylen" => body_length, - "key" => key, - "time" => time_value, - "timeString" => time_string, - "title" => title - }) - when is_binary(body) and is_number(body_length) and - is_binary(key) and is_number(time_value) and is_binary(time_string) and - is_binary(title) do - %__MODULE__{ - body: body, - body_length: body_length, - key: key, - time: DateTime.from_unix!(time_value, :millisecond), - time_string: time_string, - title: title + def new( + %{ + "bodylen" => body_length, + "timeString" => time_string + } = args + ) + when is_number(body_length) do + value = args |> Map.drop(["bodylen", "timeString"]) |> new() + + %{ + value + | body_length: body_length, + time_string: time_string || "" } end @@ -57,16 +51,14 @@ defmodule XtbClient.Messages.NewsInfo do "time" => time_value, "title" => title }) - when is_binary(body) and - is_binary(key) and is_number(time_value) and - is_binary(title) do + when is_number(time_value) do %__MODULE__{ - body: body, + body: body || "", body_length: String.length(body), - key: key, + key: key || "", time: DateTime.from_unix!(time_value, :millisecond), time_string: "", - title: title + title: title || "" } end end diff --git a/lib/xtb_client/messages/news_infos.ex b/lib/xtb_client/messages/news_infos.ex index a0405f8..78754f5 100644 --- a/lib/xtb_client/messages/news_infos.ex +++ b/lib/xtb_client/messages/news_infos.ex @@ -1,21 +1,22 @@ defmodule XtbClient.Messages.NewsInfos do - alias XtbClient.Messages.NewsInfo - @moduledoc """ Query result for list of `XtbClient.Messages.NewsInfo`s. - + ## Parameters - `data` array or results. - + ## Handled Api methods - `getNews` """ + alias XtbClient.Messages.NewsInfo + @type t :: %__MODULE__{ data: [XtbClient.Messages.NewsInfo.t()] } @enforce_keys [:data] + @derive Jason.Encoder defstruct data: [] def new(data) when is_list(data) do @@ -27,14 +28,8 @@ defmodule XtbClient.Messages.NewsInfos do end def new(data) when is_map(data) do - NewsInfo.new(data) - end - - def match(method, data) when method in ["getNews"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} + %__MODULE__{ + data: [NewsInfo.new(data)] + } end end diff --git a/lib/xtb_client/messages/operation.ex b/lib/xtb_client/messages/operation.ex index dc4d674..12c3f36 100644 --- a/lib/xtb_client/messages/operation.ex +++ b/lib/xtb_client/messages/operation.ex @@ -18,7 +18,7 @@ defmodule XtbClient.Messages.Operation do @doc """ Parse an integer number as valid operation atom. """ - @spec parse(operation_code()) :: t() + @spec parse(value :: operation_code()) :: t() def parse(value) when value in 0..7 do parse_operation(value) end @@ -39,7 +39,7 @@ defmodule XtbClient.Messages.Operation do @doc """ Format operation atom as integer value. """ - @spec format(t()) :: operation_code() + @spec format(operation :: t()) :: operation_code() def format(operation) when is_atom(operation) do format_operation(operation) end diff --git a/lib/xtb_client/messages/period.ex b/lib/xtb_client/messages/period.ex index e6dad53..585d6d5 100644 --- a/lib/xtb_client/messages/period.ex +++ b/lib/xtb_client/messages/period.ex @@ -4,17 +4,17 @@ defmodule XtbClient.Messages.Period do """ @type t :: :m1 | :m5 | :m15 | :m30 | :h1 | :h4 | :d1 | :w1 | :mn1 - @type minute_period :: 1 | 5 | 15 | 30 | 60 | 240 | 1440 | 10080 | 43200 + @type minute_period :: 1 | 5 | 15 | 30 | 60 | 240 | 1440 | 10_080 | 43_200 @doc """ Formats period given as `Period` to number of minutes. """ - @spec format(t()) :: minute_period() + @spec format(period :: t()) :: minute_period() def format(period) when is_atom(period) do - format_perod(period) + format_period(period) end - defp format_perod(period) do + defp format_period(period) do case period do :m1 -> 1 :m5 -> 5 @@ -23,15 +23,15 @@ defmodule XtbClient.Messages.Period do :h1 -> 60 :h4 -> 240 :d1 -> 1440 - :w1 -> 10080 - :mn1 -> 43200 + :w1 -> 10_080 + :mn1 -> 43_200 end end @doc """ Parses value given as number of minutes to `Period` atom type. """ - @spec parse(minute_period()) :: t() + @spec parse(value :: minute_period()) :: t() def parse(value) when is_number(value) and value > 0 do parse_period(value) end @@ -45,8 +45,8 @@ defmodule XtbClient.Messages.Period do 60 -> :h1 240 -> :h4 1440 -> :d1 - 10080 -> :w1 - 43200 -> :mn1 + 10_080 -> :w1 + 43_200 -> :mn1 end end end diff --git a/lib/xtb_client/messages/profit_calculation.ex b/lib/xtb_client/messages/profit_calculation.ex index 812b63d..da0b8ae 100644 --- a/lib/xtb_client/messages/profit_calculation.ex +++ b/lib/xtb_client/messages/profit_calculation.ex @@ -1,7 +1,15 @@ defmodule XtbClient.Messages.ProfitCalculation do - defmodule Query do - alias XtbClient.Messages.{Operation} + @moduledoc """ + Query result for profit calculation. + + ## Parameters + - `profit` profit in account currency. + + ## Handled Api methods + - `getProfitCalculation` + """ + defmodule Query do @moduledoc """ Info about query for calculation of profit. @@ -13,6 +21,8 @@ defmodule XtbClient.Messages.ProfitCalculation do - `volume` volume in lots. """ + alias XtbClient.Messages.Operation + @type t :: %__MODULE__{ closePrice: float(), cmd: Operation.t(), @@ -22,7 +32,6 @@ defmodule XtbClient.Messages.ProfitCalculation do } @enforce_keys [:closePrice, :cmd, :openPrice, :symbol, :volume] - @derive Jason.Encoder defstruct closePrice: 0.0, cmd: nil, @@ -49,22 +58,11 @@ defmodule XtbClient.Messages.ProfitCalculation do end end - @moduledoc """ - Query result for profit calculation. - - ## Parameters - - `profit` profit in account currency. - - ## Handled Api methods - - `getProfitCalculation` - """ - @type t :: %__MODULE__{ profit: float() } @enforce_keys [:profit] - @derive Jason.Encoder defstruct profit: 0.0 @@ -73,12 +71,4 @@ defmodule XtbClient.Messages.ProfitCalculation do profit: profit } end - - def match(method, data) when method in ["getProfitCalculation"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/profit_info.ex b/lib/xtb_client/messages/profit_info.ex index 1814489..2dabbde 100644 --- a/lib/xtb_client/messages/profit_info.ex +++ b/lib/xtb_client/messages/profit_info.ex @@ -1,7 +1,7 @@ defmodule XtbClient.Messages.ProfitInfo do @moduledoc """ Result of profit calculation. - + ## Parameters - `order_id` order number, - `transaction_id` transaction ID, @@ -10,7 +10,7 @@ defmodule XtbClient.Messages.ProfitInfo do - `market_value` market value, - `profit_calc_price` profit calc price, - `profit_recalc_price` profit recalc price. - + ## Handled Api methods - `getProfits` """ @@ -34,7 +34,6 @@ defmodule XtbClient.Messages.ProfitInfo do :profit_calc_price, :profit_recalc_price ] - @derive Jason.Encoder defstruct order_id: nil, transaction_id: nil, @@ -67,12 +66,4 @@ defmodule XtbClient.Messages.ProfitInfo do profit_recalc_price: profit_recalc_price } end - - def match(method, data) when method in ["getProfits"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/profit_mode.ex b/lib/xtb_client/messages/profit_mode.ex index b4f7d6a..ecf4d6b 100644 --- a/lib/xtb_client/messages/profit_mode.ex +++ b/lib/xtb_client/messages/profit_mode.ex @@ -4,12 +4,12 @@ defmodule XtbClient.Messages.ProfitMode do """ @type t :: :forex | :cfd - @type proft_code :: 5 | 6 + @type profit_code :: 5 | 6 @doc """ Parse an integer value to valid atom of profit mode. """ - @spec parse(proft_code()) :: t() + @spec parse(value :: profit_code()) :: t() def parse(value) when is_number(value) and value in [5, 6] do parse_profit_mode(value) end diff --git a/lib/xtb_client/messages/quotations.ex b/lib/xtb_client/messages/quotations.ex index 6a03e2d..f64e4b9 100644 --- a/lib/xtb_client/messages/quotations.ex +++ b/lib/xtb_client/messages/quotations.ex @@ -18,8 +18,7 @@ defmodule XtbClient.Messages.Quotations do maxLevel: integer() } - @enforce_keys [:symbol] - + @enforce_keys [:symbol, :minArrivalTime, :maxLevel] @derive Jason.Encoder defstruct symbol: "", minArrivalTime: 0, @@ -32,37 +31,17 @@ defmodule XtbClient.Messages.Quotations do } = args ) when is_integer(min_arrival_time) and is_integer(max_level) do - value = - args - |> Map.delete(:min_arrival_time) - |> Map.delete(:max_level) - |> __MODULE__.new() + value = args |> Map.delete([:min_arrival_time, :max_level]) |> new() %{value | minArrivalTime: min_arrival_time, maxLevel: max_level} end def new(%{symbol: symbol}) when is_binary(symbol) do %__MODULE__{ - symbol: symbol + symbol: symbol, + minArrivalTime: 0, + maxLevel: nil } end end - - alias XtbClient.Messages.TickPrice - - @moduledoc """ - Query result for list of `XtbClient.Messages.TickPrice`s. - - ## Handled Api methods - - `getTickPrices` - """ - - def match(method, data) - when method in ["getTickPrices"] and is_map(data) and map_size(data) > 1 do - {:ok, TickPrice.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/quote.ex b/lib/xtb_client/messages/quote.ex index e0758f8..bf035c4 100644 --- a/lib/xtb_client/messages/quote.ex +++ b/lib/xtb_client/messages/quote.ex @@ -1,15 +1,15 @@ defmodule XtbClient.Messages.Quote do - alias XtbClient.Messages.Day - @moduledoc """ Info about quote for given day. - + Parameters: - `day` day of week, - `from` start time in `Time` CET / CEST time zone (see Daylight Saving Time, DST), - `to` end time in `Time` CET / CEST time zone (see Daylight Saving Time, DST). """ + alias XtbClient.Messages.Day + @type t :: %__MODULE__{ day: Day.t(), from: Time.t(), @@ -17,7 +17,6 @@ defmodule XtbClient.Messages.Quote do } @enforce_keys [:day, :from, :to] - @derive Jason.Encoder defstruct day: nil, from: nil, diff --git a/lib/xtb_client/messages/quote_id.ex b/lib/xtb_client/messages/quote_id.ex index 3fff70f..4bc8503 100644 --- a/lib/xtb_client/messages/quote_id.ex +++ b/lib/xtb_client/messages/quote_id.ex @@ -9,7 +9,7 @@ defmodule XtbClient.Messages.QuoteId do @doc """ Parse an integer number as valid atom for quote ID. """ - @spec parse(quote_code()) :: t() + @spec parse(value :: quote_code()) :: t() def parse(value) when is_integer(value) and value in [1, 2, 3, 4, 5, 6] do parse_quote_id(value) end diff --git a/lib/xtb_client/messages/rate_infos.ex b/lib/xtb_client/messages/rate_infos.ex index 26cfca0..566a263 100644 --- a/lib/xtb_client/messages/rate_infos.ex +++ b/lib/xtb_client/messages/rate_infos.ex @@ -1,25 +1,25 @@ defmodule XtbClient.Messages.RateInfos do - alias XtbClient.Messages.{Candle} - @moduledoc """ Query result for list of `XtbClient.Messages.Candle`s. - + ## Parameters - `digits` number of decimal places, - `data` array of results. - + ## Handled Api methods - `getChartLastRequest` - `getChartRangeRequest` """ + alias XtbClient.Messages.Candle + @type t :: %__MODULE__{ digits: integer(), data: [Candle.t()] } @enforce_keys [:digits, :data] - + @derive Jason.Encoder defstruct digits: 0, data: [] @@ -31,17 +31,7 @@ defmodule XtbClient.Messages.RateInfos do is_list(rate_infos) do %__MODULE__{ digits: digits, - data: - rate_infos - |> Enum.map(&Candle.new(&1, digits)) + data: Enum.map(rate_infos, &Candle.new(&1, digits)) } end - - def match(method, data) when method in ["getChartLastRequest", "getChartRangeRequest"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/rate_limit.ex b/lib/xtb_client/messages/rate_limit.ex similarity index 98% rename from lib/xtb_client/rate_limit.ex rename to lib/xtb_client/messages/rate_limit.ex index 74a565f..78028f9 100644 --- a/lib/xtb_client/rate_limit.ex +++ b/lib/xtb_client/messages/rate_limit.ex @@ -9,6 +9,7 @@ defmodule XtbClient.RateLimit do } @enforce_keys [:limit_interval, :time_stamp] + @derive Jason.Encoder defstruct limit_interval: 200, time_stamp: 0 diff --git a/lib/xtb_client/messages/server_time.ex b/lib/xtb_client/messages/server_time.ex index c2d3db6..ed9431d 100644 --- a/lib/xtb_client/messages/server_time.ex +++ b/lib/xtb_client/messages/server_time.ex @@ -16,7 +16,6 @@ defmodule XtbClient.Messages.ServerTime do } @enforce_keys [:time, :time_string] - @derive Jason.Encoder defstruct time: nil, time_string: "" @@ -25,18 +24,10 @@ defmodule XtbClient.Messages.ServerTime do "time" => time_value, "timeString" => time_string }) - when is_number(time_value) and is_binary(time_string) do + when is_number(time_value) do %__MODULE__{ time: DateTime.from_unix!(time_value, :millisecond), - time_string: time_string + time_string: time_string || "" } end - - def match(method, data) when method in ["getServerTime"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/step.ex b/lib/xtb_client/messages/step.ex index 56b7cfc..35fd55d 100644 --- a/lib/xtb_client/messages/step.ex +++ b/lib/xtb_client/messages/step.ex @@ -1,7 +1,7 @@ defmodule XtbClient.Messages.Step do @moduledoc """ Info about one step rule. - + ## Parameters - `from_value` lower border of the volume range, - `step` lotStep value in the given volume range. @@ -13,7 +13,6 @@ defmodule XtbClient.Messages.Step do } @enforce_keys [:from_value, :step] - @derive Jason.Encoder defstruct from_value: 0.0, step: 0.0 diff --git a/lib/xtb_client/messages/step_rule.ex b/lib/xtb_client/messages/step_rule.ex index 1dfb1ac..071796b 100644 --- a/lib/xtb_client/messages/step_rule.ex +++ b/lib/xtb_client/messages/step_rule.ex @@ -17,7 +17,6 @@ defmodule XtbClient.Messages.StepRule do } @enforce_keys [:id, :name, :steps] - @derive Jason.Encoder defstruct id: 0, name: "", @@ -28,10 +27,10 @@ defmodule XtbClient.Messages.StepRule do "name" => name, "steps" => steps }) - when is_number(id) and is_binary(name) and is_list(steps) do + when is_number(id) and is_list(steps) do %__MODULE__{ id: id, - name: name, + name: name || "", steps: Enum.map(steps, &Step.new(&1)) } end diff --git a/lib/xtb_client/messages/step_rules.ex b/lib/xtb_client/messages/step_rules.ex index 87bfeca..f1948d6 100644 --- a/lib/xtb_client/messages/step_rules.ex +++ b/lib/xtb_client/messages/step_rules.ex @@ -1,22 +1,22 @@ defmodule XtbClient.Messages.StepRules do - alias XtbClient.Messages.StepRule - @moduledoc """ Query result for list of `XtbClient.Messages.StepRule`s. - + ## Parameters - `data` array or results. - + ## Handled Api methods - `getStepRules` """ + alias XtbClient.Messages.StepRule + @type t :: %__MODULE__{ data: [StepRule.t()] } @enforce_keys [:data] - + @derive Jason.Encoder defstruct data: [] def new(data) @@ -27,12 +27,4 @@ defmodule XtbClient.Messages.StepRules do |> Enum.map(&StepRule.new(&1)) } end - - def match(method, data) when method in ["getStepRules"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/symbol_info.ex b/lib/xtb_client/messages/symbol_info.ex index 2598311..9d1cac8 100644 --- a/lib/xtb_client/messages/symbol_info.ex +++ b/lib/xtb_client/messages/symbol_info.ex @@ -1,31 +1,4 @@ defmodule XtbClient.Messages.SymbolInfo do - defmodule Query do - @moduledoc """ - Info about the query for symbol info. - - ## Parameters - - `symbol` symbol name. - """ - - @type t :: %__MODULE__{ - symbol: String.t() - } - - @enforce_keys [:symbol] - - @derive Jason.Encoder - defstruct symbol: "" - - def new(symbol) - when is_binary(symbol) do - %__MODULE__{ - symbol: symbol - } - end - end - - alias XtbClient.Messages.{MarginMode, ProfitMode, QuoteId} - @moduledoc """ Information relevant to the symbol of security. @@ -83,6 +56,32 @@ defmodule XtbClient.Messages.SymbolInfo do - `getSymbol` """ + alias XtbClient.Messages.{MarginMode, ProfitMode, QuoteId} + + defmodule Query do + @moduledoc """ + Info about the query for symbol info. + + ## Parameters + - `symbol` symbol name. + """ + + @type t :: %__MODULE__{ + symbol: String.t() + } + + @enforce_keys [:symbol] + @derive Jason.Encoder + defstruct symbol: "" + + def new(symbol) + when is_binary(symbol) do + %__MODULE__{ + symbol: symbol + } + end + end + @type t :: %__MODULE__{ ask: float(), bid: float(), @@ -180,7 +179,6 @@ defmodule XtbClient.Messages.SymbolInfo do :trailing_enabled, :type ] - @derive Jason.Encoder defstruct ask: 0.0, bid: 0.0, @@ -278,9 +276,8 @@ defmodule XtbClient.Messages.SymbolInfo do "type" => type }) when is_number(ask) and is_number(bid) and - is_binary(category_name) and is_number(contract_size) and - is_binary(currency) and is_boolean(currency_pair) and is_binary(currency_profit) and - is_binary(description) and is_binary(group_name) and + is_number(contract_size) and + is_boolean(currency_pair) and is_number(high) and is_number(initial_margin) and is_number(instant_max_volume) and is_number(leverage) and is_boolean(long_only) and is_number(lot_max) and is_number(lot_min) and is_number(lot_step) and @@ -294,20 +291,20 @@ defmodule XtbClient.Messages.SymbolInfo do is_number(swap_rollover_3_days) and is_boolean(swap_enabled) and is_number(swap_long) and is_number(swap_short) and is_number(swap_type) and - is_binary(symbol) and is_number(tick_size) and is_number(tick_value) and - is_number(time_value) and is_binary(time_string) and + is_number(tick_size) and is_number(tick_value) and + is_number(time_value) and is_boolean(trailing_enabled) and is_number(type) do %__MODULE__{ ask: ask, bid: bid, - category_name: category_name, + category_name: category_name || "", contract_size: contract_size, - currency: currency, + currency: currency || "", currency_pair: currency_pair, - currency_profit: currency_profit, - description: description, + currency_profit: currency_profit || "", + description: description || "", expiration: expiration, - group_name: group_name, + group_name: group_name || "", high: high, initial_margin: initial_margin, instant_max_volume: instant_max_volume, @@ -337,21 +334,13 @@ defmodule XtbClient.Messages.SymbolInfo do swap_long: swap_long, swap_short: swap_short, swap_type: swap_type, - symbol: symbol, + symbol: symbol || "", tick_size: tick_size, tick_value: tick_value, time: DateTime.from_unix!(time_value, :millisecond), - time_string: time_string, + time_string: time_string || "", trailing_enabled: trailing_enabled, type: type } end - - def match(method, data) when method in ["getSymbol"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/symbol_infos.ex b/lib/xtb_client/messages/symbol_infos.ex index a89683e..67f17fc 100644 --- a/lib/xtb_client/messages/symbol_infos.ex +++ b/lib/xtb_client/messages/symbol_infos.ex @@ -1,21 +1,22 @@ defmodule XtbClient.Messages.SymbolInfos do - alias XtbClient.Messages.{SymbolInfo} - @moduledoc """ Query result for list of `XtbClient.Messages.SymbolInfo`s. - + ## Parameters - `data` array or results. - + ## Handled Api methods - `getAllSymbols` """ + alias XtbClient.Messages.SymbolInfo + @type t :: %__MODULE__{ data: [SymbolInfo.t()] } @enforce_keys [:data] + @derive Jason.Encoder defstruct data: [] def new(data) when is_list(data) do @@ -25,12 +26,4 @@ defmodule XtbClient.Messages.SymbolInfos do |> Enum.map(&SymbolInfo.new(&1)) } end - - def match(method, data) when method in ["getAllSymbols"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/symbol_volume.ex b/lib/xtb_client/messages/symbol_volume.ex index 2dd5dc8..ec62b8c 100644 --- a/lib/xtb_client/messages/symbol_volume.ex +++ b/lib/xtb_client/messages/symbol_volume.ex @@ -13,15 +13,14 @@ defmodule XtbClient.Messages.SymbolVolume do } @enforce_keys [:symbol, :volume] - @derive Jason.Encoder defstruct symbol: "", volume: 0.0 def new(%{symbol: symbol, volume: volume}) - when is_binary(symbol) and is_number(volume) do + when is_number(volume) do %__MODULE__{ - symbol: symbol, + symbol: symbol || "", volume: volume } end diff --git a/lib/xtb_client/messages/tick_price.ex b/lib/xtb_client/messages/tick_price.ex index cd7ca65..86f1294 100644 --- a/lib/xtb_client/messages/tick_price.ex +++ b/lib/xtb_client/messages/tick_price.ex @@ -1,6 +1,4 @@ defmodule XtbClient.Messages.TickPrice do - alias XtbClient.Messages.QuoteId - @moduledoc """ Info about one tick of price. @@ -20,6 +18,8 @@ defmodule XtbClient.Messages.TickPrice do - `timestamp` timestamp. """ + alias XtbClient.Messages.QuoteId + @type t :: %__MODULE__{ ask: float(), ask_volume: integer() | nil, @@ -41,15 +41,17 @@ defmodule XtbClient.Messages.TickPrice do :ask_volume, :bid, :bid_volume, + :exe_mode, :high, :level, :low, + :quote_id, :spread_raw, :spread_table, :symbol, :timestamp ] - + @derive Jason.Encoder defstruct ask: 0.0, ask_volume: nil, bid: 0.0, @@ -70,7 +72,8 @@ defmodule XtbClient.Messages.TickPrice do } = args ) when is_integer(exemode) do - value = __MODULE__.new(Map.delete(args, "exemode")) + value = args |> Map.delete(["exemode"]) |> new() + %{value | exe_mode: exemode} end @@ -80,7 +83,8 @@ defmodule XtbClient.Messages.TickPrice do } = args ) when is_integer(quote_id) do - value = __MODULE__.new(Map.delete(args, "quoteId")) + value = args |> Map.delete(["quoteId"]) |> new() + %{value | quote_id: QuoteId.parse(quote_id)} end @@ -103,19 +107,20 @@ defmodule XtbClient.Messages.TickPrice do is_integer(level) and is_number(low) and is_number(spread_raw) and is_number(spread_table) and - is_binary(symbol) and is_integer(timestamp_value) do %__MODULE__{ ask: ask, ask_volume: ask_volume, bid: bid, bid_volume: bid_volume, + exe_mode: nil, high: high, level: level, low: low, + quote_id: nil, spread_raw: spread_raw, spread_table: spread_table, - symbol: symbol, + symbol: symbol || "", timestamp: DateTime.from_unix!(timestamp_value, :millisecond) } end diff --git a/lib/xtb_client/messages/tick_prices.ex b/lib/xtb_client/messages/tick_prices.ex index 91dafc6..4c24899 100644 --- a/lib/xtb_client/messages/tick_prices.ex +++ b/lib/xtb_client/messages/tick_prices.ex @@ -1,4 +1,16 @@ defmodule XtbClient.Messages.TickPrices do + @moduledoc """ + Query result for list of `XtbClient.Messages.TickPrice`s. + + ## Parameters + - `data` array or results. + + ## Handled Api methods + - `getTickPrices` + """ + + alias XtbClient.Messages.TickPrice + defmodule Query do @moduledoc """ Info about the query for tick prices. @@ -16,7 +28,6 @@ defmodule XtbClient.Messages.TickPrices do } @enforce_keys [:level, :symbols, :timestamp] - @derive Jason.Encoder defstruct level: nil, symbols: [], @@ -36,24 +47,12 @@ defmodule XtbClient.Messages.TickPrices do end end - alias XtbClient.Messages.TickPrice - - @moduledoc """ - Query result for list of `XtbClient.Messages.TickPrice`s. - - ## Parameters - - `data` array or results. - - ## Handled Api methods - - `getTickPrices` - """ - @type t :: %__MODULE__{ data: [TickPrice.t()] } @enforce_keys [:data] - + @derive Jason.Encoder defstruct data: [] def new(data) @@ -64,12 +63,4 @@ defmodule XtbClient.Messages.TickPrices do |> Enum.map(&TickPrice.new(&1)) } end - - def match(method, %{"quotations" => data}) when method in ["getTickPrices"] and is_list(data) do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/trade_info.ex b/lib/xtb_client/messages/trade_info.ex index ae3a5f8..c8b5838 100644 --- a/lib/xtb_client/messages/trade_info.ex +++ b/lib/xtb_client/messages/trade_info.ex @@ -1,6 +1,4 @@ defmodule XtbClient.Messages.TradeInfo do - alias XtbClient.Messages.Operation - @moduledoc """ Info about the trade that has happened. @@ -35,6 +33,8 @@ defmodule XtbClient.Messages.TradeInfo do - `volume` volume in lots. """ + alias XtbClient.Messages.Operation + @type t :: %__MODULE__{ close_price: float(), close_time: DateTime.t() | nil, @@ -91,7 +91,7 @@ defmodule XtbClient.Messages.TradeInfo do :take_profit, :volume ] - + @derive Jason.Encoder defstruct close_price: 0.0, close_time: nil, closed: nil, @@ -127,11 +127,7 @@ defmodule XtbClient.Messages.TradeInfo do "type" => type } = args ) do - value = - args - |> Map.delete("state") - |> Map.delete("type") - |> __MODULE__.new() + value = args |> Map.delete(["state", "type"]) |> new() %{value | state: state, type: type} end @@ -146,12 +142,7 @@ defmodule XtbClient.Messages.TradeInfo do when is_number(spread) and is_number(taxes) and is_integer(timestamp_value) do - value = - args - |> Map.delete("spread") - |> Map.delete("taxes") - |> Map.delete("timestamp") - |> __MODULE__.new() + value = args |> Map.delete(["spread", "taxes", "timestamp"]) |> new() %{ value @@ -206,9 +197,9 @@ defmodule XtbClient.Messages.TradeInfo do close_time_value, closed: closed, operation: Operation.parse(operation), - comment: comment, + comment: comment || "", commission: commission, - custom_comment: custom_comment, + custom_comment: custom_comment || "", digits: digits, expiration: (not is_nil(expiration_value) && DateTime.from_unix!(expiration_value, :millisecond)) || @@ -224,7 +215,7 @@ defmodule XtbClient.Messages.TradeInfo do profit: profit, stop_loss: stop_loss, storage: storage, - symbol: symbol, + symbol: symbol || "", take_profit: take_profit, volume: volume } diff --git a/lib/xtb_client/messages/trade_infos.ex b/lib/xtb_client/messages/trade_infos.ex index a690f9b..9a1774d 100644 --- a/lib/xtb_client/messages/trade_infos.ex +++ b/lib/xtb_client/messages/trade_infos.ex @@ -1,4 +1,18 @@ defmodule XtbClient.Messages.TradeInfos do + @moduledoc """ + Query result for list of `XtbClient.Messages.TradeInfo`s. + + ## Parameters + - `data` array or results. + + ## Handled Api methods + - `getTradeRecords` + - `getTrades` + - `getTradesHistory` + """ + + alias XtbClient.Messages.TradeInfo + defmodule Query do @moduledoc """ Info about query for trade infos. @@ -12,7 +26,6 @@ defmodule XtbClient.Messages.TradeInfos do } @enforce_keys [:orders] - @derive Jason.Encoder defstruct orders: [] @@ -23,25 +36,12 @@ defmodule XtbClient.Messages.TradeInfos do end end - alias XtbClient.Messages.TradeInfo - - @moduledoc """ - Query result for list of `XtbClient.Messages.TradeInfo`s. - - ## Parameters - - `data` array or results. - - ## Handled Api methods - - `getTradeRecords` - - `getTrades` - - `getTradesHistory` - """ - @type t :: %__MODULE__{ data: [TradeInfo.t()] } @enforce_keys [:data] + @derive Jason.Encoder defstruct data: [] def new(data) when is_list(data) do @@ -55,12 +55,4 @@ defmodule XtbClient.Messages.TradeInfos do def new(data) when is_map(data) do TradeInfo.new(data) end - - def match(method, data) when method in ["getTradeRecords", "getTrades", "getTradesHistory"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/trade_status.ex b/lib/xtb_client/messages/trade_status.ex index 8a79c8b..1b70d7a 100644 --- a/lib/xtb_client/messages/trade_status.ex +++ b/lib/xtb_client/messages/trade_status.ex @@ -1,6 +1,4 @@ defmodule XtbClient.Messages.TradeStatus do - alias XtbClient.Messages.TransactionStatus - @moduledoc """ Info about the actual status of sent trade request. @@ -15,6 +13,8 @@ defmodule XtbClient.Messages.TradeStatus do - `getTradeStatus` """ + alias XtbClient.Messages.TransactionStatus + @type t :: %__MODULE__{ custom_comment: String.t(), message: String.t(), @@ -23,6 +23,8 @@ defmodule XtbClient.Messages.TradeStatus do status: TransactionStatus.t() } + @enforce_keys [:custom_comment, :message, :order, :price, :status] + @derive Jason.Encoder defstruct custom_comment: "", message: nil, order: 0, @@ -36,23 +38,14 @@ defmodule XtbClient.Messages.TradeStatus do "price" => price, "requestStatus" => status }) - when is_binary(comment) and - is_integer(order) and is_number(price) and + when is_integer(order) and is_number(price) and is_integer(status) do %__MODULE__{ - custom_comment: comment, + custom_comment: comment || "", message: message, order: order, price: price, status: TransactionStatus.parse(status) } end - - def match(method, data) when method in ["getTradeStatus"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/trade_transaction.ex b/lib/xtb_client/messages/trade_transaction.ex index 265551f..4f4ffce 100644 --- a/lib/xtb_client/messages/trade_transaction.ex +++ b/lib/xtb_client/messages/trade_transaction.ex @@ -1,7 +1,15 @@ defmodule XtbClient.Messages.TradeTransaction do - defmodule Command do - alias XtbClient.Messages.{Operation, TradeType} + @moduledoc """ + Info about realized trade transaction. + ## Parameters + - `order` holds info about order number, needed later for verification about order status. + + ## Handled Api methods + - `tradeTransaction` + """ + + defmodule Command do @moduledoc """ Info about command to trade the transaction. @@ -19,6 +27,8 @@ defmodule XtbClient.Messages.TradeTransaction do - `volume` trade volume. """ + alias XtbClient.Messages.{Operation, TradeType} + @type t :: %__MODULE__{ cmd: integer(), customComment: String.t(), @@ -98,22 +108,12 @@ defmodule XtbClient.Messages.TradeTransaction do end end - @moduledoc """ - Info about realized trade transaction. - - ## Parameters - - `order` holds info about order number, needed later for verification about order status. - - ## Handled Api methods - - `tradeTransaction` - """ - @type t :: %__MODULE__{ order: integer() } @enforce_keys [:order] - + @derive Jason.Encoder defstruct order: 0 def new(%{"order" => order}) when is_integer(order) do @@ -121,12 +121,4 @@ defmodule XtbClient.Messages.TradeTransaction do order: order } end - - def match(method, data) when method in ["tradeTransaction"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/trade_transaction_status.ex b/lib/xtb_client/messages/trade_transaction_status.ex index 859e1b3..d4d77ef 100644 --- a/lib/xtb_client/messages/trade_transaction_status.ex +++ b/lib/xtb_client/messages/trade_transaction_status.ex @@ -1,4 +1,21 @@ defmodule XtbClient.Messages.TradeTransactionStatus do + @moduledoc """ + Info about the status of particular transaction. + + ## Parameters + - `ask` price in base currency, + - `bid` price in base currency, + - `custom_comment` the value the customer may provide in order to retrieve it later, + - `message` can be `null`, + - `order` unique order number, + - `status` request status code, see `XtbClient.Messages.TradeStatus`. + + ## Handled Api methods + - `tradeTransactionStatus` + """ + + alias XtbClient.Messages.TransactionStatus + defmodule Query do @moduledoc """ Info about query for trade transaction status. @@ -12,7 +29,6 @@ defmodule XtbClient.Messages.TradeTransactionStatus do } @enforce_keys [:order] - @derive Jason.Encoder defstruct order: 0 @@ -23,23 +39,6 @@ defmodule XtbClient.Messages.TradeTransactionStatus do end end - alias XtbClient.Messages.TransactionStatus - - @moduledoc """ - Info about the status of particular transaction. - - ## Parameters - - `ask` price in base currency, - - `bid` price in base currency, - - `custom_comment` the value the customer may provide in order to retrieve it later, - - `message` can be `null`, - - `order` unique order number, - - `status` request status code, see `XtbClient.Messages.TradeStatus`. - - ## Handled Api methods - - `tradeTransactionStatus` - """ - @type t :: %__MODULE__{ ask: float(), bid: float(), @@ -49,6 +48,8 @@ defmodule XtbClient.Messages.TradeTransactionStatus do status: TransactionStatus.t() } + @enforce_keys [:ask, :bid, :custom_comment, :message, :order, :status] + @derive Jason.Encoder defstruct ask: 0.0, bid: 0.0, custom_comment: "", @@ -65,23 +66,14 @@ defmodule XtbClient.Messages.TradeTransactionStatus do "requestStatus" => status }) when is_number(ask) and is_number(bid) and - is_binary(comment) and is_integer(order) and is_integer(status) do %__MODULE__{ ask: ask, bid: bid, - custom_comment: comment, - message: message, + custom_comment: comment || "", + message: message || "", order: order, status: TransactionStatus.parse(status) } end - - def match(method, data) when method in ["tradeTransactionStatus"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/trade_type.ex b/lib/xtb_client/messages/trade_type.ex index f87e702..a229337 100644 --- a/lib/xtb_client/messages/trade_type.ex +++ b/lib/xtb_client/messages/trade_type.ex @@ -1,7 +1,7 @@ defmodule XtbClient.Messages.TradeType do @moduledoc """ Atoms representing operation types. - + ## Values - `:open` order open, used for opening orders, - `:pending` order pending, only used in the streaming `XtbClient.Connection.subscribe_get_trades/2` command, @@ -16,7 +16,7 @@ defmodule XtbClient.Messages.TradeType do @doc """ Format atom representing trade type to integer value. """ - @spec format(t()) :: trade_code() + @spec format(type :: t()) :: trade_code() def format(type) when is_atom(type) do format_type(type) end diff --git a/lib/xtb_client/messages/trades.ex b/lib/xtb_client/messages/trades.ex index 5aae66e..5a05149 100644 --- a/lib/xtb_client/messages/trades.ex +++ b/lib/xtb_client/messages/trades.ex @@ -2,7 +2,7 @@ defmodule XtbClient.Messages.Trades do defmodule Query do @moduledoc """ Info about the query for trades. - + ## Parameters - `openedOnly` if true then only opened trades will be returned. """ @@ -12,7 +12,6 @@ defmodule XtbClient.Messages.Trades do } @enforce_keys [:openedOnly] - @derive Jason.Encoder defstruct openedOnly: nil diff --git a/lib/xtb_client/messages/trading_hour.ex b/lib/xtb_client/messages/trading_hour.ex index 56f7a64..fc4b234 100644 --- a/lib/xtb_client/messages/trading_hour.ex +++ b/lib/xtb_client/messages/trading_hour.ex @@ -1,6 +1,4 @@ defmodule XtbClient.Messages.TradingHour do - alias XtbClient.Messages.{Quote} - @moduledoc """ Info about one available trading hour. @@ -10,6 +8,8 @@ defmodule XtbClient.Messages.TradingHour do - `trading` array of `XtbClient.Messages.Quote`s representing available trading hours. """ + alias XtbClient.Messages.Quote + @type t :: %__MODULE__{ quotes: [Quote.t()], symbol: String.t(), @@ -17,7 +17,6 @@ defmodule XtbClient.Messages.TradingHour do } @enforce_keys [:quotes, :symbol, :trading] - @derive Jason.Encoder defstruct quotes: [], symbol: "", @@ -28,10 +27,10 @@ defmodule XtbClient.Messages.TradingHour do "symbol" => symbol, "trading" => trading }) - when is_list(quotes) and is_binary(symbol) and is_list(trading) do + when is_list(quotes) and is_list(trading) do %__MODULE__{ quotes: quotes |> Enum.map(&Quote.new(&1)), - symbol: symbol, + symbol: symbol || "", trading: trading |> Enum.map(&Quote.new(&1)) } end diff --git a/lib/xtb_client/messages/trading_hours.ex b/lib/xtb_client/messages/trading_hours.ex index 0f08291..dda3b46 100644 --- a/lib/xtb_client/messages/trading_hours.ex +++ b/lib/xtb_client/messages/trading_hours.ex @@ -1,4 +1,16 @@ defmodule XtbClient.Messages.TradingHours do + @moduledoc """ + Query result for list of `XtbClient.Messages.TradingHour`s. + + ## Parameters + - `data` array or results. + + ## Handled Api methods + - `getTradingHours` + """ + + alias XtbClient.Messages.TradingHour + defmodule Query do @moduledoc """ Info about the query for trading hours. @@ -12,7 +24,6 @@ defmodule XtbClient.Messages.TradingHours do } @enforce_keys [:symbols] - @derive Jason.Encoder defstruct symbols: [] @@ -23,24 +34,11 @@ defmodule XtbClient.Messages.TradingHours do end end - alias XtbClient.Messages.TradingHour - - @moduledoc """ - Query result for list of `XtbClient.Messages.TradingHour`s. - - ## Parameters - - `data` array or results. - - ## Handled Api methods - - `getTradingHours` - """ - @type t :: %__MODULE__{ data: [TradingHour.t()] } @enforce_keys [:data] - @derive Jason.Encoder defstruct data: [] @@ -51,12 +49,4 @@ defmodule XtbClient.Messages.TradingHours do |> Enum.map(&TradingHour.new(&1)) } end - - def match(method, data) when method in ["getTradingHours"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/transaction_status.ex b/lib/xtb_client/messages/transaction_status.ex index 780ad15..6b2ed55 100644 --- a/lib/xtb_client/messages/transaction_status.ex +++ b/lib/xtb_client/messages/transaction_status.ex @@ -9,7 +9,7 @@ defmodule XtbClient.Messages.TransactionStatus do @doc """ Parse integer value as valid atom for transaction status. """ - @spec parse(status_code()) :: t() + @spec parse(value :: status_code()) :: t() def parse(value) when value in [0, 1, 3, 4] do parse_status(value) end diff --git a/lib/xtb_client/messages/user.ex b/lib/xtb_client/messages/user.ex index 22a1915..9133ca7 100644 --- a/lib/xtb_client/messages/user.ex +++ b/lib/xtb_client/messages/user.ex @@ -25,8 +25,15 @@ defmodule XtbClient.Messages.UserInfo do trailing_stop: boolean() } - @enforce_keys [:company_unit, :currency, :group, :ib_account, :leverage_mult, :trailing_stop] - + @enforce_keys [ + :company_unit, + :currency, + :group, + :ib_account, + :leverage_mult, + :spread_type, + :trailing_stop + ] @derive Jason.Encoder defstruct company_unit: 0, currency: "", @@ -36,9 +43,10 @@ defmodule XtbClient.Messages.UserInfo do spread_type: nil, trailing_stop: false - def new(%{"spreadType" => spread_type} = args) when is_binary(spread_type) do - value = __MODULE__.new(Map.delete(args, "spreadType")) - %{value | spread_type: spread_type} + def new(%{"spreadType" => spread_type} = args) do + value = args |> Map.delete("spreadType") |> __MODULE__.new() + + %{value | spread_type: spread_type || ""} end def new(%{ @@ -49,24 +57,17 @@ defmodule XtbClient.Messages.UserInfo do "leverageMultiplier" => leverage_mult, "trailingStop" => trailing_stop }) - when is_number(company_unit) and is_binary(currency) and - is_binary(group) and is_boolean(ib_account) and - is_number(leverage_mult) and is_boolean(trailing_stop) do + when is_number(company_unit) and + is_boolean(ib_account) and + is_number(leverage_mult) do %__MODULE__{ company_unit: company_unit, - currency: currency, - group: group, + currency: currency || "", + group: group || "", ib_account: ib_account, + spread_type: nil, leverage_mult: leverage_mult / 100, - trailing_stop: trailing_stop + trailing_stop: trailing_stop || "" } end - - def match(method, data) when method in ["getCurrentUserData"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end diff --git a/lib/xtb_client/messages/version.ex b/lib/xtb_client/messages/version.ex index 4b6529e..29e1f15 100644 --- a/lib/xtb_client/messages/version.ex +++ b/lib/xtb_client/messages/version.ex @@ -14,7 +14,7 @@ defmodule XtbClient.Messages.Version do } @enforce_keys [:version] - + @derive Jason.Encoder defstruct version: "" def new(%{"version" => version}) when is_binary(version) do @@ -22,12 +22,4 @@ defmodule XtbClient.Messages.Version do version: version } end - - def match(method, data) when method in ["getVersion"] do - {:ok, __MODULE__.new(data)} - end - - def match(_method, _data) do - {:no_match} - end end From b9b9a10c121020af66cc69db47eeae13ba9570f3 Mon Sep 17 00:00:00 2001 From: Daniel Sienkiewicz Date: Thu, 23 Nov 2023 12:34:24 +0100 Subject: [PATCH 2/8] Fix issue with Map.delete --- lib/xtb_client/messages/candle.ex | 4 ++-- lib/xtb_client/messages/chart_range.ex | 2 +- lib/xtb_client/messages/messages.ex | 6 +++--- lib/xtb_client/messages/quotations.ex | 2 +- lib/xtb_client/messages/tick_price.ex | 4 ++-- lib/xtb_client/messages/tick_prices.ex | 4 +--- lib/xtb_client/messages/trade_info.ex | 4 ++-- lib/xtb_client/messages/user.ex | 2 +- 8 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/xtb_client/messages/candle.ex b/lib/xtb_client/messages/candle.ex index 0297e4b..b913aa7 100644 --- a/lib/xtb_client/messages/candle.ex +++ b/lib/xtb_client/messages/candle.ex @@ -61,7 +61,7 @@ defmodule XtbClient.Messages.Candle do } = args ) when is_number(vol) and is_number(ctm_value) do - value = args |> Map.delete(["vol", "ctm", "ctmString", "symbol"]) |> new() + value = args |> Map.drop(["vol", "ctm", "ctmString", "symbol"]) |> new() %{ value @@ -73,7 +73,7 @@ defmodule XtbClient.Messages.Candle do end def new(%{"quoteId" => quote_id} = args) when is_integer(quote_id) do - value = args |> Map.delete(["quoteId"]) |> new() + value = args |> Map.drop(["quoteId"]) |> new() %{ value diff --git a/lib/xtb_client/messages/chart_range.ex b/lib/xtb_client/messages/chart_range.ex index 8976274..3474b70 100644 --- a/lib/xtb_client/messages/chart_range.ex +++ b/lib/xtb_client/messages/chart_range.ex @@ -38,7 +38,7 @@ defmodule XtbClient.Messages.ChartRange do def new(%{ticks: ticks} = args) when is_number(ticks) do - value = args |> Map.delete(:ticks) |> new() + value = args |> Map.drop([:ticks]) |> new() %{value | ticks: ticks} end diff --git a/lib/xtb_client/messages/messages.ex b/lib/xtb_client/messages/messages.ex index b1117c5..2334ea6 100644 --- a/lib/xtb_client/messages/messages.ex +++ b/lib/xtb_client/messages/messages.ex @@ -59,12 +59,12 @@ defmodule XtbClient.Messages do def decode_message("getAllSymbols", data), do: SymbolInfos.new(data) - def decode_message("getTickPrices", data) when is_map(data) and map_size(data) > 1, - do: TickPrice.new(data) - def decode_message("getTickPrices", %{"quotations" => data}) when is_list(data), do: TickPrices.new(data) + def decode_message("getTickPrices", data) when is_map(data) and map_size(data) > 1, + do: TickPrice.new(data) + def decode_message("getTradeRecords", data), do: TradeInfos.new(data) def decode_message("getTrades", data), do: TradeInfos.new(data) def decode_message("getTradesHistory", data), do: TradeInfos.new(data) diff --git a/lib/xtb_client/messages/quotations.ex b/lib/xtb_client/messages/quotations.ex index f64e4b9..5734d97 100644 --- a/lib/xtb_client/messages/quotations.ex +++ b/lib/xtb_client/messages/quotations.ex @@ -31,7 +31,7 @@ defmodule XtbClient.Messages.Quotations do } = args ) when is_integer(min_arrival_time) and is_integer(max_level) do - value = args |> Map.delete([:min_arrival_time, :max_level]) |> new() + value = args |> Map.drop([:min_arrival_time, :max_level]) |> new() %{value | minArrivalTime: min_arrival_time, maxLevel: max_level} end diff --git a/lib/xtb_client/messages/tick_price.ex b/lib/xtb_client/messages/tick_price.ex index 86f1294..8e7e96d 100644 --- a/lib/xtb_client/messages/tick_price.ex +++ b/lib/xtb_client/messages/tick_price.ex @@ -72,7 +72,7 @@ defmodule XtbClient.Messages.TickPrice do } = args ) when is_integer(exemode) do - value = args |> Map.delete(["exemode"]) |> new() + value = args |> Map.drop(["exemode"]) |> new() %{value | exe_mode: exemode} end @@ -83,7 +83,7 @@ defmodule XtbClient.Messages.TickPrice do } = args ) when is_integer(quote_id) do - value = args |> Map.delete(["quoteId"]) |> new() + value = args |> Map.drop(["quoteId"]) |> new() %{value | quote_id: QuoteId.parse(quote_id)} end diff --git a/lib/xtb_client/messages/tick_prices.ex b/lib/xtb_client/messages/tick_prices.ex index 4c24899..5003628 100644 --- a/lib/xtb_client/messages/tick_prices.ex +++ b/lib/xtb_client/messages/tick_prices.ex @@ -58,9 +58,7 @@ defmodule XtbClient.Messages.TickPrices do def new(data) when is_list(data) do %__MODULE__{ - data: - data - |> Enum.map(&TickPrice.new(&1)) + data: Enum.map(data, &TickPrice.new(&1)) } end end diff --git a/lib/xtb_client/messages/trade_info.ex b/lib/xtb_client/messages/trade_info.ex index c8b5838..e2c9135 100644 --- a/lib/xtb_client/messages/trade_info.ex +++ b/lib/xtb_client/messages/trade_info.ex @@ -127,7 +127,7 @@ defmodule XtbClient.Messages.TradeInfo do "type" => type } = args ) do - value = args |> Map.delete(["state", "type"]) |> new() + value = args |> Map.drop(["state", "type"]) |> new() %{value | state: state, type: type} end @@ -142,7 +142,7 @@ defmodule XtbClient.Messages.TradeInfo do when is_number(spread) and is_number(taxes) and is_integer(timestamp_value) do - value = args |> Map.delete(["spread", "taxes", "timestamp"]) |> new() + value = args |> Map.drop(["spread", "taxes", "timestamp"]) |> new() %{ value diff --git a/lib/xtb_client/messages/user.ex b/lib/xtb_client/messages/user.ex index 9133ca7..747ddc1 100644 --- a/lib/xtb_client/messages/user.ex +++ b/lib/xtb_client/messages/user.ex @@ -44,7 +44,7 @@ defmodule XtbClient.Messages.UserInfo do trailing_stop: false def new(%{"spreadType" => spread_type} = args) do - value = args |> Map.delete("spreadType") |> __MODULE__.new() + value = args |> Map.drop(["spreadType"]) |> __MODULE__.new() %{value | spread_type: spread_type || ""} end From 52e22acd9c0a37af7f5989547823eeec0fcbceb1 Mon Sep 17 00:00:00 2001 From: Daniel Sienkiewicz Date: Thu, 23 Nov 2023 12:47:00 +0100 Subject: [PATCH 3/8] Remove module name when not needed --- lib/xtb_client/messages/user.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/xtb_client/messages/user.ex b/lib/xtb_client/messages/user.ex index 747ddc1..cd32ed5 100644 --- a/lib/xtb_client/messages/user.ex +++ b/lib/xtb_client/messages/user.ex @@ -44,7 +44,7 @@ defmodule XtbClient.Messages.UserInfo do trailing_stop: false def new(%{"spreadType" => spread_type} = args) do - value = args |> Map.drop(["spreadType"]) |> __MODULE__.new() + value = args |> Map.drop(["spreadType"]) |> new() %{value | spread_type: spread_type || ""} end From cf89b5c9d856d127e73349e724bd1dd252b629d1 Mon Sep 17 00:00:00 2001 From: Daniel Sienkiewicz Date: Thu, 23 Nov 2023 13:34:08 +0100 Subject: [PATCH 4/8] Add special handling for get_chart_range query, when symbol is not returned from server --- lib/xtb_client/connection.ex | 36 ++++++++++++++++++++++----- lib/xtb_client/messages/candle.ex | 38 ++++++++++++++++------------- test/xtb_client/connection_test.exs | 29 +++++++++++++++++++--- 3 files changed, 77 insertions(+), 26 deletions(-) diff --git a/lib/xtb_client/connection.ex b/lib/xtb_client/connection.ex index 6961a7e..0c27bb6 100644 --- a/lib/xtb_client/connection.ex +++ b/lib/xtb_client/connection.ex @@ -62,11 +62,13 @@ defmodule XtbClient.Connection do alias XtbClient.{MainSocket, StreamingSocket, StreamingMessage} alias XtbClient.Messages.{ + Candle, Candles, ChartLast, ChartRange, DateRange, ProfitCalculation, + RateInfos, Quotations, SymbolInfo, SymbolVolume, @@ -509,7 +511,7 @@ defmodule XtbClient.Connection do ref_string = inspect(ref) MainSocket.query(mpid, self(), ref_string, method) - clients = Map.put(clients, ref_string, from) + clients = Map.put(clients, ref_string, {from, method, nil}) state = %State{state | clients: clients} {:noreply, state} @@ -524,17 +526,32 @@ defmodule XtbClient.Connection do ref_string = inspect(ref) MainSocket.query(mpid, self(), ref_string, method, params) - clients = Map.put(clients, ref_string, from) + clients = Map.put(clients, ref_string, {from, method, params}) state = %State{state | clients: clients} {:noreply, state} end @impl true - def handle_cast({:response, ref, resp} = _message, %State{clients: clients} = state) do - {client, clients} = Map.pop!(clients, ref) - GenServer.reply(client, resp) - state = %State{state | clients: clients} + def handle_cast( + {:response, ref, %RateInfos{data: data} = resp} = _message, + %State{clients: clients} = state + ) do + {_client, _method, %{info: %ChartRange.Query{symbol: symbol}}} = Map.get(clients, ref) + + resp = %RateInfos{ + resp + | data: Enum.map(data, &%Candle{&1 | symbol: symbol}) + } + + state = handle_query_response(ref, resp, state) + + {:noreply, state} + end + + @impl true + def handle_cast({:response, ref, resp} = _message, %State{} = state) do + state = handle_query_response(ref, resp, state) {:noreply, state} end @@ -581,6 +598,13 @@ defmodule XtbClient.Connection do {:noreply, state} end + defp handle_query_response(ref, response, %State{clients: clients} = state) do + {{client, _method, _params}, clients} = Map.pop!(clients, ref) + GenServer.reply(client, response) + + %State{state | clients: clients} + end + @impl true def handle_info({:EXIT, pid, reason}, state) do Logger.error( diff --git a/lib/xtb_client/messages/candle.ex b/lib/xtb_client/messages/candle.ex index b913aa7..a6d84dc 100644 --- a/lib/xtb_client/messages/candle.ex +++ b/lib/xtb_client/messages/candle.ex @@ -56,19 +56,26 @@ defmodule XtbClient.Messages.Candle do %{ "vol" => vol, "ctm" => ctm_value, - "ctmString" => ctm_string, - "symbol" => symbol + "ctmString" => ctm_string } = args ) when is_number(vol) and is_number(ctm_value) do - value = args |> Map.drop(["vol", "ctm", "ctmString", "symbol"]) |> new() + value = args |> Map.drop(["vol", "ctm", "ctmString"]) |> new() %{ value | vol: vol, ctm: DateTime.from_unix!(ctm_value, :millisecond), - ctm_string: ctm_string || "", - symbol: symbol || "" + ctm_string: ctm_string || "" + } + end + + def new(%{"symbol" => symbol} = args) do + value = args |> Map.drop(["symbol"]) |> new() + + %{ + value + | symbol: symbol || "" } end @@ -107,22 +114,19 @@ defmodule XtbClient.Messages.Candle do "high" => high, "low" => low, "close" => close - }, + } = args, digits ) when is_number(open) and is_number(high) and is_number(low) and is_number(close) and is_number(digits) do - %__MODULE__{ - open: to_base_currency(open, digits), - high: to_base_currency(open + high, digits), - low: to_base_currency(open + low, digits), - close: to_base_currency(open + close, digits), - vol: 0.0, - ctm: nil, - ctm_string: nil, - quote_id: nil, - symbol: nil - } + args + |> Map.merge(%{ + "open" => to_base_currency(open, digits), + "high" => to_base_currency(open + high, digits), + "low" => to_base_currency(open + low, digits), + "close" => to_base_currency(open + close, digits) + }) + |> new() end defp to_base_currency(value, digits) do diff --git a/test/xtb_client/connection_test.exs b/test/xtb_client/connection_test.exs index cb1e085..e6196bd 100644 --- a/test/xtb_client/connection_test.exs +++ b/test/xtb_client/connection_test.exs @@ -115,11 +115,13 @@ defmodule XtbClient.ConnectionTest do end test "get chart range", %{pid: pid} do + now = DateTime.utc_now() + args = %{ range: DateRange.new(%{ - from: DateTime.utc_now() |> DateTime.add(-2 * 30 * 24 * 60 * 60), - to: DateTime.utc_now() + from: DateTime.add(now, -2 * 30 * 24 * 60 * 60), + to: now }), period: :h1, symbol: "EURPLN" @@ -131,7 +133,28 @@ defmodule XtbClient.ConnectionTest do assert %RateInfos{} = result assert is_number(result.digits) assert [elem | _] = result.data - assert %Candle{} = elem + + assert %Candle{ + symbol: symbol, + open: open, + high: high, + low: low, + close: close, + vol: vol, + ctm: ctm, + ctm_string: ctm_string, + quote_id: quote_id + } = elem + + assert "EURPLN" == symbol + assert is_number(open) + assert is_number(high) + assert is_number(low) + assert is_number(close) + assert is_number(vol) + assert DateTime.before?(ctm, now) + assert is_binary(ctm_string) + refute quote_id end test "get commission definition", %{pid: pid} do From f04ed7469e79057d88a99f2413829909473ae093 Mon Sep 17 00:00:00 2001 From: Daniel Sienkiewicz Date: Thu, 23 Nov 2023 13:45:39 +0100 Subject: [PATCH 5/8] Fix problem with different params struct for same result --- lib/xtb_client/connection.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/xtb_client/connection.ex b/lib/xtb_client/connection.ex index 0c27bb6..9f665af 100644 --- a/lib/xtb_client/connection.ex +++ b/lib/xtb_client/connection.ex @@ -537,7 +537,7 @@ defmodule XtbClient.Connection do {:response, ref, %RateInfos{data: data} = resp} = _message, %State{clients: clients} = state ) do - {_client, _method, %{info: %ChartRange.Query{symbol: symbol}}} = Map.get(clients, ref) + {_client, _method, %{info: %{symbol: symbol}}} = Map.get(clients, ref) resp = %RateInfos{ resp From 868a646e95073dcb629a505029e15ad02d223912 Mon Sep 17 00:00:00 2001 From: Daniel Sienkiewicz Date: Thu, 23 Nov 2023 13:51:37 +0100 Subject: [PATCH 6/8] Fix incompatibility with Elixir version --- test/xtb_client/connection_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/xtb_client/connection_test.exs b/test/xtb_client/connection_test.exs index e6196bd..65814a3 100644 --- a/test/xtb_client/connection_test.exs +++ b/test/xtb_client/connection_test.exs @@ -152,7 +152,7 @@ defmodule XtbClient.ConnectionTest do assert is_number(low) assert is_number(close) assert is_number(vol) - assert DateTime.before?(ctm, now) + assert DateTime.compare(ctm, now) == :lt assert is_binary(ctm_string) refute quote_id end From a660179ee84185801047f90deef9eb7285dc5a63 Mon Sep 17 00:00:00 2001 From: Daniel Sienkiewicz Date: Tue, 5 Dec 2023 11:25:05 +0100 Subject: [PATCH 7/8] Fix decoding balance message --- lib/xtb_client/connection.ex | 3 +- lib/xtb_client/messages/balance_info.ex | 47 +++++++++++++++++---- test/xtb_client/connection_test.exs | 50 ++++++++++++++++------- test/xtb_client/main_socket_test.exs | 3 +- test/xtb_client/streaming_socket_test.exs | 2 +- 5 files changed, 78 insertions(+), 27 deletions(-) diff --git a/lib/xtb_client/connection.ex b/lib/xtb_client/connection.ex index 9f665af..2ea4d05 100644 --- a/lib/xtb_client/connection.ex +++ b/lib/xtb_client/connection.ex @@ -118,12 +118,11 @@ defmodule XtbClient.Connection do @impl true def init(opts) do {:ok, mpid} = MainSocket.start_link(opts) + Process.flag(:trap_exit, true) Process.sleep(500) MainSocket.stream_session_id(mpid, self()) - Process.flag(:trap_exit, true) - type = get_in(opts, [:type]) url = get_in(opts, [:url]) diff --git a/lib/xtb_client/messages/balance_info.ex b/lib/xtb_client/messages/balance_info.ex index b84e5bb..6db5ef6 100644 --- a/lib/xtb_client/messages/balance_info.ex +++ b/lib/xtb_client/messages/balance_info.ex @@ -24,7 +24,7 @@ defmodule XtbClient.Messages.BalanceInfo do balance: float(), cash_stock_value: float(), credit: float(), - currency: String.t(), + currency: String.t() | nil, equity: float(), equity_fx: float(), margin: float(), @@ -51,7 +51,7 @@ defmodule XtbClient.Messages.BalanceInfo do defstruct balance: 0.0, cash_stock_value: 0.0, credit: 0.0, - currency: "", + currency: nil, equity: 0.0, equity_fx: 0.0, margin: 0.0, @@ -60,33 +60,62 @@ defmodule XtbClient.Messages.BalanceInfo do stock_lock: 0.0, stock_value: 0.0 + def new(%{"currency" => currency} = args) do + value = args |> Map.drop(["currency"]) |> new() + + %{value | currency: currency || ""} + end + + def new( + %{ + "margin_free" => margin_free, + "margin_level" => margin_level + } = args + ) + when is_number(margin_free) and is_number(margin_level) do + value = args |> Map.drop(["margin_free", "margin_level"]) |> new() + + %{value | margin_free: margin_free, margin_level: margin_level} + end + + def new( + %{ + "marginFree" => margin_free, + "marginLevel" => margin_level + } = args + ) + when is_number(margin_free) and is_number(margin_level) do + args + |> Map.drop(["marginFree", "marginLevel"]) + |> Map.put("margin_free", margin_free) + |> Map.put("margin_level", margin_level) + |> new() + end + def new(%{ "balance" => balance, "cashStockValue" => cash_stock_value, "credit" => credit, - "currency" => currency, "equity" => equity, "equityFX" => equity_fx, "margin" => margin, - "margin_free" => margin_free, - "margin_level" => margin_level, "stockLock" => stock_lock, "stockValue" => stock_value }) when is_number(balance) and is_number(cash_stock_value) and is_number(credit) and is_number(equity) and is_number(equity_fx) and - is_number(margin) and is_number(margin_free) and is_number(margin_level) and + is_number(margin) and is_number(stock_lock) and is_number(stock_value) do %__MODULE__{ balance: balance, cash_stock_value: cash_stock_value, credit: credit, - currency: currency || "", + currency: nil, equity: equity, equity_fx: equity_fx, margin: margin, - margin_free: margin_free, - margin_level: margin_level, + margin_free: 0.0, + margin_level: 0.0, stock_lock: stock_lock, stock_value: stock_value } diff --git a/test/xtb_client/connection_test.exs b/test/xtb_client/connection_test.exs index 65814a3..b75009c 100644 --- a/test/xtb_client/connection_test.exs +++ b/test/xtb_client/connection_test.exs @@ -1,6 +1,6 @@ defmodule XtbClient.ConnectionTest do @moduledoc false - use ExUnit.Case, async: true + use ExUnit.Case doctest XtbClient.Connection alias XtbClient.Connection @@ -99,9 +99,11 @@ defmodule XtbClient.ConnectionTest do end test "get chart last", %{pid: pid} do + now = DateTime.utc_now() + args = %{ period: :h1, - start: DateTime.utc_now() |> DateTime.add(-30 * 24 * 60 * 60), + start: DateTime.add(now, -30 * 24 * 60 * 60), symbol: "EURPLN" } @@ -111,7 +113,28 @@ defmodule XtbClient.ConnectionTest do assert %RateInfos{} = result assert is_number(result.digits) assert [elem | _] = result.data - assert %Candle{} = elem + + assert %Candle{ + symbol: symbol, + open: open, + high: high, + low: low, + close: close, + vol: vol, + ctm: ctm, + ctm_string: ctm_string, + quote_id: quote_id + } = elem + + assert "EURPLN" == symbol + assert is_number(open) + assert is_number(high) + assert is_number(low) + assert is_number(close) + assert is_number(vol) + assert DateTime.compare(ctm, now) == :lt + assert is_binary(ctm_string) + refute quote_id end test "get chart range", %{pid: pid} do @@ -287,6 +310,9 @@ defmodule XtbClient.ConnectionTest do end test "trade transaction - open and close transaction", %{pid: pid} do + # needed to wait for message to be received from server that transaction is accepted + Connection.subscribe_get_trade_status(pid, self()) + buy_args = %{ operation: :buy, custom_comment: "Buy transaction", @@ -301,8 +327,7 @@ defmodule XtbClient.ConnectionTest do assert %TradeTransaction{} = result - # needs some time for server to process order correctly - Process.sleep(100) + assert_receive {:ok, %TradeStatus{}}, @default_wait_time open_order_id = result.order status = TradeTransactionStatus.Query.new(open_order_id) @@ -310,9 +335,6 @@ defmodule XtbClient.ConnectionTest do assert %TradeTransactionStatus{} = result - # needs some time for server to process order correctly - Process.sleep(100) - # get all opened only trades trades_query = Trades.Query.new(true) result = Connection.get_trades(pid, trades_query) @@ -337,6 +359,7 @@ defmodule XtbClient.ConnectionTest do result = Connection.trade_transaction(pid, close) assert %TradeTransaction{} = result + assert_receive {:ok, %TradeStatus{}}, @default_wait_time close_order_id = result.order status = TradeTransactionStatus.Query.new(close_order_id) @@ -346,6 +369,8 @@ defmodule XtbClient.ConnectionTest do end test "subscribe to get balance", %{pid: pid} do + Connection.subscribe_get_balance(pid, self()) + buy_args = %{ operation: :buy, custom_comment: "Buy transaction", @@ -361,11 +386,7 @@ defmodule XtbClient.ConnectionTest do assert %TradeTransaction{} = result open_order_id = result.order - # needs some time for server to process order correctly - Process.sleep(100) - - # real test scenario - Connection.subscribe_get_balance(pid, self()) + assert_receive {:ok, %BalanceInfo{}}, @default_wait_time # get all opened only trades trades_query = Trades.Query.new(true) @@ -389,8 +410,9 @@ defmodule XtbClient.ConnectionTest do close = TradeTransaction.Command.new(close_args) result = Connection.trade_transaction(pid, close) - assert %TradeTransaction{} = result + + assert_receive {:ok, %BalanceInfo{}}, @default_wait_time end @tag timeout: @default_wait_time diff --git a/test/xtb_client/main_socket_test.exs b/test/xtb_client/main_socket_test.exs index cac4ed0..be963d6 100644 --- a/test/xtb_client/main_socket_test.exs +++ b/test/xtb_client/main_socket_test.exs @@ -1,5 +1,6 @@ defmodule XtbClient.MainSocketTest do - use ExUnit.Case, async: true + @moduledoc false + use ExUnit.Case doctest XtbClient.MainSocket alias XtbClient.MainSocket diff --git a/test/xtb_client/streaming_socket_test.exs b/test/xtb_client/streaming_socket_test.exs index f6e189e..0471206 100644 --- a/test/xtb_client/streaming_socket_test.exs +++ b/test/xtb_client/streaming_socket_test.exs @@ -1,6 +1,6 @@ defmodule XtbClient.StreamingSocketTest do @moduledoc false - use ExUnit.Case, async: true + use ExUnit.Case doctest XtbClient.StreamingSocket alias XtbClient.MainSocket From ad8e844930b6f65b434bcd98fa5f491588d64a6a Mon Sep 17 00:00:00 2001 From: Daniel Sienkiewicz Date: Tue, 5 Dec 2023 12:10:37 +0100 Subject: [PATCH 8/8] Make tests async --- test/xtb_client/connection_test.exs | 2 +- test/xtb_client/main_socket_test.exs | 2 +- test/xtb_client/streaming_socket_test.exs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/xtb_client/connection_test.exs b/test/xtb_client/connection_test.exs index b75009c..6eefd56 100644 --- a/test/xtb_client/connection_test.exs +++ b/test/xtb_client/connection_test.exs @@ -1,6 +1,6 @@ defmodule XtbClient.ConnectionTest do @moduledoc false - use ExUnit.Case + use ExUnit.Case, async: true doctest XtbClient.Connection alias XtbClient.Connection diff --git a/test/xtb_client/main_socket_test.exs b/test/xtb_client/main_socket_test.exs index be963d6..d316bf4 100644 --- a/test/xtb_client/main_socket_test.exs +++ b/test/xtb_client/main_socket_test.exs @@ -1,6 +1,6 @@ defmodule XtbClient.MainSocketTest do @moduledoc false - use ExUnit.Case + use ExUnit.Case, async: true doctest XtbClient.MainSocket alias XtbClient.MainSocket diff --git a/test/xtb_client/streaming_socket_test.exs b/test/xtb_client/streaming_socket_test.exs index 0471206..f6e189e 100644 --- a/test/xtb_client/streaming_socket_test.exs +++ b/test/xtb_client/streaming_socket_test.exs @@ -1,6 +1,6 @@ defmodule XtbClient.StreamingSocketTest do @moduledoc false - use ExUnit.Case + use ExUnit.Case, async: true doctest XtbClient.StreamingSocket alias XtbClient.MainSocket