Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
011c698
add merged status liveview for all exchanges
asmodehn Nov 8, 2022
163275b
fix xest/clock_test
asmodehn Nov 8, 2022
0b98373
fix race conditions in tests mocking datetime by making them synchronous
asmodehn Nov 9, 2022
5935bdf
add account's assets liveview.
asmodehn Nov 9, 2022
5ddd22c
add account's assets liveview test
asmodehn Nov 10, 2022
fbeab9a
add trades liveview and tests for one symbol
asmodehn Nov 12, 2022
5032359
add portfolio modul with first ideas of structure
asmodehn Nov 13, 2022
22fbec0
cleanup warnings on recent views
asmodehn Nov 14, 2022
4ff1c59
temporary disable multi-status view test
asmodehn Nov 14, 2022
23afc08
clean deprecated comments in status liveview
asmodehn Nov 14, 2022
57a0132
add xest_cache and xest_clock apps
asmodehn Nov 14, 2022
7746c95
xestclock implemented in the local usecase
asmodehn Nov 16, 2022
d617801
add simple module to manage a list of timestamps
asmodehn Nov 16, 2022
6ce9063
add a clock wrapping a stream of ticks
asmodehn Nov 18, 2022
8f5f39a
a cleaner clock, from wich a stream can be created
asmodehn Nov 18, 2022
b06ba12
basic event structure with integer as timestamp
asmodehn Nov 19, 2022
00cf8b9
add generic timestamp and remote event structs
asmodehn Nov 21, 2022
44491e8
add timestamp and timeinterval
asmodehn Nov 22, 2022
19ec0b5
remove old timestamps. fix remote.event
asmodehn Nov 22, 2022
bc84cf5
add tests for remote and local event modules. fixup clock
asmodehn Nov 22, 2022
e105d81
add sync event record concept as collectible. moving things around...
asmodehn Nov 23, 2022
5830525
fix xest_clock and Clock.Timeunit
asmodehn Nov 24, 2022
f5b80ef
add enumerable implementation for clock
asmodehn Nov 24, 2022
4d27e6b
improve enumerable clock
asmodehn Nov 29, 2022
bcce1c4
improve main xestclock module and tests
asmodehn Nov 29, 2022
908186d
add diff of timstamp and order of timeunit
asmodehn Nov 29, 2022
732d6c9
deducing remote datetime from offset
asmodehn Nov 29, 2022
c5d339f
remove event and record. redundant with stream clock design
asmodehn Nov 29, 2022
c1ea1d7
delete event leftover
asmodehn Nov 29, 2022
60726f5
add proxy with method to compute offset from a refernece clock
asmodehn Nov 30, 2022
99208c5
modified proxy module to manage offset
asmodehn Dec 1, 2022
10cd903
add genserver as test helper to test memory usage
asmodehn Dec 1, 2022
8e522e2
add stream_stepper based on gen_Stage to extract stream content one b…
asmodehn Dec 3, 2022
f2655ae
removed check_server. add monotone stream reimplemented from elixir s…
asmodehn Dec 3, 2022
5a8c60a
monotone strict implemented with dedup, sidestepping memory concerns
asmodehn Dec 5, 2022
151811d
fix monotone after getting rid of reducers
asmodehn Dec 5, 2022
197229a
add Clock.stream. fix monotone to not consume from stateful source
asmodehn Dec 5, 2022
96b25fa
moving timestamp general logic, to reorganise and split modules along…
asmodehn Dec 5, 2022
6d855b6
fix gen_stage dependency in xest_clock/mix.exs
asmodehn Dec 5, 2022
f13ff65
turn Clock.Stream into a struct behaving like a stream
asmodehn Dec 5, 2022
a260980
add convert/2 to Clock.Stream to allow unit conversion on the fly
asmodehn Dec 5, 2022
ce84976
add offset computation to Clock.Stream
asmodehn Dec 5, 2022
f6d29ac
removed old clock implementation
asmodehn Dec 5, 2022
e0a6b76
moved clockstream module to clock
asmodehn Dec 5, 2022
829b4ab
simpler stream stepper,hopfully fixes CI mem issues
asmodehn Dec 7, 2022
d4fa34e
extract proxy offset computation to client code
asmodehn Dec 7, 2022
0736e2e
review offset usage in proxy to make it closer to clock
asmodehn Dec 7, 2022
419d66d
improve xestclock module and move proxy closer to clock
asmodehn Dec 7, 2022
e40e1fd
integrate proxy offset functionality into clock
asmodehn Dec 7, 2022
7978b4a
replace proxy with clock with offset
asmodehn Dec 7, 2022
5558361
cleanup and cosmetics
asmodehn Dec 7, 2022
72e9284
clock now weakly monotonous. add docs and fix tests
asmodehn Jan 9, 2023
6614d24
fix otp version in github workflow
asmodehn Jan 9, 2023
b92d5d6
fix otp versioni and ubuntu vm version in github workflow
asmodehn Jan 9, 2023
44e2e60
attempt to have github action relying on asdf setup
asmodehn Jan 9, 2023
c48b4b0
working nebulex wrapper as xest_cache first version
asmodehn Jan 11, 2023
481b4a6
attempt to fix github workflow
asmodehn Jan 11, 2023
bc7e871
attempt to fix github workflow
asmodehn Jan 11, 2023
f69945b
remove rebar from tool-versions hopefully fixes github action
asmodehn Jan 11, 2023
5591321
update locked deps
asmodehn Jan 11, 2023
969d2d9
fix xest_cache and xest_clock as part of umbrella. moved datetime and…
asmodehn Jan 12, 2023
cb17b60
add streamstepper to lib as ticker
asmodehn Jan 13, 2023
e449b2e
flatten xest_clock lib structure
asmodehn Jan 13, 2023
d14e7d2
[xest_clock] rename clock -> streamclock
asmodehn Jan 13, 2023
fd2ef2e
changed streamclock to always reduce to timestamps. moved xest_clock …
asmodehn Jan 14, 2023
679356f
first working version of clock_server
asmodehn Jan 14, 2023
567741b
new design to allow explicit mocking of core elixir modules to test i…
asmodehn Jan 18, 2023
a14f115
get all tests to pass again after refactoring with mocks
asmodehn Jan 19, 2023
7537b2e
simplifying behaviors and mocks to minimize need for stubs
asmodehn Jan 20, 2023
5fa39ff
credo cleanup
asmodehn Jan 20, 2023
f155696
add some impl for String.Chars protocol for Timestmap
asmodehn Jan 20, 2023
5d61a4f
worldclockapi example using new Timestamp string conversion
asmodehn Jan 20, 2023
4da710b
Merge pull request #36 from asmodehn/tentative_clockserver_api
asmodehn Jan 20, 2023
26d5aff
first implementation of a rate limiter on a stream
asmodehn Jan 23, 2023
ab8a87b
extract Stream.Timed and use it for limiter
asmodehn Jan 23, 2023
0dd1ff1
Extract timed.localstamp to separate module
asmodehn Jan 23, 2023
4c4f73a
add two example clocks as validation tests
asmodehn Jan 25, 2023
18f9351
add timevalue to simplify computations in streamclock
asmodehn Jan 26, 2023
457f031
simplify code given current usecase
asmodehn Jan 27, 2023
584081a
add timed() to streamclock, along with mocks and tests fixed
asmodehn Jan 27, 2023
0bcc912
add limiter in clockstream and fix tests
asmodehn Jan 27, 2023
dadc7fd
add first proxy idea dump with tests. limited by stream design ?
asmodehn Jan 30, 2023
3b375bd
moved timestamps outside of clockstream, into the server
asmodehn Jan 31, 2023
5b3dbe2
with tests
asmodehn Jan 31, 2023
ae8f2c9
small api attempt for xestclock
asmodehn Feb 1, 2023
37068c8
consolidating timevalues, timestamp in time module
asmodehn Feb 1, 2023
0902ee5
cleanup time value and timestamp, as well as how computation on time …
asmodehn Feb 1, 2023
b00104e
add test for local stamp and local delta
asmodehn Feb 1, 2023
a8dae26
add estimate struct
asmodehn Feb 2, 2023
9a6fdf6
moved example server mocks in tests
asmodehn Feb 2, 2023
fd479e5
add livebook for documentation. reviewed how stream handles local tim…
asmodehn Feb 6, 2023
be431e8
handlng basic monotonic time request in server
asmodehn Feb 6, 2023
c7efcb1
fix xest_Web after phoenix upgrade
asmodehn Feb 6, 2023
a7a0013
remove broken check in old clock proxy test
asmodehn Feb 6, 2023
5c7ddd7
a first atempt at estimating remote clock error in server
asmodehn Feb 6, 2023
3bced79
remove offset from timevalue. add kino tutorial code in livebook
asmodehn Feb 10, 2023
106e985
add visualization of clock proxy error
asmodehn Feb 13, 2023
d440863
update roadmap on readme for xest_clock
asmodehn Feb 13, 2023
bfabca7
now measuring with mid time-of-flight for long running remote clock r…
asmodehn Feb 13, 2023
bcc1a10
fix throttled stream source with middle timestamp
asmodehn Feb 13, 2023
0d31250
fix error check to decide request to remote clock
asmodehn Feb 14, 2023
b925d02
handling errors in time values, but not in local timestamps
asmodehn Feb 16, 2023
f161aed
simplifying stream to always use maximum precision for local timestamps
asmodehn Feb 16, 2023
82f2a7b
simplify. server doesnt need a complete streamclock and delta doesnt …
asmodehn Feb 16, 2023
d6c8e04
integrate offset compute into the server
asmodehn Feb 17, 2023
ad84393
extract streamstepper from clockproxy server, and make them more modular
asmodehn Feb 17, 2023
e604ec4
make streamstepper usable as is, or via a custom module
asmodehn Feb 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,9 @@ on:

jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04

name: Build and Test on OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
strategy:
matrix:
otp: ['24.1.2'] #, '25.1.2']
elixir: ['1.12.3', '1.13.4']
steps:

- uses: actions/checkout@v2
Expand All @@ -25,8 +21,8 @@ jobs:

- uses: erlef/setup-beam@v1
with:
otp-version: ${{matrix.otp}}
elixir-version: ${{matrix.elixir}}
version-type: strict
version-file: .tool-versions

- name: Install dependencies
run: mix deps.get
Expand Down
3 changes: 1 addition & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
erlang 25.1.2
elixir 1.13.4-otp-24
elixir 1.14.3-otp-25
direnv 2.28.0
nodejs 18.12.0
rebar 3.15.0
22 changes: 22 additions & 0 deletions apps/xest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,25 @@ Timewise, the Adapter will poll to retrieve information and attempt to maintain
A cache is in place to avoid spamming servers. Later websockets could be used as well.
The Agent is there to keep a memory of the past events and expose current state.
The common Xest structures can extrapolate from past events to estimate current situation, where reasonably safe...


## Next Step

After some time, xest has grown into both conflicting conceptual apps/libs:
- one that is used as the interface to concrete connector implementation (kraken and binance) with behaviours, protocols, etc.
- one that is used as the "logical backend" for the web UI (and maybe more later)

