Your First Algorithm
This tutorial walks you through building a complete trading algorithm from scratch. By the end, you will have a working strategy that places real orders, manages a position, and reports its performance — all in under 80 lines of Python.
What We Are Building
A simple interval-based strategy:
- Buy every 100 bars
- Hold for 50 bars, then sell
- Track P&L and print results
It is not a profitable strategy — it is a learning exercise that teaches you how every part of the TestMax API works.
Understanding the Environment
Before writing any code, let’s understand what the Algo Playground gives you:
# These environment variables are pre-set by the Playground:ACCOUNT_ID # Your simulated trading accountCONTRACT_ID # The futures contract (e.g., NQ, ES)TOTAL_BARS # How many bars are in the backtestSTEP_DELAY # Delay between bars (controls playback speed)SPEED_FILE # File path for reading speed from the UI sliderYou also get these functions ready to use:
get_next_bar()— fetch the next OHLCV barplace_order()— submit a buy or sell orderget_positions()— check what you currently holdclose_all_positions()— flatten everythingget_account()— get balance and account inforead_speed()— sync with the UI speed slider
Building the Strategy
Step 1: Set Up the Boilerplate
Every TestMax strategy starts with the same skeleton. This reads environment variables and sets up the main loop:
import os, time
ACCOUNT_ID = int(os.environ.get("ACCOUNT_ID", "0"))CONTRACT_ID = os.environ.get("CONTRACT_ID", "")TOTAL_BARS = int(os.environ.get("TOTAL_BARS", "5000"))STEP_DELAY = float(os.environ.get("STEP_DELAY", "0.02"))SPEED_FILE = os.environ.get("SPEED_FILE", "")
print(f"[INFO] Account: {ACCOUNT_ID}, Contract: {CONTRACT_ID}")print(f"[INFO] Total Bars: {TOTAL_BARS}")print("-" * 50)This is the same for every strategy. Copy it once, reuse it forever.
Step 2: Build the Main Loop (Just Logging)
Before placing any orders, let’s just read bars and print them. This verifies everything is connected:
for i in range(TOTAL_BARS): bar = get_next_bar() if bar is None: print("[INFO] No more bars") break
read_speed() if STEP_DELAY > 0: time.sleep(STEP_DELAY)
# Print every 100th bar so we don't flood the console if i % 100 == 0: print(f"Bar {i}: O={bar['o']:.2f} H={bar['h']:.2f} L={bar['l']:.2f} C={bar['c']:.2f} V={bar['v']}")Try it now. Paste this into the Playground, select any instrument and date, and run it. You should see bar data scrolling in the console.
Step 3: Add Position Tracking Variables
Now let’s add variables to track our position state:
position = None # None, 'LONG', or 'SHORT'entry_bar = 0 # Which bar number we entered onentry_price = 0.0 # The price we entered attrade_count = 0 # Total trades takenwins = 0 # Winning tradesStep 4: Add Buy Logic — Enter Every 100 Bars
Inside the main loop, after reading the bar, add the entry condition:
price = bar["c"] # Use the closing price
# Entry: buy every 100 bars if we have no position if position is None and i % 100 == 0 and i > 0: result = place_order(ACCOUNT_ID, CONTRACT_ID, 0, 1) # side=0 (Buy), size=1 contract if result.get("orderId"): position = "LONG" entry_bar = i entry_price = price print(f"[BUY] Bar {i} @ {price:.2f}")Let’s break down place_order:
ACCOUNT_ID— which account to trade onCONTRACT_ID— which instrument0— side: 0 = Buy, 1 = Sell1— size: 1 contract
It returns a dict with orderId if the order was accepted.
Step 5: Add Sell Logic — Exit After 50 Bars
Now add the exit condition. If we have been in a position for 50 bars, sell:
# Exit: sell after holding for 50 bars if position == "LONG" and (i - entry_bar) >= 50: result = place_order(ACCOUNT_ID, CONTRACT_ID, 1, 1) # side=1 (Sell) if result.get("orderId"): pnl_points = price - entry_price trade_count += 1 if pnl_points > 0: wins += 1 print(f"[SELL] Bar {i} @ {price:.2f} | P&L: {pnl_points:+.2f} pts") position = NoneStep 6: Add Cleanup and Summary
After the main loop, always close any open positions and print a summary:
# Cleanup: close any remaining positiontry: close_all_positions(ACCOUNT_ID, CONTRACT_ID)except: pass
# Print summaryaccount = get_account(ACCOUNT_ID)balance = account["balance"] if account else 50000pnl = balance - 50000
print("-" * 50)print(f"[DONE] Final Balance: ${balance:,.2f}")print(f"[DONE] Net P&L: ${pnl:+,.2f}")print(f"[DONE] Total Trades: {trade_count}")if trade_count > 0: print(f"[DONE] Win Rate: {wins / trade_count * 100:.1f}%")Complete Code
Here is the entire strategy in one block. Copy and paste this into the Algo Playground:
#!/usr/bin/env python3"""My First Algorithm — TestMax Algo PlaygroundBuy every 100 bars, sell 50 bars later."""import os, time
# --- Setup ---ACCOUNT_ID = int(os.environ.get("ACCOUNT_ID", "0"))CONTRACT_ID = os.environ.get("CONTRACT_ID", "")TOTAL_BARS = int(os.environ.get("TOTAL_BARS", "5000"))STEP_DELAY = float(os.environ.get("STEP_DELAY", "0.02"))SPEED_FILE = os.environ.get("SPEED_FILE", "")
print(f"[INFO] Account: {ACCOUNT_ID}, Contract: {CONTRACT_ID}")print(f"[INFO] Strategy: Buy every 100 bars, hold for 50")print("-" * 50)
# --- State ---position = Noneentry_bar = 0entry_price = 0.0trade_count = 0wins = 0
# --- Main Loop ---try: for i in range(TOTAL_BARS): bar = get_next_bar() if bar is None: print("[INFO] No more bars") break
read_speed() if STEP_DELAY > 0: time.sleep(STEP_DELAY)
price = bar["c"]
# Log progress every 500 bars if i % 500 == 0: print(f"[INFO] Bar {i}/{TOTAL_BARS} | Price: {price:.2f}")
# --- Entry: buy every 100 bars --- if position is None and i % 100 == 0 and i > 0: result = place_order(ACCOUNT_ID, CONTRACT_ID, 0, 1) if result.get("orderId"): position = "LONG" entry_bar = i entry_price = price print(f"[BUY] Bar {i} @ {price:.2f}")
# --- Exit: sell after 50 bars --- if position == "LONG" and (i - entry_bar) >= 50: result = place_order(ACCOUNT_ID, CONTRACT_ID, 1, 1) if result.get("orderId"): pnl_points = price - entry_price trade_count += 1 if pnl_points > 0: wins += 1 print(f"[SELL] Bar {i} @ {price:.2f} | P&L: {pnl_points:+.2f} pts") position = None
finally: # Always clean up try: close_all_positions(ACCOUNT_ID, CONTRACT_ID) except: pass
account = get_account(ACCOUNT_ID) balance = account["balance"] if account else 50000 pnl = balance - 50000
print("-" * 50) print(f"[DONE] Final Balance: ${balance:,.2f}") print(f"[DONE] Net P&L: ${pnl:+,.2f}") print(f"[DONE] Total Trades: {trade_count}") if trade_count > 0: print(f"[DONE] Win Rate: {wins / trade_count * 100:.1f}%")Key Takeaways
-
get_next_bar()returns one OHLCV bar at a time. Your entire strategy runs inside aforloop that calls this on every iteration. -
place_order(account, contract, side, size)is how you trade. Side 0 = Buy, Side 1 = Sell. Always check the result fororderIdto confirm it was accepted. -
Always call
read_speed()inside your loop. This lets the UI speed slider control playback. -
Always wrap your loop in
try/finallyand callclose_all_positions()in thefinallyblock. This ensures positions are cleaned up even if your code crashes. -
Use
get_account()at the end to get the actual balance. TestMax tracks the real P&L including slippage and fills — do not rely solely on your own calculations.
Exercises
Try modifying this strategy to learn more:
- Change the interval — try buying every 50 bars or every 200 bars. Does the result change?
- Add short selling — alternate between buying and selling. Use
side=1to go short. - Add a simple filter — only buy if the current bar’s close is above its open (a green candle).
- Track more stats — calculate the average win and average loss in points.
What’s Next
This strategy trades blindly on a timer. In the next tutorials, you will learn to make decisions based on actual market data:
- Understanding OHLCV Bars — what the bar data actually means
- Moving Average Strategies — your first indicator-based strategy