Custom Strategies
Templates are a great starting point, but the real power of the Algo Playground is writing your own strategies. With custom Python code, you can implement any trading logic — combine multiple indicators, add risk management rules, filter by time of day, or build entirely novel approaches.
What you can build
The Algo Playground gives you full access to:
- All OHLCV bar data — Open, high, low, close, and volume for every bar in your date range
- Order placement — Market, limit, and stop orders with any size
- Position management — Query open positions, close individual positions, or flatten everything
- Final results — The run’s closing balance and net P&L from
Replay/end, printed by the cleanup section - Raw API access — Call any Simulator API endpoint directly for advanced use cases
- Custom indicators — Compute any indicator from raw price data using Python standard library math
Since strategies run as standalone Python scripts with only standard library imports, there are no external dependencies to manage. You compute everything from raw data — which also means your code has no dependency on TestMax-specific libraries.
The Custom template
When you select the Custom template in the Playground, you get a blank strategy with all the infrastructure already set up:
# Available functions:# place_order(ACCOUNT_ID, CONTRACT_ID, side, size) — side: 0=Buy, 1=Sell# get_positions(ACCOUNT_ID) — returns open positions# close_all_positions(ACCOUNT_ID, CONTRACT_ID) — flatten all positions# get_account(ACCOUNT_ID) — returns account info# api("endpoint", body) — call any Simulator API endpoint## Each bar is a dict: { "t": timestamp, "o": open, "h": high, "l": low, "c": close, "v": volume }
closes = []position = Nonetrade_count = 0wins = 0entry_price = 0
print("[INFO] Strategy: Custom")
try: for i in range(MAX_BARS): bar = get_next_bar() if bar is None: break
closes.append(bar["c"]) read_speed() if STEP_DELAY > 0: time.sleep(STEP_DELAY)
price = bar["c"]
# ──── Write your trading logic below ──── pass # Replace with your logicThis is an excerpt. The editor holds the complete script: the header (imports, config, API helpers) and session setup sit above it, and the cleanup (the finally: block and results) sits below it. Whatever is in the editor runs as the whole file, so write your logic inside the main loop and leave the rest of the scaffold in place.
Development workflow
Here is a recommended workflow for building custom strategies:
- Start from a template. Run an existing template to understand the structure and output format.
- Switch to Custom. Select the Custom template and start modifying the main loop.
- Test incrementally. Change the
MAX_BARSline in the code to a small number (e.g.,MAX_BARS = 100) while developing. This lets you iterate quickly without waiting for a full backtest. - Use print() for debugging. Add
print(f"[INFO] ...")statements to inspect variable values and logic flow. The output appears in real time in the Output tab. - Run a full backtest. Once your logic works on a small sample, restore the original
MAX_BARSline and run against the full date range. - Save your strategy. When you have something worth keeping, save it with a descriptive name so you can reload it later.
Example: SMA + RSI combo strategy
Here is a quick example of a strategy that combines two indicators — only taking SMA crossover entries that are confirmed by RSI. It replaces the strategy section of the Custom Strategy script (from the state variables to the end of the loop); keep the header, setup and cleanup around it:
# ParametersFAST = 10SLOW = 30RSI_PERIOD = 14
closes = []position = Nonetrade_count = 0wins = 0entry_price = 0
def sma(data, period): if len(data) < period: return None return sum(data[-period:]) / period
def calc_rsi(data, period): if len(data) < period + 1: return 50 gains, losses = [], [] for j in range(len(data) - period, len(data)): change = data[j] - data[j - 1] gains.append(max(0, change)) losses.append(max(0, -change)) avg_gain = sum(gains) / period avg_loss = sum(losses) / period if avg_loss == 0: return 100 return 100 - (100 / (1 + avg_gain / avg_loss))
print("[INFO] Strategy: SMA + RSI Combo")
try: for i in range(MAX_BARS): bar = get_next_bar() if bar is None: break
closes.append(bar["c"]) read_speed() if STEP_DELAY > 0: time.sleep(STEP_DELAY)
if len(closes) < SLOW: continue
fast_ma = sma(closes, FAST) slow_ma = sma(closes, SLOW) rsi = calc_rsi(closes, RSI_PERIOD) price = bar["c"]
# Buy: fast SMA above slow SMA AND RSI above 50 if fast_ma > slow_ma and rsi > 50 and position != "LONG": if position == "SHORT": place_order(ACCOUNT_ID, CONTRACT_ID, 0, 2) if entry_price - price > 0: wins += 1 trade_count += 1 else: place_order(ACCOUNT_ID, CONTRACT_ID, 0, 1) position = "LONG" entry_price = price trade_count += 1 print(f"[TRADE] BUY @ {price:.2f} | RSI: {rsi:.1f}")
# Sell: fast SMA below slow SMA AND RSI below 50 elif fast_ma < slow_ma and rsi < 50 and position != "SHORT": if position == "LONG": place_order(ACCOUNT_ID, CONTRACT_ID, 1, 2) if price - entry_price > 0: wins += 1 trade_count += 1 else: place_order(ACCOUNT_ID, CONTRACT_ID, 1, 1) position = "SHORT" entry_price = price trade_count += 1 print(f"[TRADE] SELL @ {price:.2f} | RSI: {rsi:.1f}")What you need to know
Before writing custom strategies, make sure you understand: