HFTS

Documentation

Everything you need to write, run, and interpret HFTS strategies.

Quickstart

A strategy is a Python class with a single required method on_calc that is called for every market tick. The simplest possible strategy:

class MyStrategy: def on_calc(self, tick): if not self.has_active_orders(): self.buy_market(0.01) # buy 0.01 BTC at market

Open the app, paste this into the editor, select a symbol and date range, and click Run.

Strategy lifecycle

MethodCalled when
__init__(self)Once before the first tick. Use to initialise state variables.
on_calc(self, tick)Every market tick (bid/ask update). Main strategy logic goes here.
on_ack(self, order)Called when the exchange acknowledges an order (open or instant fill). Optional.
on_complete(self, order)Called when a passive limit order is fully filled. Optional.
on_partial(self, order)Called when a passive limit order is partially filled. order.size is the amount filled in this event; the order remains open for the rest. Optional.
on_error(self, order, error)Called when an order is rejected. error is a string describing the reason (e.g. "not_enough", "postonly"). Optional.
class MyStrategy: def __init__(self): self.window = 20 # configurable via params self._prices = [] def on_calc(self, tick): self._prices.append(tick.ask) # strategy logic...

Tick object

Each call to on_calc receives a tick with the latest market state:

FieldTypeDescription
tick.bidfloatBest bid price
tick.askfloatBest ask price
tick.bid_sizefloatSize available at best bid
tick.ask_sizefloatSize available at best ask
tick.timestampdatetimeExchange timestamp of this quote
tick.sequenceintExchange sequence number (virtual)

Indicators

The sandbox includes talipp, a streaming technical indicator library designed for event-driven systems. Indicators update incrementally on each new value — no need to recompute over the full history on every tick.

from talipp.indicators import EMA, RSI, MACD class MyStrategy: def __init__(self): self._ema = EMA(20) def on_calc(self, tick): self._ema.add(tick.ask) if self._ema[-1] is not None: # use self._ema[-1] as the current value

Popular indicators available:

IndicatorDescription
EMA(period)Exponential moving average
SMA(period)Simple moving average
RSI(period)Relative strength index (0–100)
MACD(fast, slow, signal)MACD line, signal line, and histogram
BB(period, std_dev)Bollinger Bands — upper, middle, lower
STOCH(period)Stochastic oscillator (%K and %D)
ATR(period)Average true range
VWAP()Volume-weighted average price

Full list of supported indicators →

Need a different library? Additional packages can be added to the sandbox on request — reach out via the contact form.

Placing orders

All order methods are available on self inside on_calc.

Limit orders

MethodParametersDescription
buy_limit(price, size, post_only=False)price: float, size: float|None, post_only: boolPost a passive buy limit order. If size is None, uses all available USD. If post_only is True, the order is cancelled instead of filled when it would immediately cross the spread.
sell_limit(price, size, post_only=False)price: float, size: float|None, post_only: boolPost a passive sell limit order. If size is None, sells all BTC. If post_only is True, the order is cancelled instead of filled when it would immediately cross the spread.

Market orders

MethodParametersDescription
buy_market(size)size: float|NoneBuy at best ask immediately. If size is None, uses all available USD.
sell_market(size)size: float|NoneSell at best bid immediately. If size is None, sells all BTC.

Order management

MethodDescription
has_active_orders()Returns True if any order is currently open or pending fill.
last_order()Returns the most recently submitted order as an Order object, or None if no order has been placed yet.
cancel_all()Cancel all open orders.
# Breakout example def on_calc(self, tick): if self.has_active_orders(): return if tick.ask > self.resistance: self.buy_limit(tick.ask - 1.0, None) # all-in limit elif tick.bid < self.support: self.sell_limit(tick.bid + 1.0, None) # all-out limit

Wallet

The simulation starts with a fixed initial balance. Access current balances via:

FieldTypeDescription
self.wallet_quotefloatAvailable quote currency balance (e.g. USD)
self.wallet_basefloatAvailable base currency balance (e.g. BTC)

Setup methods

Call these inside __init__ to configure the run before it starts.

MethodParametersDescription
fund(amount)amount: floatSet the starting USD balance. Defaults to 10 000.0 if not called.
seed(value)value: intSeed the engine's random number generator for fully reproducible runs. Two runs with the same seed and parameters produce identical fill sequences and P&L. Defaults to a fixed seed if not called.
set_taker_fee(rate)rate: floatSet the taker fee multiplier applied to market orders and limit orders that cross the spread. Defaults to 0.997 (0.3% fee).
set_maker_fee(rate)rate: floatSet the maker fee multiplier applied to passive limit orders that rest in the book. Defaults to 1.000 (no fee).

Parameters

Any instance variables set in __init__ are treated as configurable parameters. They appear in the UI and can be overridden per run without editing code.

def __init__(self): self.window = 20 # lookback period self.threshold = 0.002 # breakout threshold (0.2%) self.size = 0.05 # order size in BTC

Prefix a variable with _ to mark it as internal state (not a parameter):

self._prices = [] # internal, not shown in UI

Report

After each run the report tab shows:

  • Accumulated P&L curve (Highstock interactive chart)
  • Per-trade table — side, price, size, fee, latency, status
  • Summary: total P&L, win rate, average latency, max drawdown, total fees
  • Cancelled and failed orders are flagged inline

Latency model

HFTS models the full round-trip delay for every order:

  • Sync delay — time from tick reception to order submission (your processing time)
  • Ack delay — exchange processing and acknowledgement time

Both delays are sampled from a configurable distribution (normal or uniform) with configurable min/max bounds. The order book state at the simulated arrival time is used for fill logic — so a late order can miss the price level entirely and get cancelled.

Fill rules

Order typeFeeFill condition
buy_limitMaker (0%)Ask drops to or below your price after ack delay. May fill as taker if the price crosses the spread at ack time. If the available size at that price level is smaller than the order, the order is partially filled and remains open for the remainder — on_partial is called for each partial fill, on_complete when fully filled.
sell_limitMaker (0%)Bid rises to or above your price after ack delay. May fill as taker if the price crosses the spread at ack time. Same partial fill behaviour as buy_limit.
buy_marketTaker (0.3%)Fills immediately at ask at ack time
sell_marketTaker (0.3%)Fills immediately at bid at ack time