Log in

Strategy API

Introduction

If you're new to programming in Python, we recommend checking out some of the resources in our guide on learning how to code, before you start implementing your first strategy.

Tradewave is a scripted trading platform for the emerging new field of cryptocurrencies. We allow you to codify your trading strategies into simple Python scripts that analyse market data and make decisions on your behalf.

Your strategies run in a sandboxed Python environment. You are free to import most of Python’s standard library and for your convenience we also support some third party libraries.

Our Strategy API lets you access current and historical candle data, useful metadata (e.g. how many ticks the current backtest has been running for) as well as functions for placing orders and logging.

Your strategies are run by a stock Python 2.7.3 interpreter.

How this works

We never hold any of your funds. You transfer any funds you want to trade with into your favourite exchange and then send us the API keys (we'll show you how to do this).

Your bot runs on our servers. When your strategy performs a buy or sell, we place an order at the exchange on your behalf and ensure that it executes correctly.

Please note that during backtesting, no orders are placed and the execution is simulated.

Code structure

tick()

The meat of a strategy is defined by its tick() function. Your strategy must define this function as a minimum requirement.

The trading engine calls tick() repeatedly according to the tick interval parameter that you set, e.g. five minutes. New candle data is made available each time it is called.

Note: tick() is called immediately at the close of the last available candle for its interval. So if you set the tick interval to five minutes, and tick() is called at 16:20:00, the close price returned by data.btc_usd.close is for the candle that opened at 16:15:00.

initialize()

Optional function that is called before the first tick. This a good place to set any initial values in storage that you might want to use across ticks.

You shouldn't try to access the data variable or attempt to place orders in this function.

stop()

Optional function called at the end of a backtest, or when a live bot is stopped. This is a good place to tidy up your portfolio by closing any open positions and cancelling orders.

Note: during live trading, this optional function is given 15-seconds of execution time before the bot instance is stopped. An exception will be thrown if it exceeds the time limit. Please be aware that if the server hosting your bot fails unexpectedly, this may not be called. For scheduled maintenance (e.g. when we upgrade the trading engine) it will always be called and your bot will restart automatically once the maintenance is complete.

Programmatically stopping your strategy

Some strategies require the ability to stop trading under certain conditions. We provide the special Stop exception for this purpose:

  1. def tick():
  2. if data.btc_usd.close > 500:
  3. buy(pairs.btc_usd, 5)
  4. elif data.btc_usd.close <= 100:
  5. raise Stop() # you may want to stop trading under extraordinary conditions
  6. def stop():
  7. # If you provide a stop() function, it will be called immediately afterwards
  8. log('I got stopped.')

An example strategy

This is the Hello World of trading strategies. It makes use of storage and the optional initialize() and stop() functions.

  1. # A basic Moving Average Crossover strategy
  2. def initialize():
  3. storage.invested = False
  4. def tick():
  5. short_term = data.btc_usd.ma(30)
  6. long_term = data.btc_usd.ma(100)
  7. if (short_term > long_term) and not storage.invested:
  8. log('Buying BTC')
  9. buy(pairs.btc_usd) # market order
  10. storage.invested = True
  11. elif (short_term < long_term) and storage.invested:
  12. log('Selling all BTC')
  13. sell(pairs.btc_usd)
  14. storage.invested = False
  15. def stop():
  16. if storage.invested:
  17. log('Clearing our position by selling all BTC holdings')
  18. sell(pairs.btc_usd)

Constants

All constants evaluate to integers, but these could change, so we recommend that you use the names defined here to place orders and access historical data.

All of the constants below are exposed to your strategies.

currencies

  • currencies.btc
  • currencies.ltc
  • currencies.usd
  • currencies.eur

pairs

A pair describes a particular instrument, so for example if you have USD in your portfolio and you would like to buy BTC, you would place a buy order using the pairs.btc_usd instrument.

  • pairs.btc_usd
  • pairs.ltc_usd
  • pairs.ltc_btc
  • pairs.btc_eur
  • pairs.ltc_eur

intervals

These simply map to the equivalent value in seconds, i.e. intervals._1m evaluates to 60. You're free to use the value in seconds rather than the constant if you prefer.

  • intervals._1m (one minute)
  • intervals._5m
  • intervals._10m
  • intervals._15m
  • intervals._30m
  • intervals._1h (one hour)
  • intervals._2h
  • intervals._4h
  • intervals._12h

exchanges

  • exchanges.btce
  • exchanges.bitstamp
  • exchanges.bitfinex
  • exchanges.kraken
  • exchanges.atlasats

fees

Each exchange maintains it's own fee schedule and these sometimes vary across instruments. To look up the fees used internally by buy() and sell(), try this:

  1. fee = fees[exchanges.bitstamp][pairs.btc_usd] # returns Decimal('0.005')

Note: Some exchanges provide varying fees (e.g. high volume customers, maker-taker schedules). Currently the backtesting engine assumes the default fees, but we will be adding configurable options later. In the case of maker-taker exchanges like Bitfinex, the taker fee is used.

When a strategy is trading live, the engine will attempt to fetch the fees associated with your account, as they may differ if you are a high-volume customer. This will be reflected in the Order object that is returned when placing a buy or sell order.

Portfolio object

You can use the portfolio object to inspect your current funds at the exchange. During live trading, we update this data for you automatically, immediately before your tick() function is called.

During backtesting, the engine tries to simulate real conditions by updating these amounts as you place buy and sell orders.

  1. def tick():
  2. # Your current BTC holdings at the exchange (returns a Decimal object)
  3. my_bitcoin = portfolio.btc
  4. # Dictionary notation is also supported
  5. my_litecoin = portfolio[currencies.ltc]
  6. log(my_bitcoin) # logs: 50.00000000

Manually updating your portfolio

Sometimes you may want to update the portfolio object to reflect your latest funds, even while a tick() is still in progress.

Please note that we do this for you automatically before each tick, so usually you won't need to call this function.

Important: during live trading, we impose a per-minute rate limit on performing actions at most exchanges and we'll throw an exception in the rare case that you breach it. This is to prevent the exchanges from blacklisting our servers.

  1. def tick():
  2. # Manually update the portfolio object to reflect current funds (this call is blocking)
  3. portfolio.update()

Info object

The info object gives you access to metadata about the current backtesting or live trading session.

info.starting_portfolio

Your portfolio when the backtesting or live trading session started, before any orders were placed.

More about the Portfolio object

info.current_time

Time at the close of current candle (UTC). During backtesting this will be set according to when the original data was recorded.

Represented in seconds since the epoch.

info.running_time

Time since the backtest or live trading session started running, in seconds.

info.tick

Current tick number. Starts at zero.

info.max_ticks

How many ticks this session will run for (only available during backtesting).

info.interval

The tick interval you specified, in seconds.

info.primary_exchange

The exchange of the primary pair that you selected when running the backtest or live trading session.

info.primary_pair

The primary pair that you selected when running the backtest or live trading session. Example usage:

  1. # The current close price of your primary pair
  2. data[info.primary_pair].close

info.begin

The time at which the current backtest or live trading session started, in seconds since the epoch.

info.end

When the current session will end (only available during backtesting).

Candle data

The data provided by Tradewave is organised into OHLCV (Open, High, Low, Close, Volume) candles. By default these are aggregated across your chosen tick interval, but as you'll see below you can access multiple intervals in the same strategy.

If you try to access data from previous ticks too far in the past and we don't have that data available, a TradewaveDataError will be thrown and your backtest or live bot will terminate.

Usually you'll want to access a candle for the current tick, for example:

  1. current_close = data.btc_usd.close
  2. current_volume = data.btc_usd.volume

As you can see, the first attribute selects a pair, in this case BTC/USD. You can also access pairs using dictionary notation like this:

  1. data[pairs.btc_usd].close

All candle values are returned as Decimal objects. Here is the full list of the available attributes:

  • open
  • high
  • low
  • close
  • volume
  • trades
  • price (same as the close price)

The following example shows various ways to access candle data:

  1. data.btc_usd.open
  2. data[pairs.btc_usd].high
  3. data.btc_usd.volume
  4. data.ltc_usd.low
  5. data[pairs.ltc_usd].close

Indicators

Indicators work in a similar way to candles; most of them return a single Decimal object. For indicators that take a period parameter, please note that the current limit is 250 ticks in most cases.

MA (Simple Moving Average)

Parameters: ma(period)

  1. def tick():
  2. x = data.btc_usd.ma(30)
  3. plot('MA', x)

An overview of moving averages

EMA (Exponential Moving Average)

Parameters: ema(period)

  1. def tick():
  2. x = data.btc_usd.ema(30)
  3. plot('EMA', x)

An overview of moving averages

VWAP

Parameters: vwap(period)

  1. def tick():
  2. x = data.btc_usd.vwap(30)
  3. plot('VWAP', x)

Learn more about VWAP (Volume Weighted Average Price)

MACD

Parameters: macd(fast_period, slow_period, signal_period=9)

  1. def tick():
  2. macd, macd_signal, macd_histogram = data.btc_usd.macd(12, 26)
  3. value = macd_histogram[-1]
  4. plot('MACD', value, secondary=True) # Plot to a secondary y-axis

Learn more about MACD (Moving Average Convergence Divergence)

AROON

Parameters: aroon(period)

  1. def tick():
  2. aroon_down, aroon_up = data.btc_usd.aroon(30)
  3. plot('AROON_down', aroon_down, secondary=True)
  4. plot('AROON_up', aroon_up, secondary=True)

Learn more about AROON

SAR

Parameters: sar(acceleration, max_acceleration)

  1. def tick():
  2. x = data.btc_usd.sar(acceleration=0.02, max_acceleration=0.2)
  3. plot('SAR', x)

Learn more about SAR (Parabolic SAR)

RSI

Parameters: rsi(period)

  1. def tick():
  2. x = data.btc_usd.rsi(30)
  3. plot('RSI', x, secondary=True)

Learn more about RSI (Relative Strength Index)

StochRSI

Parameters: stochrsi(period, fastk_period, fastd_period, fastd_matype=0)

  1. def tick():
  2. fastk, fastd = data.btc_usd.stochrsi(30, fastk_period=5, fastd_period=3)
  3. plot('fastk', fastk, secondary=True) # Plots to the secondary y-axis
  4. plot('fastd', fastd, secondary=True)

Learn more about Stochastic RSI

STOCH

Parameters: stoch(fastk_period, slowk_period, slowd_period, slowk_matype=0, slowd_matype=0)

  1. def tick():
  2. slowk, slowd = data.btc_usd.stoch(fastk_period=5, slowk_period=3, slowd_period=3)
  3. plot('slowk', slowk, secondary=True)
  4. plot('slowd', slowd, secondary=True)

Learn more about STOCH (Stochastic Oscillator)

MFI

Parameters: mfi(period)

  1. def tick():
  2. x = data.btc_usd.mfi(15)
  3. plot('MFI', x, secondary=True)

Learn more about MFI (Money Flow Index)

ADX

Parameters: adx(period)

  1. def tick():
  2. x = data.btc_usd.adx(15)
  3. plot('ADX', x, secondary=True)

Learn more about ADX (Average Directional Movement Index)

ATR

Parameters: atr(period)

  1. def tick():
  2. x = data.btc_usd.atr(15)
  3. plot('ATR', x, secondary=True)

Learn more about ATR (Average True Range)

MOM

Parameters: mom(period)

  1. def tick():
  2. x = data.btc_usd.mom(30)
  3. plot('MOM', x, secondary=True)

Learn more about MOM (Momentum)

TSF

Parameters: tsf(period)

  1. def tick():
  2. x = data.btc_usd.tsf(30)
  3. plot('TSF', x)

Learn more about TSF (Time Series Forecast)

STD (Standard Deviation)

Parameters: std(period)

  1. def tick():
  2. x = data.btc_usd.std(30)
  3. plot('STD', x, secondary=True)

Using custom indicators

We provide a period() function for fetching batches of historical candle data. It returns a numpy.array object suitable for passing to TA-Lib indicators. Here's an example:

  1. import talib
  2. def tick():
  3. n = 30
  4. # Returns a numpy.array containing close prices from the previous 30 ticks
  5. prices = data.btc_usd.period(n, 'close')
  6. # TA-Lib indicators return arrays so we need to extract the last item
  7. result = talib.MA(prices, timeperiod=n)[-1] # identical to data.btc_usd.ma(n)

Some indicators, like EMA, need to be warmed up sufficently before they will return reliable data. Use the warmup_period() function to fetch a suitable array of historical values:

  1. import talib
  2. def tick():
  3. warmup = data.btc_usd.warmup_period('close') # can use any candle metric, e.g. 'open'
  4. result = talib.EMA(warmup, timeperiod=30)[-1] # identical to data.btc_usd.ema(30)

List of all TA-Lib indicators

Plotting

Often it can be useful to plot the results of indicators and other functions to the chart. You'll usually want to do this in your tick() function.

plot(series_key, value, secondary=False)

Plots a given value to the series that you specify. Calling this with the same series_key will plot values to the same continuous line. Here's an example using an indicator:

  1. def tick():
  2. plot('EMA_7', data.btc_usd.ema(7))
  3. plot('EMA_30', data.btc_usd.ema(30))

You can specify any string you like (letters, numbers and underscores only) for the series_key and it will appear in the chart's legend. Here's how our example looks in a real backtest:

This is fine if the values you are plotting are similar in magnitude to the closing price, which we plot to the chart automatically. If not, then you can specify that the values for a series are plotted to a secondary y-axis by passing secondary=True to the plot() function.

Here's a common example, using the MACD indicator:

  1. def tick():
  2. macd, macd_signal, macd_histogram = data.btc_usd.macd(12, 26)
  3. value = macd_histogram[-1]
  4. plot('MACD', value, secondary=True) # plot to a secondary y-axis

And here's the result. Notice the new y-axis on the left-hand-side of the chart:

Note: we only ever create one secondary y-axis, so if you plot multiple series with secondary=True be careful to ensure that they are of a similar magnitude.

Orders

buy(pair, amount=None, price=None, timeout=60)

Place a buy order for the given pair. If amount is not specified then the maximum amount will be assumed, given your current portfolio. For example, calling buy(pairs.btc_usd) would buy the maximum amount of BTC possible given how much USD you have in your portfolio.

If a price it not specified, a market order is assumed. Otherwise, a limit order is placed.

The timeout specifies how long to wait for the order to be filled until it is cancelled, and defaults to 60 seconds. The value is ignored during backtesting because orders are always filled immediately. If timeout=0 is given, the order will never be timed out by the trading engine (although the exchange may remove it automatically at some point.)

Note: during backtesting, a market order uses the close price of the current candle to estimate the market price.

If you do not hold enough funds to place this order, a TradewaveFundsError will be thrown. An invalid amount or price will trigger a TradewaveInvalidOrderError exception. Please see the section below for an example of how to handle these correctly.

  1. def tick():
  2. # When the price of Bitcoin falls below $500, buy 5 BTC at market price
  3. if data.btc_usd.close < 500:
  4. buy(pairs.btc_usd, 5)
  5. # When the price of Bitcoin breaches $800, sell all my BTC holdings
  6. elif data.btc_usd.close >= 800:
  7. sell(pairs.btc_usd)

Note: if you are deriving the order amount from other variables, we recommend working with the Decimal class for improved accuracy.

sell(pair, amount=None, price=None, timeout=60)

Place a sell order for the given pair. If amount is not specified then the maximum amount will be assumed, given your current portfolio. For example, calling sell(pairs.btc_usd) would sell all of the BTC held in your portfolio.

Please see buy() for more information. The same rules and mechanics apply to selling.

Handling errors when placing orders

If you place an invalid order (e.g. you might have insufficient funds) the engine will raise an exception. During live trading, this will cause your bot to stop.

If you would prefer your bot to remain running in these situations, you can catch the exceptions and handle them as you see fit. Here's an example that simply logs to the console and allows the strategy to continue running as normal:

  1. def tick():
  2. try:
  3. buy(pairs.btc_usd, 5)
  4. except TradewaveFundsError, e:
  5. log('Not enough funds!')
  6. except TradewaveInvalidOrderError, e: # thrown when you pass an invalid price or amount
  7. log('Unable to place buy order: %s' % e.message)

A note about market orders

Note: the following only applies to live trading. During backtesting we use the close price of the current candle to estimate the market price.

Some of the exchanges available through Tradewave provide support for native market orders. This means that an order can be placed without a price specified; the exchange will attempt to match it immediately to the most favourable order on the other side of the book.

In other cases (BTC-e, for example) the exchange doesn't provide the necessary order type, so the engine must approximate a market order by fetching the order book and placing a limit order at the best price.

The downside of this is that market prices can change quite quickly. In the short amount of time between the moment we receive the order book and when we place your order, the order book may have changed. To account for this, we apply a short timeout to these orders and automatically adjust the price if they have not filled within a 20-second window. This will happen up to a maximum of fifteen times; at that point, if your order hasn't filled, it will be cancelled.

Order objects

Both the buy() and sell() functions return an object containing information about your order. It also provides methods allowing you to easily update and cancel the order.

Note: during live trading, we update all pending orders 5-seconds before your tick() function is called. This means that order.filled will be updated automatically if you keep a reference to the original Order object. During backtesting, orders are always filled instantly

  1. $ order = buy(pairs.btc_usd, 0.5)
  2. $ log(order)
  3. '<Order: order_id=..., order_type=buy, filled=False, timed_out=False, cancelled=False>'
  4. # Cancel the order
  5. $ order.cancel() # this happens instantly, it won't wait for the next tick
  6. # You can access it as a dictionary, or as an object
  7. $ log(order.fee)
  8. '0.001 BTC'
  9. $ log(order['net'])
  10. '0.499 BTC'
  11. # This is done automatically before each tick(), but if you need to, you can
  12. # update the order details in real-time (a rate limit applies, don't do this too often)
  13. $ order.update()
  14. $ log(order.filled)
  15. 'True'

Below is a full list of attributes available on the Order object:

  • exchange
  • exchange_order_id (actual order ID used by the exchange)
  • order_id (generated by the trading engine)
  • timestamp
  • order_type
  • price
  • pair
  • timeout
  • fee
  • fee_percentage
  • cost
  • amount
  • net
  • filled
  • timed_out
  • cancelled

get_orders()

Returns a list of all Order objects created during this live trading session. These are automatically kept up-to-date by the engine.

Note: this will only return orders that were placed during this live trading or backtesting session.

cancel_order(order_id)

If you want to reference order IDs manually, we provide these functions that are not tied to an Order object. Here's an example:

  1. def tick():
  2. if huge_profit_opportunity:
  3. order = buy(pairs.ltc_usd)
  4. storage.my_order_id = order.order_id
  5. # ...some more logic...
  6. if oops_lets_cancel:
  7. cancel_order(storage.my_order_id)

get_order(order_id)

Returns an Order object with the given ID. All orders are automatically kept up-to-date by the engine.

Storage object

Always use the storage object if you need to persist any data. During live trading, we'll serialize and store it in our database immediately after your tick() function executes. If you stop and the bot and start it again later, your data will still be accessible.

Almost all of the standard Python types are supported, but an exception will be thrown if you try to store a function or object in there.

It supports attribute access, like this:

  1. storage.my_value = 1000
  2. log(storage.my_value) # writes: 1000

Or you can use it like a standard Python dictionary:

  1. storage['my_value'] = 1000
  2. log(storage['my_value']) # writes: 1000

Note: during live trading we serialize and persist this data so that is available across sessions (i.e. it remains available after manually restarting your bot, or if we restart your bot for maintenance/upgrade purposes). The engine will throw an exception if your storage object contains more than 512kb of data.

Correctly initializing data

It's common practice for strategies to initialize variables in the storage object so that they can be referenced later in your tick() function or elsewhere.

When a strategy is traded live we call your initialize() every time the bot is restarted. So to prevent your strategy from overwriting storage variables set in previous sessions, we recommend the following pattern:

  1. def initialize():
  2. # If "last_order_id" isn't already stored, sets it to an initial value of None
  3. storage.last_order_id = storage.get('last_order_id', None)

Note: this works because the storage object is represented internally as a standard Python dictionary.

Clearing existing data

As your strategy changes over time, you may find your storage object becoming cluttered with old, redundant data. You can clear it like this:

  1. def initialize():
  2. storage.reset()

Functions

log(object)

Converts the given object to a string and logs to console. Please note that print statements are currently ignored.

  1. def initialize():
  2. log('Starting!')
  3. def tick():
  4. log(data.btc_usd.close) # writes the close price on every tick

email(message, subject=None)

Sends an email to the address associated with the user who started the bot. This is useful for sending yourself notifications when certain events occur.

Note: Emails are ignored during backtesting.

Libraries

We provide the following statistical and scientific computing libraries for use in your strategies:

To use a third party library you'll need to import it manually. Here's an example:

  1. import talib
  2. import numpy as np
  3. def tick():
  4. my_array = np.array([data.btc_usd.low, data.btc_usd.high])
  5. log(my_array)

Advanced

Accessing data from previous ticks

The data object also lets you access previous tick intervals with list index notation:

  1. x = data.btc_usd[-1].high # the high price of candle in the previous tick
  2. y = data.btc_usd[-5].close # close price, five ticks ago
You can use the same technique for indicators too:
  1. # VWAP beginning three ticks ago, across the five previous ticks
  2. x = data.btc_usd[-3].vwap(5)

Note: indices must be negative integers or zero, so trying to access data.btc_usd[5] would throw an exception.

Accessing data from other exchanges

By default data is returned for the exchange you selected when you started the backtest or live trading session. You can access other exchanges as follows:

  1. # High price for current tick on the Bitstamp exchange
  2. x = data(exchange=exchanges.bitstamp).btc_usd.high
  3. # VWAP across the last five ticks on the BTC-E exchange
  4. y = data(exchange=exchanges.btce).ltc_usd.vwap(5)

Later we'll add the ability to place buy and sell orders on other exchanges, opening up the opportunity for arbitrage strategies.

Accessing data aggregated for other tick intervals

The default behaviour of data is to return data for the tick interval that your backtest or live bot is trading against. You can access other intervals as follows:

  1. # Close price for current tick, aggregated across the past five minutes
  2. x = data(interval=intervals._5m).btc_usd.close
  3. # You can express the interval in seconds too
  4. y = data(interval=300).btc_usd.close
  5. # It also works for indicators
  6. z = data(interval=intervals._1h).btc_usd.ema(30)

Bonus points: you can change the exchange and interval at the same time, like this:
data(exchange=exchanges.btce, interval=300).btc_usd.close

A note about smoothing

When you manually set an interval we always align the data going backwards from the current tick, such that there is always a full candle available. For some indicators, this can lead to an accurate but rather erratic signal.

To counteract this, we automatically smooth some indicators in certain situations, essentially by calculating the moving average of their results with a period proportional to the ratio between the aggregation and your chosen tick interval. If you would like to disable this behaviour, you can turn it off as follows:

  1. def tick():
  2. # If we ran this aggregated indicator along with a 1-hour tick interval, it would
  3. # automatically smooth for us, but we can disable it manually as follows
  4. x = data(interval=intervals._2h, smooth=False).btc_usd.ma(30)
  5. plot('MA', x)

Automatic smoothing for indicators is only activated when an interval greater than your chosen tick interval is set. The aggregation must also be wholly divisible by the tick interval, so for example, 15m/5m would be a candidate, but 15m/10m would not.

The following indicators are currently smoothed in these circumstances:

  • MA
  • EMA
  • Stoch
  • StochRSI
  • TSF
  • ATR
  • ADX
  • MFI
  • MOM
  • RSI
  • STD
  • VWAP

Other indicators are more complex and may require custom solutions. Please consult our discussion forums for more information.

Other attributes

Some of the internals are accessible if you need them:

  1. # The point at which the current candle's data began
  2. ts = data.btc_usd.timestamp # Unix time, i.e. seconds since the epoch
  3. dt = data.btc_usd.datetime # datetime.datetime object

External HTTP requests

Some strategies require external data sources in order to make trading decisions.

For example, maybe you found a service that reports the current number of web wallets across the main providers (Coinbase, Blockchain, etc.) and you think that growth in those numbers might precede spikes in the price of Bitcoin.

Each of the functions below throws a TradewaveHTTPError in case of a connection issue or if the retrieved data is not formatted correctly.

When you can make HTTP requests

In order to guarantee compatibility between backtesting and live trading, we only allow HTTP requests to be initiated in certain conditions.

To place GET requests using get_json() or get_text() you must define a new function in your strategy called update_external_data(). Using those functions anywhere else in your strategy will result in a TradewaveHTTPError being thrown. Here's an example:

  1. def initialize():
  2. storage.num_coinbase_wallets = None
  3. def update_external_data():
  4. obj = get_json('http://some.web.service/coinbase.json')
  5. storage.num_coinbase_wallets = obj['wallets']
  6. def tick():
  7. if storage.num_coinbase_wallets > 2000000:
  8. buy(pairs.btc_usd)

During backtesting, the engine will call update_external_data() once, immediately before your tick() function is called for the first time. In live trading, we call it before your tick() function but we do so every tick. You may not want that behaviour so the following example shows how to prevent it:

  1. def update_external_data():
  2. # Only perform the GET request before the first tick, not every time.
  3. # This works for both live trading and backtesting (although during backtesting
  4. # this function is only ever called once anyway)
  5. if info.tick == 0:
  6. text = get_text('http://some.web.service/a-news-story.html')
  7. storage.sentiment = my_sentiment_analysis(text)
  8. def tick():
  9. ...

The rules for POST requests are different. You can initiate a request with post() at any point in your strategy, but it's disabled during backtesting. In live trading, you can only call post() once per tick.

get_json(url, **kwargs)

Fetches the given resource and attempts to convert it to JSON using Python's json library. Using this function outside of your update_external_data() function will cause an error to be thrown.

This function wraps the requests library internally and you can pass along most of the supported keyword arguments.

get_text(url, **kwargs)

Fetches the given resource and returns raw text without any attempt to parse it. Using this function outside of your update_external_data() function will cause an error to be thrown.

post(url, data=None, **kwargs)

Initiates a POST request to a given URL. To pass a payload or custom parameters, please refer to the documentation for the requests library.

You can initiate a request with post() at any point in your strategy, but it's disabled during backtesting and will log a warning. In live trading, you can only call post() once per tick. Doing so multiple times will throw an error.

  1. def tick():
  2. ...
  3. params = {'to': '1212123456', 'message': 'I bought all the Bitcoin.'}
  4. post('http://some.web.service/send-sms/', data=params)

Catching errors

It's important to catch errors thrown when, for example, the web service you are fetching data from is down. Not doing so will cause your live trading bots to stop in this situation. All of the HTTP functions above throw a TradewaveHTTPError when something has gone wrong:

  1. def update_external_data():
  2. try:
  3. obj = get_json('http://some.web.service/coinbase.json')
  4. storage.num_coinbase_wallets = obj['wallets']
  5. except TradewaveHTTPError, e:
  6. log('Problem fetching wallet stats: %s' % e.message)
  7. storage.num_coinbase_wallets = None

Channels

Sometimes it can be useful for backtests and bots to communicate between each other. When a strategy is backtested or traded live, we allow you to share its storage object by associating a key with it to create a channel.

Channels can be useful when data needs to be shared between backtests or bots. For e.g. if one backtest feeds into antoher backtest or to live trading bot. You can also share with other Tradewave users by sharing the key. A channel is read-only, so any users that you share your key will be unable to write any data back to your storage object.

Choosing a key

To open a channel, you need to choose a unique string as the channel key.

  • Pick a long, difficult to guess key (15 characters is the minimum)
  • Keep it secret, unless you would purposely like to share your storage data with others
  • Set the channel in the initialize function, so all validations are done before it enters the tick function

Your key must be unique across the entire Tradewave platform. The engine will throw a TradewaveChannelError if your chosen key has already been used by someone else, or if you have already used it in an existing strategy.

Opening a channel

To share your bot's storage object, you need to tell the engine to open a channel:

  1. # Your key should be long and difficult to guess
  2. MY_KEY = '8c8f2474-35bb-486a-8655-b091bce92648'
  3. def initialize():
  4. # Opens a global, read-only channel to the storage object. From now on any changes you
  5. # make to storage are readable by anyone who knows the key.
  6. storage.set_channel(MY_KEY)
  7. def tick():
  8. # After this tick runs, the "signal" key will be accessible on the channel
  9. if data.btc_usd.close <= 500:
  10. storage.signal = 1
  11. elif data.btc_use.close > 500:
  12. storage.signal = 0

Please note that your bot's storage object will remain readable indefinitely to others who read via the channel key. If you would like to revoke access to those you have shared the key with, you should set a different key.

Reading from a channel

We provide a simple modifier on the storage object in order to access a channel. Furthermore, you can use the channel to read your storage between backtests and bots:

  1. MY_KEY = '8c8f2474-35bb-486a-8655-b091bce92648'
  2. def tick():
  3. try:
  4. channel = storage(channel=MY_KEY)
  5. current_signal = channel.signal
  6. except TradewaveChannelError, e:
  7. log('Unable to access the channel: %s' % e)
  8. current_signal = 0
  9. log('Current signal: %s' % current_signal)

A simple use case could be that a strategy is backtested to identify a ideal cross-over variable, which is then fed into the live bot via a channel key without stopping the bot.

Classes

We provide a few optional classes that you may find useful.

Money

Convenient if you want to attach a specific currency to amounts you are working with. Instances will throw an exception if you try to perform arithmetic on other Money objects representing a different currency.

Money uses Decimal internally so it supports exact floating-point arithmetic.

  1. my_btc = Money('100', 'btc')
  2. log(my_btc) # writes: 100.00 BTC
  3. log(my_btc.to_decimal()) # writes: 100
  4. log(my_btc.to_float()) # writes: 100.0

Note: Money forces true division for all division operators.

Decimal

Part of Python's standard library. If you're concerned about exactness in your calculations, especially in deciding amounts and prices for trades, you should think about using this class. You can read more about it here.

Keyboard shortcuts

The following keyboard shortcuts are available while you're using the code editor.

  • Save: Ctrl-S (or Cmd-S on Mac)
  • Backtest: Ctrl-B (or Cmd-B on Mac)
Log in