Showing posts with label system trading. Show all posts
Showing posts with label system trading. Show all posts

Sunday, January 20, 2019

Quick backtest update [ STRATEGY-TREND-1: XBTUSD @ 2018-11-01 -> 2019-01-17 ]

STRATEGY-TREND-1: XBTUSD @ 2018-11-01 -> 2019-01-17 { git: a623d60742e96d35b8f7365496ffd99911aa7dbb }

NOTE: I'll post future backtests on this page ==> https://quantoga.blogspot.com/p/backtests.html

  • 10000 USD fixed position size (perpetual futures contracts).
  • Fees are included.
  • Limit orders are used for both entries and exits.
Huge improvements are still possible; i.e. even by adding basic kelly% for position sizing+++.

NOTE: the candle coloring isn't a very good representation of what's going on. The color is only based on avg position at a single point in time (the open). The candle at 2019-01-10 (2nd image) is shown as a long (green), but that's only for the first part of the candle before it goes short. It then goes long again before the next candle -- which is therefore green "again". In the end the initial loss for that duration is cancelled out by the gain from the brief short taken in the middle of the drop.


Don't pay too much attention to the colors in this; they are not correct or accurate.


...this is based on recent improvements to the strategy I did a screencapture of here: https://www.youtube.com/watch?v=JnCQ3qOKou4 ..actually, the biggest change is probably a move from trading based on discrete time to continuous time and the addition of a signal system with dedicated grace durations etc. etc..  

Thursday, January 10, 2019

Big data: from compressed text (e.g. CSV) to compressed binary format -- or why Nippy (Clojure) and java.io.DataOutputStream are awesome

Say you have massive amounts of historical market data in a common, gzip'ed CSV format or similar and you have these data types which represents instances of the data in your system:

(defrecord OFlow ;; Order flow; true trade and volume data!
    [^double trade ;; Positive = buy, negative = sell.
     ^double price ;; Average fill price.
     ^Keyword tick-direction ;; :plus | :zero-plus | :minus | :zero-minus
     ^long timestamp ;; We assume this is the ts for when the order executed in full.

     ^IMarketEvent memeta]

(defrecord MEMeta
    [^Keyword exchange-id
     ^String symbol
     ^long local-timestamp])


A good way to store and access this would be to use a binary format and a modern, fast compression algorithm. The key issue is fast decompression and LZ4HC is the best here as far as I'm aware of -- apparently reaching the limitations of what's possible with regards to RAM speed. To do this we'll use https://github.com/ptaoussanis/nippy which exposes the DataOutputStream class nicely and enables us to express a simple binary protocol for reading and writing our data types, like this:

(nippy/extend-freeze OFlow :QA/OFlow [^OFlow oflow output]
                     (.writeDouble output (.trade oflow))
                     (.writeDouble output (.price oflow))
                     (.writeByte output (case (.tick-direction oflow)
                                          :plus 0, :zero-plus 1, :minus 2, :zero-minus 3))
                     (.writeLong output (.timestamp oflow))
                     ;; MEMeta
                     (.writeUTF output (name (.exchange-id ^MEMeta (.memeta oflow))))
                     (.writeUTF output (.symbol ^MEMeta (.memeta oflow)))
                     (.writeLong output (.local-timestamp ^MEMeta (.memeta oflow))))

(nippy/extend-thaw :QA/OFlow [input]
                   (->OFlow (.readDouble input)
                            (.readDouble input)
                            (case (.readByte input)
                              0 :plus, 1 :zero-plus, 2 :minus, 3 :zero-minus)
                            (.readLong input)
                            (->MEMeta (keyword (.readUTF input))
                                      (.readUTF input)
                                      (.readLong input))))


..to write out the binary data to a file, you'd do something like (oflow-vector is a vector containing OFlow instances):

(nippy/freeze-to-file "data.dat" oflow-vector                                   
                      {:compressor nippy/lz4hc-compressor, :encryptor nil, :no-header? true})


..and to read it back in to get a vector of OFlow instances as the result you'd do something like this:

(nippy/thaw-from-file "data.dat"
                      {:compressor nippy/lz4hc-compressor, :encryptor nil, :no-header? true})


...it's so simple and the result is very, very good in terms of speed and space savings [I'll add some numbers here later]. Of course you'd still want to use something like PostgreSQL for indexed views or access to the data, but this is very nice for fast access to massive amounts of sequential, high resolution data. I've split things up in such a way that each file contains 1 day worth of data; this way it is possible to make fast requests to ranges of the data at any location without doing long, linear searches. 👍

Sunday, April 22, 2018

Roadmap: 2018

Last update: June 4rd 2018