This is problematic as:
- the web (and xest app) wants to make sure all connector implementations work as expected...
- connector implementations depend on xest to know how to communicate with the rest of the software.

There are conflicts in dependencies (although in an umbrella app, these might not be obvious at first),
but when changing some xest modules used everywhere, sometimes, only part of them are rebuilt by mix.

There are also likely other hidden conflicts brought by this current state of things.
At a high level, this resemble a Strategy Pattern, and we should therefore separate those two concerns in two apps/libs.

Proposed solution:
- a xest_connector lib, containing all modules necessary to build a working interface to a crypto exchange API.
- a xest app as it is now, only to be the client of these connectors (via an adapter, token with protocols, or some other convenient elixir design),
and the unique contact point from the web (and other UI apps).
11 changes: 10 additions & 1 deletion apps/xest/lib/xest/account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,21 @@ defmodule Xest.Account do
defmodule Behaviour do
@moduledoc "Behaviour to allow mocking a xest account for tests"
@callback balance(atom()) :: %Xest.Account.Balance{}
@callback balance(atom(), Keyword.t()) :: %Xest.Account.Balance{}
@callback transactions(atom()) :: %Xest.Account.TradesHistory{}
@callback transactions(atom(), String.t()) :: %Xest.Account.TradesHistory{}
end

def balance(connector) do
def balance(connector, opts \\ [filter: fn x -> Float.round(x, 8) != 0 end]) do
Xest.Account.Adapter.retrieve(connector, :balance)
|> Map.update!(
:balances,
&Enum.filter(&1, fn b ->
{free, ""} = Float.parse(b.free)
{locked, ""} = Float.parse(b.locked)
opts[:filter].(free) or opts[:filter].(locked)
end)
)
end

## OLD version: all trades (kraken)
Expand Down
16 changes: 16 additions & 0 deletions apps/xest/lib/xest/account/adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ defmodule Xest.Account.Adapter do
connector_response
end

