[ANN] Announcing Trading.jl

Hi All,

I’m delighted to announce Trading.jl, an event-driven algorithmic trading and backtesting package.

It provides an easy-to-use framework for defining and executing trading strategies based on technical indicators, as well as backtesting these strategies on historical data. The backtesting is set up in a way to be as equivalent to realtime trading as possible.

It builds on the ease of use, extensibility, inherent performance and elegance of the ECS (Entity-Component-System) paradigm, implemented through Overseer.jl, and keeps interoperability with the Julia ecosystem by interfacing with TimeSeries.jl.

Features

  • Highly customizable and extensible by being based on ECS
  • Backtesting and Realtime trading treated on completely equal footing
  • Several built in indicators such as SMA, EMA, MovingStdDev, RSI and Bollinger
  • Interaction with brokers such as Alpaca
  • High performance, with backtesting on 100k data points within seconds

Illustrative Example

To define a trading strategy, all you need to do is implement a Julia struct that subtypes System with an updatefunction that defines the trading logic. The update function is called periodically by the framework and has access to tick data for the tickers that the strategy was created for, as well as any technical indicators requested by the strategy. The package includes several built-in technical indicators such as simple moving averages, relative strength index, and exponential moving averages. Users can also define their own custom indicators.

struct MyStrategy <: System end

Overseer.requested_components(::MyStrategy) = (Open, Close, SMA{20, Close}, SMA{200, Close})

function Overseer.update(s::MyStrategy, trader, ticker_ledgers)
   for ledger in ticker_ledgers
        for e in new_entities(ledger, s)
            #Trading logic goes here
        end
    end
end

To execute a trading strategy in real-time, users can create a Trader object with the desired strategies, and connect it to a real-time data source through the different broker APIs:

broker = AlpacaBroker("<key_id>", "<secret>")
strategy = Strategy(:my_strategy, [MyStrategy()], tickers=["AAPL"])
trader = Trader(broker, strategies=[strategy])
start(trader)

If you want to backtest a trading strategy on historical data, you can use BackTester instead of Trader with the desired data range, interval, and strategies. The BackTester will simulate the behavior of a realtime Trader on the specified data. Afterward, a TimeArray can be created with the data from the trader, and used for performance analysis.

trader = BackTester(HistoricalBroker(broker),
                    strategies=[strategy],
                    start = <start date>,
                    stop  = <stop date>,
                    dt = <data timeframe>)
start(trader)

The Documentation is relatively complete and ever improving.

Have a look, let me know if you have any feedback, and happy trading!

Cheers

28 Likes

I really like how Trader and BackTester can be swapped. That seems elegant to me. There’s a lot I don’t understand yet (mostly due to being a Julia beginner), but I have some questions about this line:

Overseer.requested_components(::MyStrategy) = (Open, Close, SMA{20, Close}, SMA{200, Close})
  • What timeframe will the Open and Close values be in?
  • How does a strategy get access to the values for Open, Close, and SMA{20, Close}?

how to add broker support? is there an “interface” to implement? asking because I still have GitHub - Moelf/TDAmeritrade.jl: A pure Julia wrapper for TD Ameritrade APIs maintained

2 Likes

Yes, I’ll do a small writeup in the documentation. It’s not too much. I’ll have a look also at your api.

2 Likes

In the case of a BackTester there’s a dt keyword argument that will determine the frequency of bars that will come in and thus also those quantities. Ofc SMA{20,Close} will have nothing for the first 19 timesteps and then each timestep it will be the rolling value.

In the case of a realtime Trader it will be as fast as they stream in from the Broker. So usually 1 Min based. There’s an automatic interpolation that happens when for example a couple of timesteps no bars show up (I’ve had this happen using the Alpaca realtime stream) and then one comes in.

I am planning to make it a bit more controllable though, say if you want to have only 5Min based strategies, or at least an automatic binner.

In the definition of a Strategy you specify which tickers it works on. Those will be registered and the system will listen for bar updates. These are then streamed into the TickerLedgers, 1 for each of the tickers, and passed as the 3rd argument to the Overseer.update function of your strategy.

Derived quantities such as SMA{20, Close} will be automatically computed in the TickerLedgers as data rolls in.

1 Like

Looks really great! I was wondering if there’s an easy way to integrate trading costs? Especially in real-time trading the costs of the trade more often than not eat up all the profit.

Thank you for publishing this package! I wonder if it is possible to supply a limit order book to provide ask/bid price/volume info for performing more accurate backtesting?

The way that’s done right now when backtesting is through some settings in the HistoricalBroker (see in the middle of the page here):

broker = HistoricalBroker(AlpacaBroker(ENV["ALPACA_KEY_ID"], ENV["ALPACA_SECRET"]))

broker.variable_transaction_fee = 0.0
broker.fee_per_share = 0.005
broker.fixed_transaction_fee = 0.0;

I will clarify this a bit more in the HistoricalBroker documentation though thanks!

EDIT: I’m also considering to put in some possibility to delay the order execution, potentially randomly since I’ve found that to also be quite an important difference in Realtime vs Backtesting. Then it would be possible to play around with cancellation of Orders that weren’t filled etc, something that’s not quite in there yet

1 Like

I think technically this is absolutely possible, at least through Alpaca. There’s a way to ask for the quotes in a particular timerange. I’m not sure if this is exactly the order book but probably it could be derived from it.

For actual trades that happened there’s something similar with trades

Cool package.

Also, Alpaca looks pretty cool as well. Their paper trading platform looks like a good start but it does make some pretty strong/weird assumptions (that they are honest about). Just a word of caution if you run a paper account algo there and see good performance, they are assuming away a decent chunk of real world order outcomes, so your live money performance will be potentially quite quite different. I’m guessing here, but maybe you could offer better backtesting realism in the package, but with data from Alpaca (which is just from IEX).

Right, although for now even with their paper trading I haven’t really found a reliable strategy :stuck_out_tongue:, I also had those concerns and that’s why I thought about putting some more capability in the HistoricalBroker.

As another project I’m actually working a bit on implementing my own stock exchange thingy which would also use their data to simulate trading. But that’s mainly to improve my c++ so it’ll be a long time before that’s anywhere near as useful as what Alpaca offers already :stuck_out_tongue:

2 Likes

I’ve written up a page on how to integrate new brokers here. Let me know what you think!

3 Likes

Does this support equity and index options trades/backtesting?

That simply depends on your broker, the package is agnostic to what asset you’re trading. It uses strings for identification, so as long as your asset can be identified with a string and your broker can receive orders and give you data updates for those assets it is possible

2 Likes

this looks pretty interesting.

Have you put an example of your results on a webpage?

I’d like to see a real time example of how it works.

Maybe hook it up to a Yahoo or Google data feed? Exciting stuff well done.

1 Like

how about the greeks for options? Simple ones would be interesting delta/gamma. Also implied volatility would play a role so that would be of value. This looks VERY interesting to me

If one wanted to look at more than one timeframe at a time, is that possible with your system? For example, if the broker were feeding you 1m candles, could you derive candles in higher timeframes (like 30m and 4h) from them, and could strategies do analysis on those derived values?

Currently there’s no such system implemented, but it’s extremely simple to achieve though!

It’s very similar to the other indicator systems. You’d essentially have a Binner System which would look through components for a Bin{timeframe} component and then handle adding new values as bars stream in.

1 Like

Sorry for replying so delayed. It’s a bit difficult showing a realtime example, but since the backtester and realtime are essentially identical, if your system runs in a backtester it’ll run in realtime too. Whether it’s as performant a strategy is of course a very different question :smiley:.

It kind of depends on how much real market conditions are simulated by the HistoricalBroker during backtesting. For now the most basic assumptions on order execution etc are used and so might not reflect real world conditions very well. I am planning to implement more sim features in the HistoricalBroker but one step at a time (ofc help would speed things up).

So far I’ve been focused on implementing the framework’s features rather than trying out different types of strategies.

Orderbook support for example is almost there :smiley:

These should be very easy to implement similar to the current Indicator systems.