Most recently being worked on first:
  1. Get rid of mutable state. General cleanup. Add better support for continuous time series data and overall model the system more around this instead of a strict assumption that everything is or should be (converted to) discrete time series data.
  2. More testing! Flesh out the rest of the old exchange API components – and do some testing on new exchanges also.
  3. Deal with trading system vs. exchange sync problems; see text at top of strategy_result_handler.clj. Test using demo account; try to trigger sync problems this time.
  4. Some "strategy helper" component is need that will deal with :post-only orders that are cancelled because they'd get instantly executed as market orders. Some retry logic with a limit as to how far it will stretch itself would be nice here.
  5. Architecture backtester in such a way that it's "just another exchange backend". This should make the code and general architecture simpler and more robust. It'll also enable us to have several backtester types.
  6. While I do handle order book streams, I do not store the historical data here in a DB; fix this.

Roadmap: finished

Most recently finished first:
  • Flesh out IExchange which will be used by the strategies to set proper order size and deal with exchanges that do not have dedicated TP / SL fields for their order / position objects. I.e. use a bidir map between entry order <--> tp/sl order. Separate this from exchanges that supports :linked-order-cancellation.
  • Handle the output generated by the backtester and strategies and prepare the result for efficient transmission to exchanges. Test using demo account; use fixed values for order size.
  • Unify the various market data type streams behind a common interface / protocol and update the code to not assume e.g. OHLC in so many places. Video update here.
27th april
30th april
1st may
  • Write some dummy test strategies to verify that the new backtester, trade planner and so on works (it does).
  • Add "trade planner"; wraps a base strategy expressed in high-level terms (indicator levels, signals, ++) and uses it to generate future limit entries and limit/stop-loss/take-profit -exits instead of trading using simple reactions (e.g. market orders) in the passing moment. TLDR: this makes it possible to build market making strategies.
  • Add support for order flow streams and analysis.
  • Refactor the old backtester; split it into smaller components (flexibility).
  • Add support for real order- and position objects instead of simple signals.

Future

Wednesday, February 7, 2018

2018: Working on new market making strategy; trading the Bitcoin bottom



....very much work in progress scratched down using pinescript. Basically mean reversion with pyramiding. Because of fees and execution latency this strategy will most likely only work well in the real world with limit orders for entries and exits – i.e. the strategy must plan ahead instead of reacting using market orders and/or stop-entries.

It seems to work well on traditional markets too.; natural gas:


....or things like forex; here AUDUSD:


...or more unusual pairs like AUDNZD:


..and stocks, like FB:


Tuesday, March 14, 2017

Position size & equity curves: linear vs. logarithmic scale


Here’s a short story (from this Wikipedia page) showing why getting position size right is very important:

In one study, each participant was given $25 and asked to bet on a coin that would land heads 60% of the time. The prizes were capped at $250. "Remarkably, 28% of the participants went bust, and the average payout was just $91. Only 21% of the participants reached the maximum. 18 of the 61 participants bet everything on one toss, while two-thirds gambled on tails at some stage in the experiment. Neither approach is in the least bit optimal." Using the Kelly criterion and based on the odds in the experiment, the right approach would be to bet 20% of the pot on each throw. If losing, the size of the bet gets cut; if winning, the stake increases.

 Anyway, after developing a new strategy, optimizing it then finally running something like Kelly% on it you might end up with an equity curve that looks like this:

..however when dealing with longer time frames, perhaps using a log scale for the equity curve (left hand side) might make things clearer? Take a look:


..much nicer! For some curves, log makes eyeballing things like the momentum of your returns much easier which might give you hints about a possible curve fit problem or similar – so this is not simply about aesthetics.

Happy weekend and trading!

Using a circular buffer to deal with slow exchange APIs

Using something like com.google.common.collect.EvictingQueue and only requesting the missing "tail" of your historical price data is much quicker for many broker APIs. This will help for brokers that sometimes return stale data and only supports polling i.e. you'd only poll for a limited amount of data.

In Clojure this would go something like this:

Thursday, March 9, 2017

Quick notes on double numeric type vs. integers

NOTE: If you're dealing with "money values" you should probably do things quite differently from what I'm talking about here -- but it all depends.

Benefits of using double (primitives) instead of longs (integers) as I see it for my use case i.e. number crunching in finance and statistics:
  • "Everything is the same". No need to constantly convert and cast back and forth between numeric types when doing e.g. division, sqrt and so on.
  • Things like NaN and Infinite (vs. Finite; testable) turns out to be really useful. E.g. NaN and Infinity will flow through your code and all future calculations without causing trouble and in the end your output will be NaN or Infinity. No need to check for and handle special cases all over the place – instead only doing this at the original input end; i.e. in one or just a few places.
  • Dividing by 0.0 does not lead to an exception which will disrupt the flow of my program. I will get Infinite as a normal return value instead.
  • Primitive doubles seem to be fast enough(?) and certainly accurate enough (I'm not dealing with money directly; only statistics which *in turn* will deal with money as integer type). GPUs also seem to have better and better support for the double primitive type so I don't feel I'm painting myself into a corner here.
  • Bonus: things like Double/POSITIVE_INFINITY, Double/NEGATIVE_INFINITY, Double/MAX_VALUE, Double/MIN_VALUE and so on can or might be used to denote special information or flags.
..I'm not sure how much I might be losing with regards to performance by using doubles instead of integers – if anything at all, but for convenience and simplicity it seems very good.