├── .formatter.exs ├── .gitignore ├── LICENSE ├── README.md ├── corr_matrix.png ├── lib └── correlations.ex ├── mix.exs ├── mix.lock └── test ├── correlations_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | correlations-*.tar 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gunnar Rosenberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/tterb/atomic-design-ui/blob/master/LICENSEs) 2 | [![Hex.pm Version](http://img.shields.io/hexpm/v/correlations.svg?style=flat)](https://hex.pm/packages/correlations) 3 | 4 | # Correlations 5 | 6 | A financial correlations library for elixir, fully compatible with the elixir `Decimal` library. 7 | 8 | ![correlation matrix img](https://github.com/GunnarPDX/correlation-matrix-chart/blob/master/correlation-matrix.png?raw=true) 9 | 10 | ### Example frontend usage 11 | https://github.com/GunnarPDX/Nice-Charts 12 | 13 | ## Installation 14 | 15 | This package can be installed by adding `correlations` to your list of dependencies in `mix.exs`: 16 | 17 | ```elixir 18 | def deps do 19 | [ 20 | {:correlations, "~> 0.1.1"}, 21 | ] 22 | end 23 | ``` 24 | 25 | ## Docs 26 | 27 | #### HexDocs: [https://hexdocs.pm/correlations](https://hexdocs.pm/correlations/Correlations.html#functions) 28 | 29 | ## Functions 30 | 31 | - `portfolio_correlations_picker(stocks, portfolio_size)` 32 | 33 | - `portfolio_correlations_list(stocks, portfolio_size)` 34 | 35 | - `correlation_matrix(stocks)` 36 | 37 | - `json_correlation_matrix(stocks)` 38 | 39 | - `correlation(x, y)` 40 | 41 | 42 | ## Usage 43 | ```elixir 44 | iex> alias Correlations, as: C 45 | 46 | iex> stocks = [ 47 | aapl: [124.400002, 121.099998, 121.190002, 120.709999, 119.019997], 48 | nvda: [569.039978, 569.929993, 563.809998, 558.799988, 552.460022], 49 | tsla: [442.299988, 446.649994, 461.299988, 448.880005, 439.670013], 50 | amzn: [3442.929932, 3443.629883, 3363.709961, 3338.649902, 3272.709961] 51 | ] 52 | 53 | iex> decimal_stocks = for {k, v} <- stocks, do: {k, decimalize(v)} 54 | 55 | iex> C.portfolio_correlations_picker(decimal_stocks, 2) 56 | {Decimal<0.104192125>, [:aapl, :tsla]} 57 | ``` 58 | -------------------------------------------------------------------------------- /corr_matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GunnarPDX/Correlations/657c7493f512103d596a458d4ecd7d96589e77dc/corr_matrix.png -------------------------------------------------------------------------------- /lib/correlations.ex: -------------------------------------------------------------------------------- 1 | defmodule Correlations do 2 | 3 | @moduledoc """ 4 | Documentation for `Correlations`. 5 | """ 6 | 7 | alias Decimal, as: D 8 | alias Enum, as: E 9 | alias List, as: L 10 | alias Map, as: M 11 | alias Atom, as: A 12 | 13 | 14 | @type coefficient :: non_neg_integer | :NaN | :inf 15 | @type exponent :: integer 16 | @type sign :: 1 | -1 17 | @type t :: %Decimal{sign: sign, coef: coefficient, exp: exponent} 18 | @type decimal :: t | integer | String.t() 19 | @type json :: String.t() 20 | 21 | 22 | @doc """ 23 | ## Portfolio Correlations Picker 24 | Picks the optimal portfolio combination with lowest correlation coefficient 25 | 26 | ## Examples 27 | ``` 28 | iex> stocks = [aapl: [#Decimal<124.400002>, #Decimal<121.099998>, ...], nvda: [#Decimal<552.460022>, ...], ... ] 29 | ... 30 | 31 | iex> size = 2 32 | 2 33 | 34 | iex> portfolio_correlations_picker(stocks, portfolio_size) 35 | {#Decimal<0.104192125>, [:aapl, :tsla]} 36 | ``` 37 | """ 38 | @spec portfolio_correlations_picker(list({atom, list(decimal)}), integer) :: {decimal, list(atom)} 39 | 40 | def portfolio_correlations_picker(stocks, portfolio_size) do 41 | # find combinations 42 | # |> pick portfolio with lowest correlation 43 | portfolio_correlations_list(stocks, portfolio_size) 44 | |> E.reduce(nil, fn x, acc -> pick_lowest(x, acc) end) 45 | end 46 | 47 | 48 | 49 | @doc """ 50 | ## Portfolio Correlations List 51 | Creates a list of portfolio combinations with correlation coefficients 52 | 53 | ## Examples 54 | ``` 55 | iex> stocks = [aapl: [#Decimal<124.400002>, #Decimal<121.099998>, ...], nvda: [#Decimal<552.460022>, ...], ... ] 56 | ... 57 | 58 | iex> size = 2 59 | 2 60 | 61 | iex> portfolio_correlations_list(stocks, portfolio_size) 62 | [ 63 | {#Decimal<0.809616345>, [:aapl, :nvda]}, 64 | {#Decimal<0.104192125>, [:aapl, :tsla]}, 65 | {#Decimal<0.674977695>, [:aapl, :amzn]}, 66 | {#Decimal<0.198672188>, [:nvda, :tsla]}, 67 | {#Decimal<0.867990065>, [:amzn, :nvda]}, 68 | {#Decimal<0.236184044>, [:amzn, :tsla]} 69 | ] 70 | ``` 71 | """ 72 | @spec portfolio_correlations_list(list({atom, list(decimal)}), integer) :: list({decimal, list(atom)}) 73 | 74 | def portfolio_correlations_list(stocks, portfolio_size) do 75 | # make a list of symbols 76 | sym_list = for {k, _v} <- stocks, do: k 77 | # create a correlation matrix of all stock pair combos 78 | corr_matrix = correlation_matrix(stocks) 79 | # flatten correlation matrix 80 | corr_list = L.flatten(corr_matrix) 81 | # create stock combinations for all possible portfolios of desired size 82 | sym_combos = combinations(sym_list, portfolio_size) 83 | # create list of possible portfolios with avg overall correlation coefficient 84 | portfolio_correlations(sym_combos, corr_list) 85 | end 86 | 87 | 88 | 89 | @doc """ 90 | ## Correlation Matrix 91 | Creates a correlation matrix from a list of products 92 | 93 | ## Examples 94 | ``` 95 | iex> stocks = [aapl: [#Decimal<124.400002>, #Decimal<121.099998>, ...], nvda: [#Decimal<552.460022>, ...], ... ] 96 | ... 97 | 98 | iex> correlation_matrix(stocks) 99 | [ 100 | [{:aapl, :nvda, #Decimal<0.809616347>},{:aapl, :tsla, #Decimal<0.104192125>},{:aapl, :amzn, #Decimal<0.674977695>}], 101 | [{:nvda, :tsla, #Decimal<0.198672188>}, {:nvda, :amzn, #Decimal<0.867990063>}], 102 | [{:tsla, :amzn, #Decimal<0.236184044>}], 103 | ] 104 | ``` 105 | """ 106 | @spec correlation_matrix(list({atom, list(decimal)})) :: list(list({atom, atom, decimal})) 107 | def correlation_matrix(stocks) do 108 | # get percent changes per tick 109 | # then generate corr matrix 110 | stocks 111 | |> get_percent_changes() 112 | |> correlation_matrix([]) 113 | end 114 | 115 | # end when tail is empty 116 | defp correlation_matrix([_], acc) do 117 | E.reverse(acc) 118 | end 119 | 120 | defp correlation_matrix([head|tail], acc) do 121 | # find correlation coefficient pair combinations for head with remaining stocks 122 | res = correlation_coefs(head, tail, []) |> E.reverse() # reverse list to 'fix/prettify' data order 123 | # repeat for remaining stocks 124 | correlation_matrix(tail, [res|acc]) 125 | end 126 | 127 | # shouldn't ever get reached 128 | defp correlation_matrix([], acc), 129 | do: E.reverse(acc) 130 | 131 | 132 | 133 | @doc """ 134 | ## JSON Correlation Matrix 135 | returns matrix data in JSON format. 136 | Ex frontend usage: (Link)[https://github.com/GunnarPDX/correlation-matrix-chart] 137 | """ 138 | ## json format ~> {x: 3, y: 1, color: 0.236184044, xLabel: 'tsla', yLabel: 'amzn'} 139 | @spec correlation_matrix(list({atom, list(decimal)})) :: json 140 | def json_correlation_matrix(stocks) do 141 | # create corr matrix data structure 142 | corr_matrix = correlation_matrix(stocks) 143 | # reformat matrix for frontend usage 144 | {_i, res_list} = json_matrix(corr_matrix) 145 | # flatten and convert to JSON 146 | res_list 147 | |> L.flatten() 148 | |> Jason.encode() 149 | end 150 | 151 | # reformat matrix to have x and y indicies 152 | defp json_matrix(corr_matrix) do 153 | E.reduce(corr_matrix, {1,[]}, fn(l, {index, acc}) -> 154 | 155 | {_, _, row} = E.reduce(l, {index, 1, []}, fn({sym1, sym2, corr_val}, {i1, i2, acc}) -> 156 | # convert corr_val decimal to float and tickers to strings for frontend 157 | cell = %{x: i1, y: i2, color: D.to_float(corr_val), xLabel: A.to_string(sym1), yLabel: A.to_string(sym2)} 158 | # iterate col 159 | {i1, i2 + 1, acc ++ [cell]} 160 | end) 161 | # iterate row 162 | {index + 1, [acc|row]} 163 | end) 164 | end 165 | 166 | 167 | 168 | @doc false 169 | defp get_percent_changes(stocks), 170 | do: for {k, v} <- stocks, do: {k, changes(v)} 171 | 172 | defp changes([head|tail]), 173 | do: changes(tail, head, []) 174 | 175 | defp changes([head|tail], prev, acc) do 176 | # find percent change 177 | per_change = D.mult(D.div(D.sub(head, prev), prev), 100) 178 | changes(tail, head, [per_change|acc]) 179 | end 180 | 181 | defp changes([], _, acc), 182 | do: E.reverse(acc) 183 | 184 | 185 | 186 | @doc false 187 | # TODO 188 | defp _get_downside_changes(stocks), 189 | do: for {k, v} <- stocks, do: {k, _downside_changes(v)} 190 | 191 | defp _downside_changes([head|tail]), 192 | do: _downside_changes(tail, head, []) 193 | 194 | defp _downside_changes([head|tail], prev, acc) do 195 | # find percent change 196 | per_change = D.mult(D.div(D.sub(head, prev), prev), 100) 197 | # ignore positive changes 198 | cond do 199 | per_change > 0 -> _downside_changes(tail, head, [D.new(0)|acc]) 200 | :else -> _downside_changes(tail, head, [per_change|acc]) 201 | end 202 | end 203 | 204 | defp _downside_changes([], _, acc), 205 | do: E.reverse(acc) 206 | 207 | 208 | 209 | @doc false 210 | defp correlation_coefs({symbol, quotes} = stock ,[{curr_symbol, curr_quotes} = _head|tail], acc) do 211 | # find correlation coefficients for stock with each remaining stock 212 | corr_coef = correlation(quotes, curr_quotes) |> D.abs() # TODO: add opts for pos/neg corr coef 213 | # package result as tuple with stocks names 214 | res = {symbol, curr_symbol, corr_coef} 215 | # repeat for remaining stocks 216 | correlation_coefs(stock, tail, [res|acc]) 217 | end 218 | 219 | defp correlation_coefs(_, [], acc), 220 | do: E.reverse(acc) 221 | 222 | 223 | 224 | @doc """ 225 | ## Correlation 226 | Finds the correlation coefficient between two lists of decimals 227 | 228 | ## Examples 229 | ``` 230 | iex> list1 = [#Decimal<124.400002>, #Decimal<121.099998>, ...] 231 | ... 232 | 233 | iex> list2 = [#Decimal<569.039978>, #Decimal<569.929993>, ...] 234 | ... 235 | 236 | iex> correlation(list1, list2) 237 | #Decimal<0.809616345> 238 | ``` 239 | """ 240 | @spec correlation(list(decimal), list(decimal)) :: decimal 241 | def correlation(x, y) when length(x) == length(y) do 242 | # Pearson’s correlation coefficient formula 243 | # (insensitive to argument order) 244 | avg_x = mean(x) 245 | avg_y = mean(y) 246 | n = x 247 | |> Enum.zip(y) 248 | |> Enum.map(fn {xi, yi} -> D.mult(D.sub(xi, avg_x), D.sub(yi, avg_y)) end) 249 | |> E.reduce(fn x, acc -> D.add(x, acc) end) 250 | # |> E.sum() 251 | dx = denominate(x, avg_x) 252 | dy = denominate(y, avg_y) 253 | D.div(n, D.sqrt(D.mult(dx, dy))) 254 | end 255 | 256 | defp denominate(list, avg) do 257 | list 258 | |> Enum.map(fn i -> D.mult(D.sub(i, avg), D.sub(i, avg)) end) 259 | |> E.reduce(fn x, acc -> D.add(x, acc) end) 260 | # |> E.sum() 261 | end 262 | 263 | 264 | 265 | @doc false 266 | # calc mean avg 267 | defp mean(list) when is_list(list), 268 | do: mean(list, 0, 0) 269 | 270 | defp mean([], 0, 0), 271 | do: nil 272 | 273 | defp mean([], t, l), 274 | do: D.div(t, l) 275 | 276 | defp mean([x | xs], t, l), 277 | do: mean(xs, D.add(t, x), D.add(l, 1)) 278 | 279 | 280 | 281 | @doc false 282 | # create stock combos for possible portfolios 283 | defp combinations(enum, k) do 284 | List.last(create_combos(enum, k)) 285 | |> Enum.uniq 286 | end 287 | 288 | defp create_combos(enum, k) do 289 | combos_by_length = [[[]]|List.duplicate([], k)] 290 | list = Enum.to_list(enum) 291 | List.foldr list, combos_by_length, fn x, next -> 292 | sub = :lists.droplast(next) 293 | step = [[]|(for l <- sub, do: (for s <- l, do: [x|s]))] 294 | :lists.zipwith(&:lists.append/2, step, next) 295 | end 296 | end 297 | 298 | 299 | 300 | @doc false 301 | defp portfolio_correlations(sym_combos, corr_list) do 302 | # loop through symbol combos and find correlation coefficient averages 303 | # |> filter for fully linked nodes 304 | # |> return avg coefficient for portfolio 305 | for x <- sym_combos do 306 | corr_list 307 | |> E.filter(fn {s1, s2, _val} -> E.member?(x, s1) and E.member?(x, s2) end) 308 | |> average_combo_corr_coef() 309 | end 310 | end 311 | 312 | defp average_combo_corr_coef(combo) do 313 | # create map of stocks with lists of coefficients 314 | coef_map = E.reduce(combo, %{}, fn 315 | x, acc -> 316 | {sym1, sym2, coef} = x 317 | M.merge(acc, %{sym1 => [coef]}, fn _k, v1, v2 -> v1 ++ v2 end) 318 | |> M.merge(%{sym2 => [coef]}, fn _k, v1, v2 -> v1 ++ v2 end) 319 | end) 320 | 321 | # average coefficient lists and then average whole portfolio 322 | coef_avg = for {_sym, list} <- coef_map do mean(list) end |> mean() 323 | # reform symbol list for portfolio 324 | sym_list = for {sym, _list} <- coef_map do sym end 325 | # return portfolio tuple with correlation coefficient and stocks 326 | {coef_avg, sym_list} 327 | end 328 | 329 | 330 | 331 | @doc false 332 | # pick portfolio with lowest correlation coefficient 333 | defp pick_lowest(x, nil), 334 | do: x 335 | 336 | defp pick_lowest({k, _v} = x, {acc_k, _acc_v} = acc) do 337 | cond do 338 | D.lt?(k, acc_k) -> x 339 | :else -> acc 340 | end 341 | end 342 | 343 | 344 | 345 | end -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Correlations.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :correlations, 7 | version: "0.1.1", 8 | elixir: "~> 1.10", 9 | start_permanent: Mix.env() == :prod, 10 | package: package(), 11 | description: description(), 12 | deps: deps() 13 | ] 14 | end 15 | 16 | defp description do 17 | """ 18 | A financial correlations library for elixir, fully compatible with the elixir Decimal library. 19 | """ 20 | end 21 | 22 | defp package do 23 | [ 24 | files: ["lib", "mix.exs", "README*"], 25 | maintainers: ["Gunnar Rosenberg"], 26 | licenses: ["MIT"], 27 | links: %{"GitHub" => "https://github.com/GunnarPDX/Correlations"} 28 | ] 29 | end 30 | 31 | # Run "mix help compile.app" to learn about applications. 32 | def application do 33 | [ 34 | extra_applications: [:logger] 35 | ] 36 | end 37 | 38 | # Run "mix help deps" to learn about dependencies. 39 | defp deps do 40 | [ 41 | {:decimal, "~> 2.0"}, 42 | {:jason, "~> 1.2"}, 43 | {:ex_doc, ">= 0.0.0", only: :dev, runtime: false} 44 | # {:dep_from_hexpm, "~> 0.3.0"}, 45 | # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 46 | ] 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, 3 | "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"}, 4 | "ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"}, 5 | "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, 6 | "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, 7 | "makeup_elixir": {:hex, :makeup_elixir, "0.15.0", "98312c9f0d3730fde4049985a1105da5155bfe5c11e47bdc7406d88e01e4219b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "75ffa34ab1056b7e24844c90bfc62aaf6f3a37a15faa76b07bc5eba27e4a8b4a"}, 8 | "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, 9 | } 10 | -------------------------------------------------------------------------------- /test/correlations_test.exs: -------------------------------------------------------------------------------- 1 | defmodule CorrelationsTest do 2 | use ExUnit.Case 3 | # TODO: fix hex docs formatting 4 | # doctest Correlations 5 | 6 | alias Correlations, as: C 7 | alias Decimal, as: D 8 | 9 | @ex_stocks [ 10 | aapl: [124.400002, 121.099998, 121.190002, 120.709999, 119.019997], 11 | nvda: [569.039978, 569.929993, 563.809998, 558.799988, 552.460022], 12 | tsla: [442.299988, 446.649994, 461.299988, 448.880005, 439.670013], 13 | amzn: [3442.929932, 3443.629883, 3363.709961, 3338.649902, 3272.709961] 14 | ] 15 | 16 | @ex_corr_list [ 17 | {0.809616345, [:aapl, :nvda]}, 18 | {0.104192125, [:aapl, :tsla]}, 19 | {0.674977695, [:aapl, :amzn]}, 20 | {0.198672188, [:nvda, :tsla]}, 21 | {0.867990065, [:amzn, :nvda]}, 22 | {0.236184044, [:amzn, :tsla]} 23 | ] 24 | 25 | @ex_corr_matrix [ 26 | [{:aapl, :amzn, 0.674977695}, {:aapl, :tsla, 0.104192125}, {:aapl, :nvda, 0.809616347}], 27 | [{:nvda, :amzn, 0.867990063}, {:nvda, :tsla, 0.198672188}], 28 | [{:tsla, :amzn, 0.236184044}] 29 | ] 30 | 31 | @ex_json_corr_matrix "[{\"color\":0.674977695,\"x\":1,\"xLabel\":\"aapl\",\"y\":1,\"yLabel\":\"amzn\"},{\"color\":0.104192125,\"x\":1,\"xLabel\":\"aapl\",\"y\":2,\"yLabel\":\"tsla\"},{\"color\":0.809616347,\"x\":1,\"xLabel\":\"aapl\",\"y\":3,\"yLabel\":\"nvda\"},{\"color\":0.867990063,\"x\":2,\"xLabel\":\"nvda\",\"y\":1,\"yLabel\":\"amzn\"},{\"color\":0.198672188,\"x\":2,\"xLabel\":\"nvda\",\"y\":2,\"yLabel\":\"tsla\"},{\"color\":0.236184044,\"x\":3,\"xLabel\":\"tsla\",\"y\":1,\"yLabel\":\"amzn\"}]" 32 | 33 | 34 | def decimalize(list), 35 | do: (for i <- list, do: D.from_float(i)) 36 | 37 | 38 | test "lowest correlation portfolio pick" do 39 | D.Context.set(%D.Context{D.Context.get() | precision: 9}) 40 | stocks_d = for {k, v} <- @ex_stocks, do: {k, decimalize(v)} 41 | 42 | res = C.portfolio_correlations_picker(stocks_d, 2) 43 | 44 | assert res == {D.from_float(0.104192125), [:aapl, :tsla]} 45 | end 46 | 47 | test "correlation list" do 48 | D.Context.set(%D.Context{D.Context.get() | precision: 9}) 49 | stocks_d = for {k, v} <- @ex_stocks, do: {k, decimalize(v)} 50 | ex_corr_list = for {v, list} <- @ex_corr_list, do: {D.from_float(v), list} 51 | 52 | res = C.portfolio_correlations_list(stocks_d, 2) 53 | 54 | assert res == ex_corr_list 55 | end 56 | 57 | test "correlations matrix" do 58 | D.Context.set(%D.Context{D.Context.get() | precision: 9}) 59 | stocks_d = for {k, v} <- @ex_stocks, do: {k, decimalize(v)} 60 | ex_corr_matrix = for l <- @ex_corr_matrix, do: (for {k1, k2, v} <- l, do: {k1, k2, D.from_float(v)}) 61 | 62 | res = C.correlation_matrix(stocks_d) 63 | 64 | assert res == ex_corr_matrix 65 | end 66 | 67 | test "json correlations matrix" do 68 | D.Context.set(%D.Context{D.Context.get() | precision: 9}) 69 | stocks_d = for {k, v} <- @ex_stocks, do: {k, decimalize(v)} 70 | 71 | {_, res} = C.json_correlation_matrix(stocks_d) 72 | 73 | assert res == @ex_json_corr_matrix 74 | end 75 | 76 | 77 | end -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------