Skip to content

Python basics for algo trading

Traders use Python to build trading bots, technical indicators, plugins and other tools to aid algo trading or manual operations, and this article explains coding basics in Python.

Python is a popular, high-level, general-purpose programming language used for web development, data science, automation and more recently, algorithmic trading in cTrader. Whether you are creating simple signal-based trading bots or complex multi-indicator strategies, Python makes the process faster, more efficient and easier to manage.

Python algorithms in one minute!

  • Experience built-in Python support and seamless access to the cTrader Algo API, with cTrader being the only major platform offering native integration out of the box. No plugins, adapters or complex setups required.
  • Focus more on what your strategy should achieve as the logic becomes easier to assemble. Enjoy rapid iteration, reusable components and scalable performance.
  • Develop quickly, debug smarter and collaborate without barriers. Your code will always be adaptable, reliable and maintainable.
  • Access and benefit from thousands of free/open-source libraries, in-depth tutorials, code samples and strong community support worldwide.

Variables and data types

In Python, variables are dynamically typed, which means you do not have to declare their data types explicitly. The interpreter determines the type at runtime based on the value assigned.

You create variables by simply assigning a value to one:

1
2
3
4
price = 105.50         # float
enabled = True         # boolean
symbol = "EURUSD"      # string
orders = []            # empty list

Common data types

Type Description Example
int Whole numbers 5, 100, api.StepPips
float Decimal numbers 1.1234, 100.5, api.Symbol.Ask
bool Boolean values True, False
str Strings "EURUSD", api.Label
list Collection of items [1, 2, 3], [p for p in api.Positions]

In cTrader algorithms, variables store important data related to market information, trading decisions and cBot configuration. These variables often represent calculated trading volumes, indicator values, Boolean flags, symbol names, labels and more.

Here is an example:

1
2
3
4
class GridcBot():
    def on_start(self):
        self.volumeInUnits = api.Symbol.QuantityToVolumeInUnits(api.VolumeInLots)
        self.enoughMoney = True

In this case,self.volumeInUnits is a float used to place orders, while self.enoughMoney is a bool controlling whether to continue opening positions in a grid strategy.

Classes and objects

Python is an object-oriented programming (OOP) language, providing tools to organise code through classes and objects that model real-world entities and behaviours. A class defines a blueprint for creating objects, which are instances of that class with their own unique data and behaviours. The key concepts are outlined below:

  • A class is defined using the class keyword.
  • The __init__ method (constructor) is commonly used to initialise object state.
  • Instance methods define behaviour.
  • Attributes hold data specific to each object.

For example, you can create a Car class:

1
2
3
4
5
6
7
class Car:
    def __init__(self, color, speed):
        self.color = color
        self.speed = speed

    def accelerate(self):
        self.speed += 10

Then you can create objects of the Car class and interact with them:

1
2
3
my_car = Car("red", 50)
my_car.accelerate()
print(my_car.speed)  # This returns 60

In cTrader context

In cTrader, cBots, indicators and plugins are implemented as classes that encapsulate their entire logic. Algos interact with the cTrader platform using a special api object that provides access to symbols, charts, orders, indicators and more.

You can create a SimplecBot class in cTrader this way:

1
2
3
class SimplecBot():
    def on_start(self):
        api.Print("cBot started")

The SimplecBot class includes a single method (on_start(self)) that executes once when the trading bot starts. The api.Print function writes a message to the log, demonstrating how object methods encapsulate trading behaviour.

Each instance of a class effectively represents a self-contained trading algorithm that manages its own lifecycle and trading decisions using OOP principles. In a standard cTrader cBot, a class might store internal state data, such as volume, indicator results, etc., and use several methods to manage trading logic.

Consider this MACD crossover example that initialises the MACD indicator when started and places a buy order when a MACD crossover signal occurs:

1
2
3
4
5
6
7
class MACDCrossover():
    def on_start(self):
        self.macd = api.Indicators.MacdCrossOver(api.Source, api.LongCycle, api.ShortCycle, api.SignalPeriods)

    def on_bar_closed(self):
        if self.macd.MACD.Last(0) > self.macd.Signal.Last(0):
            api.ExecuteMarketOrder(TradeType.Buy, api.SymbolName, volume)

The self keyword

Inside a class, self refers to the instance of the class and provides access to its variables and methods. self is used to:

  • Store data such as indicator results, position volumes and configuration values.
  • Track internal state across different method calls such as on_start(), on_tick() or on_bar_closed().

This example demonstrates how self is used:

1
2
3
4
class MACDCrossover():
    def on_start(self):
        self.volume = api.Symbol.QuantityToVolumeInUnits(api.VolumeInLots)
        self.macd = api.Indicators.MacdCrossOver(api.Source, api.LongCycle, api.ShortCycle, api.SignalPeriods)

In this case,self.volume stores the calculated volume to use for trades, while self.macd stores the MACD indicator object, allowing access to its values throughout the cBot's lifecycle. Without self, these variables would be local to on_start() and inaccessible in on_bar_closed() where trading decisions may be made.

Methods and functions

Functions are blocks of reusable code used to perform a specific task. When functions are defined inside a class, they are called methods, and they operate on the internal state of the class via the self parameter. In the context of algorithmic trading with cTrader, methods define how your trading algorithm behaves under different conditions, such as market ticks, bar closures or initialisation.

The platform automatically calls special methods (known as event handlers) at appropriate times, such as:

on_start(self) – called once when the bot is initialised.

1
2
def on_start(self):
    api.Print("cBot started")

on_tick(self) – called on every incoming market tick.

1
2
def on_tick(self):
    api.Print("Tick received")

on_bar_closed(self) – called when a new candle/bar closes (ideal for time-based strategies).

1
2
3
4
def on_bar_closed(self):
    if Functions.HasCrossedAbove(...):
        self.close_positions(TradeType.Sell)
        api.ExecuteMarketOrder(...)

Custom or helper methods

To avoid rewriting and duplicating code, you can define your own methods. Such custom methods should encapsulate common logic that your trading robot may reuse.

The method below returns all open positions associated with the current cBot's label, making it easy to track or close positions consistently.

1
2
def get_bot_positions(self):
    return api.Positions.FindAll(api.Label)

Consider another method that calculates the distance in pips between the current price and the position's entry, which is critical for decisions in grid-based strategies.

1
2
def get_distance_in_pips(self, position):
    return (position.EntryPrice - api.Symbol.Ask if position.TradeType == TradeType.Buy else api.Symbol.Bid - position.EntryPrice) / api.Symbol.PipSize

Once a method has been defined, you can call it from within the same class using the self keyword. This approach lets you reuse the logic anywhere in your bot without duplicating code.

For example, after defining get_bot_positions(), you might call it inside another method to close all positions:

1
2
3
4
def close_positions(self, tradeType):
    for position in self.get_bot_positions():   # Helper method call
        if position.TradeType == tradeType:
            api.ClosePosition(position)

Here, self.get_bot_positions() invokes the helper method, returning only the positions tied to the current bot label.

Similarly, the get_distance_in_pips() method can be reused to make trading decisions. For example, in a grid strategy, you may want to open a new order only if the market has moved far enough from the last position:

1
2
3
4
5
6
7
def on_tick(self):
    grid_positions = self.get_bot_positions()
    if len(grid_positions) > 0:
        position_with_highest_pips = sorted(grid_positions, key=lambda pos: pos.Pips, reverse=True)[0]
        distance = self.get_distance_in_pips(position_with_highest_pips)  # Helper method call
        if distance >= api.StepPips:
            self.open_position()

Other concepts

Namespaces and imports

Namespaces in Python help organise code and avoid name collisions by grouping related identifiers (variables, functions, classes). Imports allow you to bring in external modules, libraries or packages to extend functionality beyond Python's standard features.

In Python, you may import standard modules or custom packages:

1
2
import math
print(math.sqrt(16))  # 4.0

You can also import specific functions or classes:

1
2
from math import sqrt
print(sqrt(25))  # 5.0

When writing algorithms for cTrader in Python, imports work a bit differently because cTrader integrates with the .NET-based cTrader Algo API. This setup involves a bridge that allows Python code to interact with .NET assemblies, and this explains why a typical cBot starts with:

1
2
3
4
5
6
7
8
9
import clr

clr.AddReference("cAlgo.API")

# Import cAlgo API types
from cAlgo.API import *

# Import trading wrapper functions
from robot_wrapper import *

Where:

  • import clr loads the Common Language Runtime (CLR), enabling Python to interoperate with .NET libraries.
  • clr.AddReference("cAlgo.API") references the cAlgo API assembly, which contains all trading-related classes, methods and objects.
  • from cAlgo.API import * brings in all types from the API (e.g., TradeType, Color, Position).
  • from robot_wrapper import * loads helper functions and wrappers that simplify common trading operations.

The namespace system ensures that:

  • Python's built-in names (such as print) avoid conflicts with cTrader's names (such as api.Print).
  • All trading-specific objects (api.Symbol, api.Indicators, api.ExecuteMarketOrder) come from the cTrader Algo API namespace, keeping them organised and distinct.
  • You can extend functionality by importing your own custom packages to work alongside cTrader's built-in types.

Loops

