Comments (42)
Yes of course! Thanks for all your help! :)
from tai.
That seemed to fix it!
from tai.
Howdy @chrism2671,
Can you post a copy of your config?
The dummy keys are just to get the tests passing on CI, you should not need to do anything with them. If the venue you want to stream data allows public access without API credentials you can just set an empty map for accounts
when configuring the venue.
I believe OkEx
is the only venue out of the adapters I currently have that requires API credentials for streaming data.
from tai.
So I'm just using the example config with no modifications:
https://github.com/fremantle-capital/tai/blob/master/config/dev.exs.example
from tai.
I have a feeling the issue is coming from Confex (a module I'm not familiar with).
I tried replacing the :ex_poloniex config section with absolute paths:
config :ex_poloniex,
api_key: {:system_file, "/home/chris/code/elixir/tai/secrets/poloniex_api_key"},
api_secret: {:system_file, "/home/chris/code/elixir/tai/secrets/poloniex_api_secret"}
but that didn't seem to make any different.
I'm running:
MIX_ENV=dev elixir --sname tai -S mix run --no-halt
from tai.
(it does work if I strip out the confex adapter and just stick in the API keys as strings.)
from tai.
@chrism2671 have you set the environment variables for POLONIEX_API_KEY
& POLONIEX_API_SECRET
?
e.g.
export POLONIEX_API_KEY="/home/chris/code/elixir/tai/secrets/poloniex_api_key"
export POLONIEX_API_SECRET="/home/chris/code/elixir/tai/secrets/poloniex_api_secret"
MIX_ENV=dev elixir --sname tai -S mix run --no-halt
from tai.
Ah, I didn't do that, I hadn't realised that's what it was looking for.
I have actually got the thing running now by just pasting in API keys directly; I don't think there's anything wrong with tai
as such.
I have a couple of other questions.
I'm trying to write my first advisor. Let's say, for example, I want to do a moving average. I need to maintain that MA value somewhere. I'm trying to store it in %State{}
, but without much luck. How should I store advisor state?
from tai.
I think I figured it out (stick in {:ok, whatever_state_you_want}
, is that right?)
from tai.
@chrism2671 good to hear it's running for you now. This raises a good point though, that the configuration options are not sufficiently documented. I will add some more clarity to the README
.
To keep track of data while the advisor is running you can update the store
within %Tai.Advisor.State{}
like you mentioned by returning the tuple {:ok, new_store}
e.g.
def handle_inside_quote(_venue_id, _product_symbol, _market_quote, _changes, state) do
count = Map.get(state.store, :count, 0)
new_store = Map.put(state.store, :count, count + 1)
{:ok, new_store}
end
from tai.
Yep, that's exactly what I wrote.
I have another question. Is it possible to see the existing positions on an exchange? I can see Tai.VenuesAdapters.Bitmex.Positions
, but it's unclear to me if there's a general interface for this, and an analogue for OKEx.
Equally, for orders, is there a way to pull this from the exchange, rather than relying on tai
's internal state?
from tai.
One more question, is it possible to detect and handle executions?
from tai.
I have another question. Is it possible to see the existing positions on an exchange? I can see Tai.VenuesAdapters.Bitmex.Positions, but it's unclear to me if there's a general interface for this, and an analogue for OKEx.
@chrism2671 positions are a very early WIP. There is some starter code but it doesn't do much so I have not documented it anywhere. When I need to deal with positions I currently just track that in the advisors run_store
.
Equally, for orders, is there a way to pull this from the exchange, rather than relying on tai's internal state?
There is no way for tai to explicitly pull the order state. You would have to use the wrapper packages directly.
One more question, is it possible to detect and handle executions?
Yes. You can provide an order_updated_callback
.
from tai.
OK great. I might try to build that.
Is there an easy way to debug the contents of an advisor? When the advisor crashes, it just quits silently and changes status back to 'unstarted', without actually displaying the error. I'm working with iex -S mix
.
from tai.
@chrism2671 that would be super sweet!
Take a look at the video in the README for an example of the dev workflow. You can tail the log if you configure the file logger backend.
from tai.
Oh that helps!
So the error is that it's looking for handle_info/2
on my advisor. I've defined it as
def handle_info(_, state), do: {:noreply, state}
but I'm not sure I should be defining it at all?
Is there a way to inspect a running advisor's state? I tried Tai.Advisors.Store.all()
but that's not what I was looking for ;).
from tai.
@chrism2671 you will need to define handle_info
like you mentioned.
To inspect the state of the advisor you can do something like:
name = Tai.Advisor.to_name(group_id, advisor_id)
pid = Process.whereis(name)
state = :sys.get_state(pid)
from tai.
So consider a case where you have limit orders on one instrument, and you want to fire a market order as soon as the limit order is filled. You'd put the market orders in the order_updated
of the limit order, but in this case, state
is out of scope, so you wouldn't be able to update your position. Am I missing something?
I have to say, I'm really enjoying working with tai!
from tai.
Just as an update to this, I tried doing:
order_updated_callback: &order_updated/3
def order_updated(_previous_order, _updated_order, state) do
IO.inspect("Order updated")
IO.inspect state
{:ok, state.store}
end
But couldn't make this work (BadArityError). (My reasoning was looking at line 136 of advisor.ex
, callback.(old_order, updated_order, state)
).
from tai.
That's really cool to hear @chrism2671. Made my day π
Apologies for the delayed reply. I was out camping this weekend π
If you want to execute within the context of the advisor to have access to state
you can send the advisor process a message e.g.
advisor_name = Tai.Advisor.to_name(state.group_id, state.advisor_id)
%Tai.Trading.OrderSubmissions.BuyLimitGtc{
# β¦
order_updated_callback: &Tai.Advisor.cast_order_updated(advisor_name, &1, &2, callback, {:extra, :args})
}
|> Tai.Trading.Orders.create()
from tai.
I'm glad!
That seemed to work. I sent it through as a closure and state was available!
Just wondering, on PriceLevel
, should the price be Decimal
?
I'm trying to think about how to implement market orders. I can fire them with ExOkex (which I'm already using for pulling balances), or send limit orders priced to lift orders already on the book. Do you have any suggestions? It seems like price
is a required field for OrderStore
.
from tai.
Just wondering, on PriceLevel, should the price be Decimal?
I've elected to make the conversion to Decimal
as lazy as possible for performance reasons. I've taken the approach to try and just pass through the information from the venues as fast as possible in the venue adapters.
I'm trying to think about how to implement market orders. I can fire them with ExOkex (which I'm already using for pulling balances), or send limit orders priced to lift orders already on the book. Do you have any suggestions? It seems like price is a required field for OrderStore.
This is another deliberate choice. I'm avoiding market orders as much as possible due to the unreliability of venues. I don't want tai
to be associated with a crazy fat finger trade if possible. tai
only supports limit
orders at this point so market orders would have to be simulated by chasing the quotes through the order book.
from tai.
I've got things up and going now, and am testing on real orders.
I've got an issue on Bitmex live that appears to be the following when some orders are cancelled.
09:00:25.994 [info] {"data":{"account_id":"main","client_id":"e97a4ad7-975b-4eee-b8de-b409636173eb","close":null,"cumulative_qty":"0","enqueued_at":"2019-09-03T09:00:25.522402Z","error_reason
":"nil","last_received_at":"2019-09-03T09:00:25.891739Z","last_venue_timestamp":"2019-09-03T09:00:25.963Z","leaves_qty":"0","price":"0.01713","product_symbol":"ethu19","product_type":"future"
,"qty":"1","side":"sell","status":"canceled","time_in_force":"gtc","type":"limit","updated_at":"2019-09-03T09:00:25.992482Z","venue_id":"bitmex","venue_order_id":"38cf2237-604b-77e8-ea06-e867
d4394aa1"},"type":"Tai.OrderUpdated"}
09:00:26.041 [info] {"data":{"action":"passive_cancel","client_id":"e97a4ad7-975b-4eee-b8de-b409636173eb","required":["open","partially_filled","filled","expired","pending_amend","amend","ame
nd_error","pending_cancel","cancel_accepted"],"was":"canceled"},"type":"Tai.OrderUpdateInvalidStatus"}
09:00:26.164 [info] {"data":{"account_id":"main","client_id":"ba048b4e-e75c-4e95-8a66-709cb27ad3c5","close":null,"cumulative_qty":"0","enqueued_at":"2019-09-03T09:00:25.529810Z","error_reason
":"nil","last_received_at":"2019-09-03T09:00:26.161613Z","last_venue_timestamp":"2019-09-03T09:00:26.137Z","leaves_qty":"0","price":"0.01713","product_symbol":"ethu19","product_type":"future"
,"qty":"1","side":"sell","status":"canceled","time_in_force":"gtc","type":"limit","updated_at":"2019-09-03T09:00:26.162688Z","venue_id":"bitmex","venue_order_id":"e17b8701-a479-acd3-46fd-6892
486b0f04"},"type":"Tai.OrderUpdated"}
09:00:26.175 [error] Task #PID<0.597.0> started from :advisor_debug_main terminating
** (FunctionClauseError) no function clause matching in Tai.Trading.Orders.Cancel.notify_updated_order/1
(tai) lib/tai/trading/orders/cancel.ex:84: Tai.Trading.Orders.Cancel.notify_updated_order({:cancel_error, {:error, {:invalid_status, :canceled, :pending_cancel}}})
(elixir) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
(elixir) lib/task/supervised.ex:35: Task.Supervised.reply/5
(stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Function: #Function<0.29610064/0 in Tai.Trading.Orders.Cancel.cancel/1>
Args: []
I'm not 100% sure where this is coming from (it seems to be transient). Any thoughts?
from tai.
@chrism2671 give v0.0.35
a go. There are a bunch of passive order sequence bug fixes in there.
from tai.
@chrism2671 I've added an example ping/pong advisor which should provide you with a better reference to work from https://github.com/fremantle-capital/tai/tree/master/apps/examples/lib/examples/ping_pong
from tai.
Thanks!
I'm just trying to merge in the latest commit. My def init() no longer seems to fire. I did change it def after_start(), but I'm just trying to follow the logic. Should I be using init()?
Before:
def init(state) do
IO.inspect "Init"
store_update =
state.store
|> Map.put_new(:advisor_name, Tai.Advisor.to_name(state.group_id, state.advisor_id))
state = Map.put(state, :store, store_update)
{:ok, state, {:continue, :subscribe_to_products}}
end
After:
def after_start(state) do
IO.inspect "Init"
store_update =
state.store
|> Map.put_new(:advisor_name, Tai.Advisor.to_name(state.group_id, state.advisor_id))
state = Map.put(state, :store, store_update)
{:ok, state.store}
end
from tai.
Just dug a little more into this, confirmed after_state()
is running, but doesn't seem to be saving the state.
from tai.
@chrism2671 after_start
is the new hook you'll want to use. There's an update on latest master that saves the run_store
08f2f19
from tai.
Got it going, and moved everything over to handle_event().
I'm just trying to figure out; is there an easy way to refresh the balance register? It doesn't seem to track the exchanges (bitmex, okex), and I'm wondering if there was a good way to do it via REST. I can see hydrate_products_and_balances()
, but not a clear way to update it.
from tai.
@chrism2671 sweet. Sorry there has been a decent amount of churn recently in the core API...
There is no way to refresh the balances atm but it's definitely something I'd like to add. I just haven't required it so far. I would also like to refresh the products. But other priorities have been more important π
from tai.
What's the right way to log fills? Should I be doing this with order_updated_callback/2
or handle_info/2
?
from tai.
What's the right way to log fills? Should I be doing this with order_updated_callback/2 or handle_info/2?
order_updated_callback
You'll want to handle the :partially_filled
& :filled
status updates. e.g.
- https://github.com/fremantle-capital/tai/blob/master/apps/examples/lib/examples/ping_pong/manage_order_update.ex#L44
- https://github.com/fremantle-capital/tai/blob/master/apps/examples/lib/examples/ping_pong/manage_order_update.ex#L60
from tai.
Awesome, that worked really nicely.
I'm getting these back from Bitmex in my dev.log
on amend. The actual behaviour on the exchange seems fine.
β16:41:34.615 [warn] {"data":{"msg":{"account":14674,"clOrdID":"gtc-pmjhWzP8RIygWQcK/ari5A==","orderID":"8β
860b9f3-1c9b-487e-433b-04d58a01e0e5","price":0.01942,"symbol":"ETHZ19","timestamp":"2019-10-28T16:41:34.59β
4Z","transactTime":"2019-10-28T16:41:34.594Z"},"received_at":"2019-10-28T16:41:34.615085Z","venue_id":"bitβ
mex"},"type":"Tai.StreamMessageUnhandled"} β
β16:41:36.109 [warn] {"data":{"action":"Elixir.Tai.Trading.OrderStore.Actions.PendAmend","client_id":"c77bβ
a810-f7ce-4e68-8595-1727acbf8580","last_received_at":null,"last_venue_timestamp":null,"required":["open","β
partially_filled","amend_error"],"was":"pending_amend"},"type":"Tai.OrderUpdateInvalidStatus"}
This is while amending limit orders resting on bitmex. Can I safely ignore these? The actual amends seem fine (qty=1).
from tai.
I'll add it seems to get out of sync with the positions on bitmex (i.e. it misses a fill), maybe once every 12 hours (I compare the net position from the orders table with the position from REST every minute).
from tai.
lnav screenshot:
from tai.
It looks like you're trying to amend an order that is currently getting amended. You'll need to guard the status of the order before attempting the operation e.g. https://github.com/fremantle-capital/tai/blob/master/apps/examples/lib/examples/ping_pong/manage_quote_change.ex#L25
I'll add it seems to get out of sync with the positions on bitmex (i.e. it misses a fill), maybe once every 12 hours (I compare the net position from the orders table with the position from REST every minute).
Do you think this is related to the multiple amend operations? I've created a story and will keep an eye on it.
from tai.
Sorry, only just noticed this message! I added a bunch more stuff to make sure I was trying to amend an order that wasn't in an amend-able state, and that seems to have gotten rid of the error messages.
It does still get out of sync with Bitmex, this seems to happen after 12-24 hours of operation, and usually after there's been a lot of volatility, Bitmex has been :overloaded. I run a REST sanity check every minute.
As an aside, I sidecar-ed Grafana into it, which produces pretty graphs, and would definitely recommend using it if you don't already.
I have a question. I'm trying to get the Bitmex.Stream.Connection
to subscribe to the margin
channel on Bitmex. I can't clearly see where auth_channels get declared, and on start_link
, it seems like channels = []
. Can I declare this in the config file?
I know I'll need write handlers for this message type; I just want to get the data flowing in so I can figure it out.
from tai.
Heyo @chrism2671,
That's super dope! π I also use Grafana to do something similar.
Sorry, only just noticed this message! I added a bunch more stuff to make sure I was trying to amend an order that wasn't in an amend-able state, and that seems to have gotten rid of the error messages.
Great to hear
It does still get out of sync with Bitmex, this seems to happen after 12-24 hours of operation, and usually after there's been a lot of volatility, Bitmex has been :overloaded. I run a REST sanity check every minute.
Thanks for investigating. How do you feel about creating a PR that does this check and logs an event? It would be really handy to be able to alert on it.
from tai.
I have a question. I'm trying to get the Bitmex.Stream.Connection to subscribe to the margin channel on Bitmex. I can't clearly see where auth_channels get declared, and on start_link, it seems like channels = []. Can I declare this in the config file?
I know I'll need write handlers for this message type; I just want to get the data flowing in so I can figure it out.
Sorry overlooked this...
You do it in the venue configuration e.g.
config :tai,
venues: %{
bitmex: [
enabled: true,
adapter: Tai.VenueAdapters.Bitmex,
products: "xbtusd",
quote_depth: 3,
channels: [:margin],
accounts: %{
main: %{
api_key: {:system_file, "BITMEX_API_KEY"},
api_secret: {:system_file, "BITMEX_API_SECRET"}
}
}
]
You'll also need to add it to the list of optional channels in the BitMEX
connection. https://github.com/fremantle-capital/tai/blob/master/apps/tai/lib/tai/venue_adapters/bitmex/stream/connection.ex#L84
from tai.
Sorry for slow response!
Thanks for investigating. How do you feel about creating a PR that does this check and logs an event? It would be really handy to be able to alert on it.
Currently I do this in the advisor- 'tis a bit hacky & looks a bit like this:
def rest_position(state) do
case Tai.Venues.Config.parse_adapters[@venue_id] |> Tai.Venue.positions(:main) do
{:ok, []} ->
0
{:ok, positions} ->
position =
positions
|> Enum.filter(fn x -> x.product_symbol == state.config.bitmex_leg end)
|> hd
|> Map.get(:qty)
|> Decimal.to_integer
log_metric(state, "bitmex_position_rest", position)
if state.store[:initial_position] do
websocket_position = position_from_orders(state, :filled) + state.store.initial_position
sanity = websocket_position == position
if sanity == false do
Logger.error("Position out of sync websocket: #{websocket_position} rest: #{position}")
# shutdown(state, "Position out of sync websocket: #{websocket_position} rest: #{position}")
end
end
position
{:error, error} ->
Logger.error "Could not get bitmex position (using cached position) #{inspect error}"
initial_position = if state.store[:initial_position], do: state.store.initial_position, else: 0
position_from_orders(state, :filled) + initial_position
end
end
It probably makes sense to have some kind of sanity checking layer, but I'm not sure the best way to engineer this in (I'm not sure I'm a good enough coder (yet!) to contribute here!)
I do see two options.
- Do a 60 second position reconciliation via REST at the Adapter layer
- Add a method in Advisor that gets run every
x
seconds that allows someone to write their own sanity check. This is quite nice as it means writing sanity checks is a part of writing an Advisor, and it's probably easy to add.
I think Advisor probably also needs a on_shutdown() thing to clean up on a fail (cancel all orders, flatten all positions); that part might be quite straightforward.
from tai.
I like the idea of doing something like this at the adapter layer.
You can cancel all orders in the BitMEX adapter with a timer. Thanks to @yurikoval π
bitmex: [
enabled: true,
adapter: Tai.VenueAdapters.Bitmex,
products: "xbtusd",
quote_depth: 3,
accounts: %{
main: %{
api_key: {:system_file, "BITMEX_API_KEY"},
api_secret: {:system_file, "BITMEX_API_SECRET"}
}
},
opts: %{
autocancel: %{ping_interval_ms: 15_000, cancel_after_ms: 60_000}
}
]
from tai.
@chrism2671 can I close this issue as it seems like you're up and running now π
I've also added a built with tai section if you're ever interested in publicly releasing something you've built.
from tai.
Related Issues (12)
- Test Issue
- start error HOT 10
- iex -S mix hangs on 'generate tai app' HOT 8
- Manage orders in separate process to caller
- Add adapters to `OrderStore` & use etso by default HOT 1
- Add paging to `orders` command HOT 1
- Easy way to ingest market data from a Provider? HOT 5
- Seperation of Database from main tai package. HOT 4
- Installation failed HOT 1
- Questions HOT 1
- (FunctionClauseError) no function clause matching in Tai.VenueAdapters.Bitmex.Stream.RouteOrderBooks.handle_cast/2 HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. πππ
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from tai.