def retrieve(:kraken, :trades, symbol) do
connector_response =
kraken().trades(
# looking for process via its name
Process.whereis(kraken()),
""
)
# TMP to get only one symbol (TODO: better in connector)
|> Map.update!(:history, &Enum.filter(&1, fn t -> t.symbol == symbol end))

connector_response
end

# TODO: retrieve trades for *all* symbol
# ... maybe page by page ??

def retrieve(:binance, :trades, symbol) do
connector_response =
binance().trades(
Expand Down
6 changes: 3 additions & 3 deletions apps/xest/lib/xest/api_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ defmodule Xest.APIServer do
def handle_call(request, from, state) do
{tmap, actual_state} = state
# Note: actual state should !never! impact request/response
case Xest.TransientMap.fetch(tmap, request) do
case XestCache.TransientMap.fetch(tmap, request) do
{:ok, hit} ->
{:reply, hit, state}

:error ->
case mockable_impl().handle_cachemiss(request, from, actual_state) do
{:reply, reply, new_state} ->
tmap = Xest.TransientMap.put(tmap, request, reply)
tmap = XestCache.TransientMap.put(tmap, request, reply)
{:reply, reply, {tmap, new_state}}

{:noreply, new_state} ->
Expand All @@ -71,7 +71,7 @@ defmodule Xest.APIServer do
# Here we add a trasient map to use as cache
# and prevent call to be handled in client code when possible
lifetime = Keyword.get(options, :lifetime, nil)
tmap = Xest.TransientMap.new(lifetime)
tmap = XestCache.TransientMap.new(lifetime)
# leveraging GenServer behaviour
GenServer.start_link(module, {tmap, init_arg}, options)
end
Expand Down
18 changes: 18 additions & 0 deletions apps/xest/lib/xest/clock.ex
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
defmodule Xest.Clock do
# NOT an alias.
require XestClock.DateTime
# In this module the DateTime.t() type is the core Elixir one.

defmodule Behaviour do
@moduledoc "Behaviour to allow mocking a xest clock for tests"
@callback utc_now(atom()) :: DateTime.t()
@callback utc_now() :: DateTime.t()
end

@behaviour Behaviour

@impl true
def utc_now() do
datetime().utc_now()
end

@impl true
def utc_now(:binance) do
binance().utc_now(
# finding the process (or nil if mocked)
Process.whereis(binance())
)
end

@impl true
def utc_now(:kraken) do
kraken().utc_now(
# finding the process (or nil if mocked)
Process.whereis(kraken())
)
end

defp datetime() do
Application.get_env(:xest_clock, :datetime_module, XestClock.DateTime)
end

defp kraken() do
Application.get_env(:xest, :kraken_clock)
end
Expand Down
4 changes: 2 additions & 2 deletions apps/xest/lib/xest/clock/proxy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ defmodule Xest.Clock.Proxy do

def expired?(proxy) do
# only request local utc_now when actually needed
expired?(proxy, Xest.DateTime.utc_now())
expired?(proxy, XestClock.DateTime.utc_now())
end

@spec expired?(t(), DateTime.t()) :: boolean()
Expand All @@ -59,7 +59,7 @@ defmodule Xest.Clock.Proxy do
end

@spec retrieve(t(), DateTime.t()) :: t()
def retrieve(proxy, requested_on \\ Xest.DateTime.utc_now())
def retrieve(proxy, requested_on \\ XestClock.DateTime.utc_now())

def retrieve(%__MODULE__{remote_clock: nil} = proxy, requested_on) do
proxy
Expand Down
1 change: 1 addition & 0 deletions apps/xest/lib/xest/exchange/adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ defmodule Xest.Exchange.Adapter do

def retrieve(:kraken, :symbols) do
kraken().symbols(Process.whereis(kraken()))
|> Map.keys()
end
end
2 changes: 1 addition & 1 deletion apps/xest/lib/xest/exchange/servertime.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Xest.Exchange.ServerTime do
import Algae

defdata do
servertime :: Xest.DateTime.t()
servertime :: XestClock.DateTime.t()
end
end

Expand Down
36 changes: 36 additions & 0 deletions apps/xest/lib/xest/portfolio.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
defmodule Xest.Portfolio do
@moduledoc false

# TODO a way to organise:
# - what is displayed and how, no matter the view
# - what can be done with what is displayed
# - the information that is relevant long term

# IDEA :
# a HODL list : currencies already owned, to hold onto (prevent inadvertent selling)
# a SELL list : currencies sellable (ready to trade as quote -default- or base)
# a BUY list : currencies that are buyable (ready to trade as quote -default- or base)
# Note both sell and buy currencies are "tradable".
# the list note the intent, waiting for an opportunity on the market...

# This creates a NEW list of the list we dont currently HOLD and want to sell

# Since it is an intent: the user must decide which currency goes into which list.
# The list must be remembered between runs... stored into user account/portfolio/bot configuration ?

# IDEA : 2 levels of user interactions/future bots
# - one level waiting for the *best* time to sell / buy (currently the user, but to be replaced when possible)
# - one level deciding what to sell / what to buy and on which exchange (currently the user)

# HELD + KEEP -> HODL list
# HELD + TRADE -> SELL list by default (BUY possible) + possible amount adjustment
# NEW + TRADE -> BUY list by default (SELL possible) + possible amount adjustment
# Stop condition for automatic exiting BUY and SELL list
# -> timeout
# -> market conditions...

# NOTE :
# - basic functionality first (check markets + spot trade).
# - wide exchange compatibilty second (need more users !).
# - detailed functionality (sub accounts, etc.) third.
end
3 changes: 3 additions & 0 deletions apps/xest/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ defmodule Xest.MixProject do
# Type `mix help deps` for examples and options.
defp deps do
[
{:xest_clock, in_umbrella: true},
{:xest_cache, in_umbrella: true},

# Tooling
{:mix_test_watch, "~> 1.0", only: :dev, runtime: false},
{:credo, "~> 1.5", only: [:dev, :test], runtime: false},
Expand Down
14 changes: 0 additions & 14 deletions apps/xest/test/support/datetime_stub.ex

This file was deleted.

11 changes: 9 additions & 2 deletions apps/xest/test/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ ExUnit.start()

# Datetime configuration for an optional mock,
# when setting local clock is required.
Hammox.defmock(Xest.DateTime.Mock, for: Xest.DateTime.Behaviour)
Hammox.stub_with(Xest.DateTime.Mock, Xest.DateTime.Stub)
Hammox.defmock(XestClock.DateTime.Mock, for: XestClock.DateTime.Behaviour)
# Hammox.stub_with(XestClock.DateTime.Mock, XestClock.DateTime.Stub)

Hammox.defmock(TestServerMock, for: Xest.APIServer)

Application.put_env(:xest, :api_test_server, TestServerMock)

# NOTE :here we depend on connector implementations,
# But only dynamically, and only for tests !!
# Therefore, the connectors should be built (for test mix env) previously to running the tests here
# TODO: investigate Protocols for a better design here...
# because current design breaks when renaming modules in different apps
# and running only some app's tests => dependency not detected -> not recompiled

# Mocking connector using provided behavior
Hammox.defmock(XestKraken.Exchange.Mock, for: XestKraken.Exchange.Behaviour)

Expand Down
17 changes: 12 additions & 5 deletions apps/xest/test/xest/api_server_test.exs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
defmodule Xest.APIServer.Test do
use ExUnit.Case, async: true
use ExUnit.Case, async: false
use FlowAssertions

alias Xest.DateTime
alias XestClock.DateTime
alias Xest.APIServer

# cf https://medium.com/genesisblock/elixir-concurrent-testing-architecture-13c5e37374dc
Expand Down Expand Up @@ -52,8 +52,15 @@ defmodule Xest.APIServer.Test do

describe "Given time to cache" do
setup do
# setting up datetime mock
Application.put_env(:xest, :datetime_module, Xest.DateTime.Mock)
# saving XestClock.DateTime implementation
previous_datetime = Application.get_env(:xest_clock, :datetime_module)
# Setup XestClock.DateTime Mock for these tests
Application.put_env(:xest_clock, :datetime_module, XestClock.DateTime.Mock)

on_exit(fn ->
# restoring config
Application.put_env(:xest_clock, :datetime_module, previous_datetime)
end)

# starts server test process
server_pid =
Expand All @@ -72,7 +79,7 @@ defmodule Xest.APIServer.Test do
{:reply, state, state}
end)

# because we need ot play with time here...
# because we need to play with time here...
DateTime.Mock
|> allow(self(), server_pid)

Expand Down
Loading