Loops allow you to repeat operations over a sequence of values or until a condition is met. In algorithmic trading, loops are often used to process positions (e.g., close, filter or update them), check conditions across multiple assets or indicators, implement grid or martingale strategies where repeated order placement is required and more.

In Python, iteration is straightforward and supports both for loops (for iterating through collections) and while loops (for repeating until a condition changes).

You can write a loop to close positions:

1
2
3
4
def close_positions(self, tradeType):
    for position in self.get_bot_positions():
        if position.TradeType == tradeType:
            api.ClosePosition(position)

In this case:

  • self.get_bot_positions() returns all positions belonging to the current cBot.
  • The for loop goes through each position.
  • Matching positions are closed with api.ClosePosition.

Consider a different loop that iterates over all open grid positions, closes them one by one and recursively checks if any positions remain (safety in case of partial closures).

1
2
3
4
5
6
def close_grid_positions(self):
    for position in self.get_grid_positions():
        position.Close()

    if len(self.get_grid_positions()) > 0:
        self.close_grid_positions()

Conditional statements

Conditional statements control the flow of execution in Python by allowing your algorithm to make decisions based on logical expressions. Conditionals are the backbone of strategy logic, determining when to enter, exit or manage positions.

Python supports three main conditional constructs:

  • if – executes code when the condition is true.
  • elif (else if) – checks another condition if the first one was false.
  • else – provides a fallback when no conditions are met.

Consider the MACD crossover conditions expressed in this code:

1
2
3
4
if self.macd.MACD.Last(0) > self.macd.Signal.Last(0):
    api.ExecuteMarketOrder(TradeType.Buy, api.SymbolName, self.volumeInUnits, api.Label, api.StopLossInPips, api.TakeProfitInPips)
elif self.macd.MACD.Last(0) < self.macd.Signal.Last(0):
    api.ExecuteMarketOrder(TradeType.Sell, api.SymbolName, self.volumeInUnits, api.Label, api.StopLossInPips, api.TakeProfitInPips)

This structure:

  • Places a buy order when the MACD line is above the signal line.
  • Places a sell order when the MACD line is below the signal line.
  • Skips trading if there is no clear signal.

Lists

Python provides powerful and concise tools for working with collections of data, such as trades, prices or indicators. Two of the most common are list comprehensions and built-in aggregation functions such as sum().

For example, you can write a code that iterates over numbers and keeps only even numbers this way:

1
2
numbers = [1, 2, 3, 4, 5]
squares = [n**2 for n in numbers if n % 2 == 0]

You can use a list comprehension to select only positions for the current symbol, producing a filtered list that is useful in multi-symbol environments.

1
positions = [p for p in api.Positions if p.SymbolName == api.SymbolName]

Aggregating profit or volume using sum() is a standard approach in grid trading strategies for determining when total profit meets the target.

1
net_profit_sum = sum([p.NetProfit for p in grid_positions])

You can combine filtering and aggregation this way:

1
2
buy_positions = [p for p in api.Positions if p.TradeType == TradeType.Buy]
avg_buy_price = sum([p.EntryPrice * p.VolumeInUnits for p in buy_positions]) / sum([p.VolumeInUnits for p in buy_positions])

Synchronous and asynchronous operations

Python follows a synchronous execution model by default. Your algorithm's lifecycle methods, such as on_start, on_tick and on_bar_closed, are triggered by the platform's event loop, one at a time.

Here is a basic representation of a synchronous trading flow:

graph TD
    A(Encounter a signal) ==> B(Execute an order)
    B ==> C(Position hits a TP/SL)
    C ==> D(Close the position)
    D ==> A

In synchronous flows, any long-running operation can block the thread, potentially delaying the next event. However, in real-world strategies, algorithms typically initiate non-blocking actions, such as submitting an order or setting stop-loss and take-profit, then they wait for future events (such as a new tick or bar) to handle results. This setup results in an asynchronous-like orchestration, even though Python's async/await constructs are not used.

Note

For most trading algorithms, explicit asynchronous programming is unnecessary.

cTrader manages concurrency by serializing your event-based callbacks. Each method is invoked independently and never in parallel, removing the need for multithreading safeguards. This design enables strategies to respond to multiple trading conditions at once, in a logical sense, without blocking.

In the example below, the strategy evaluates buy and sell signals independently and responds accordingly, without any overlap in execution context.

graph TD
    A([Encounter a signal]) ==> B([Open a buy order]) & C([Open a sell order for hedging]) ==> D([An order hits its take<br>profit or stop loss])
    D ==> E([Close the position])
    E ==> A

Even though decisions may seem to be in parallel, they are handled sequentially by cTrader's internal queueing system. The platform architecture aims to prevent concurrency issues while keeping trading robots responsive to market changes.

Image title