Documentation
How AlgoDesk works
for AlgoDesk Desktop v1.0.22

How scripts work

Strategies, indicators, commissions and optimisation fitness functions are small C# programs called scripts. They sit in a folder on your computer, where you can read, copy and change them, and AlgoDesk compiles them into the app.

The four kinds of script

Kind What it does Where you use it
Strategy Decides when to buy and sell, and places the orders. On a chart (right-click, Strategies...), in the Backtester and the Optimizer, and live.
Indicator Calculates values from the bars and draws them on a chart. On a chart (right-click, Indicators...), and inside strategies and other indicators.
Commission Works out what each trade costs. Commissions in a strategy's settings, for example in the Backtester and the Optimizer. See Commissions.
Optimisation fitness function Gives each optimisation run a score, so the Optimizer can keep the best. Optimize on in the Optimizer. See Fitness functions.

AlgoDesk comes with a set of each. Built-in scripts lists them.

Where scripts live

All scripts are in Documents\AlgoDesk\bin\Custom.

Folder or file What is in it
Strategies\Default The built-in strategies.
Indicators\Default The built-in indicators.
Commissions The built-in commission scripts.
OptimizationFitnesses The built-in fitness functions.
Strategies\Strategy.cs and Indicators\Default\Indicator.cs The base every strategy and every indicator builds on. Leave them as they are.
AlgoDesk.Custom.csproj The project file, so the folder opens in a C# editor.
lib AlgoDesk's own libraries, which the scripts are compiled against.
CLAUDE.md and .claude AlgoDesk's guide for Claude Code. Every update replaces CLAUDE.md, .claude\settings.json and .claude\skills\algodesk-*.
CLAUDE.local.md and .claude\settings.local.json Your own notes and rules for Claude. Updates never touch them.

The last two rows are for Claude Code, which can write scripts for you. See Coding scripts with Claude.

AlgoDesk compiles every .cs file in this folder and in all its subfolders, except bin and obj. So a backup copy left in the folder, such as MACrossOver - Copy.cs, is compiled too. It has the same class name as the original, and the compile fails. Keep backups outside the folder.

To use a different folder, change Path to Custom Project in Tools, Options, under General, Project.

Every update replaces the built-in scripts, even if you changed them. Put your own scripts in a folder of your own, such as Strategies\Mine. Updates never touch them.

Windows File Explorer open at Documents › AlgoDesk › bin › Custom, listing the folders bin, Commissions, Indicators, lib, OptimizationFitnesses and Strategies and the file AlgoDesk.Custom.csproj

Editing a script

A script is a plain text file, so any text editor will do. For code completion and error underlining, open AlgoDesk.Custom.csproj in Visual Studio or another C# editor that opens project files.

Save the file, then compile in AlgoDesk. AlgoDesk compiles the scripts itself, so you do not need to build them in your editor.

To make your own strategy from a built-in one:

  1. Copy the file into your own folder, for example Strategies\Mine\MyCrossover.cs.
  2. Rename the class to match the file name: MyCrossover.
  3. Change the namespace to match the folder: AlgoDesk.Custom.Strategies.Mine. The last part, Mine, is the folder the strategy is listed under in the Strategies and Select strategy windows.
  4. Give StrategyName a new name and StrategyGuid a new GUID. Any GUID generator makes one.
  5. Compile.

The new name and GUID matter. The name is what you see in every list. Presets find a strategy by its GUID, and so do the scripts that Export adds for you. An indicator works the same way, with IndicatorName and IndicatorGuid.

What a script looks like

These pieces come from the built-in MA Crossover strategy.

A parameter is a property marked [AlgoDeskProperty]. It appears in the strategy's settings under its display name and group, presets save it, and the Optimizer can vary it:

[AlgoDeskProperty]
[Category(EntrySettings)]
[Display(Name = "Period Fast", Order = 53, GroupName = EntrySettings)]
public int PeriodFast
{ get; set; }

OnStateChanged runs as the strategy is set up. SetDefaults happens once, when the strategy is created: here it sets the name and each parameter's starting value. Configured happens at the start of every run: here it clears the Debug window and creates the two moving averages.

public override void OnStateChanged()
{
    if (State == State.SetDefaults)
    {
        SetDefaults();
        SetParameters();
    }
    else if (State == State.Configured)
    {
        ClearDebug();
        AddIndicators();

    }
}

OnBarUpdate runs once for every bar. MA Crossover waits until it has three times Period Slow bars, then looks for a cross. Close[0] is the close of the current bar and [1] means one bar back; the fast average is EMA(High, PeriodFast) and the slow one EMA(Low, PeriodSlow):

_enterLong = Close[0] > _maFast[0] && _maFast[0] > _maSlow[0] &&  _maFast[1] <= _maSlow[1];
_enterShort = Close[0] < _maFast[0] && _maFast[0] < _maSlow[0] &&  _maFast[1] >= _maSlow[1];

A strategy also has OnOrderUpdate, which runs each time one of its orders changes, for example when it fills.

Reacting to each trade

OnTickUpdate runs for each trade a live chart receives for its own instrument, in a strategy running on the chart and in the indicators you put on the chart. An indicator that a strategy calls does not get it, so do per-trade work in the strategy's own OnTickUpdate. It never runs in a backtest or in the Optimizer, which work with whole bars. tick is the trade: tick.C is its price, tick.V its size and tick.t its time. candle is the bar the trade belongs to, with the trade already in it.

tick.AggressorSide says which side started the trade: AggressorSide.Buy when a buyer took the offer, AggressorSide.Sell when a seller hit the bid, and AggressorSide.Unknown when the connection does not say.

Connection tick.AggressorSide
dxFeed Usually Buy or Sell. Unknown for a trade dxFeed does not classify, and always for an index, which has no trades of its own.
Bybit Buy or Sell.
Interactive Brokers Always Unknown: its trade stream does not report the side.
Tradovate Always Unknown: AlgoDesk does not read the side from Tradovate's trades.
TopstepX Buy or Sell, as TopstepX reports the side of each trade.

After a reconnect, AlgoDesk fills the gap with the bars it downloads, and they reach OnTickUpdate one bar at a time with the side Unknown. candle.AggressorSide is always Unknown, because a bar is many trades.

These lines add up the volume bought and the volume sold, in a strategy or in an indicator you put on the chart:

private double _bought;
private double _sold;

public override void OnTickUpdate(ICandle candle, ICandle tick)
{
    if (tick.AggressorSide == AggressorSide.Buy)
        _bought += tick.V;
    else if (tick.AggressorSide == AggressorSide.Sell)
        _sold += tick.V;
}

Set totals like these back to zero in Configured, as the rules below explain.

Prices and boxes on the chart

A strategy can ask the user to click a price on its chart, and draw boxes of lines there that the user drags, as the built-in Setup Helper does. Its files SetupHelper.cs and SetupHelper.Panel.cs, in Strategies\Default, are the worked example.

Chart.PickPrice(this, hint, done) shows hint at the top of the price pane, followed by "Esc to cancel", and turns the pointer into a crosshair with a dashed line. The next click in the price pane calls done with that price, rounded to the tick. done gets null instead when the user presses Esc or picks a drawing tool, when another pick takes its place, or when the chart closes. A chart runs one pick at a time. An optional fourth argument, describe, is called with the price under the pointer and returns a note to show next to it, such as the money a stop there would risk.

Chart.AddSetupBox(this, slot, entry, stop, target) draws a box of up to three lines, entry, stop and target, at the right edge of the price pane, and returns it as a SetupBox. Slot 0 is at the edge, and each higher slot one box further left. A price of double.NaN draws no line; stop and target can be left out. On the box:

  • Entry, Stop and Target are the lines' prices; EntryText, StopText and TargetText the text after each price; Warning a text under the box.
  • IsLocked set to true stops the user dragging the lines.
  • Moved says which line moved (e.Line) and its price (e.Price), for each new price while the user drags it, and once more with e.IsFinal set to true when it is dropped.
  • Remove() takes the box off the chart.
Chart.PickPrice(this, "Click the entry price", price =>
{
    if (price == null)
        return;                          // Esc, a drawing tool, or another pick

    var box = Chart.AddSetupBox(this, 0, price.Value);
    box.EntryText = "Entry";
});

A box is not a drawing: Delete and Clear all leave it alone, and it is not saved with the chart. When the strategy stops, AlgoDesk takes its boxes away and ends its pick; Chart.ReleaseScriptObjects(this) does the same at any time. Chart is null when the strategy has no chart, so check it first.

done and Moved run when the user clicks or drags, outside OnBarUpdate and OnOrderUpdate. To place orders from them, and to move the boxes when an order changes, follow Setup Helper: Finish in SetupHelper.Panel.cs sends a plan from a click, and OnPlanChangedForPanel passes each order change on to the boxes.

What a script knows about its instrument

Besides TickSize, the price step, and Multiplier, what a move of 1.0 in price makes on one contract, a script can read these facts about its instrument:

Member What it holds
InstrumentType Its type as the connection reports it, such as "FUTURE" or "SPOT".
InstrumentBase The currency bought and sold, such as BTC on BTCUSDT.
InstrumentQuote The currency the price is quoted in, such as USDT on BTCUSDT.
InstrumentCurrency Its currency as the connection reports it.
MinLotSize The smallest order size it accepts.

A text is null, and a number 0, when the instrument does not say. These are read-only and are not parameters, so they never appear in a strategy's settings. Setup Helper uses them to offer BTC or USDT as its unit, to round sizes down to the lot and to refuse spot instruments.

Printing messages

Print writes a line to the Debug window. To open it, choose New, then Debug on the toolbar. Button Trader prints a line for each button you press and each fill, like this:

private void Say(string text)
{
    Print(System.DateTime.Now.ToString("HH:mm:ss.fff") + " " + StrategyName + ": " + text);
}
  • ClearDebug() empties the Debug window. Right-click the window for Copy and Clear.
  • Log writes to the Log tab of the Control Center instead.
  • Print does nothing while the Optimizer runs, so an optimisation does not flood the window.

AlgoDesk writes its own lines about orders and positions to the same window, such as "Submitting account order" and "ACCOUNT ORDER ADD". Lines your script prints sit among them.

The Debug window after two trades with Button Trader on Local paper: Button Trader's own lines, each starting with the time, such as "Button Trader: Buy: open long 1", "Enter Long filled 1 @ 85914.4 -> Long 1", the stop and target it placed and "Stop Long filled", with AlgoDesk's order and position lines such as "ACCOUNT ORDER ADD" between them

Indicator files

When you compile, AlgoDesk adds a block to the end of each indicator file, marked "AlgoDesk generated code. Neither change nor remove." It lets strategies and other indicators call the indicator by name with its parameters, such as SMA(14) or EMA(High, PeriodFast). Leave the block alone, and keep your own code inside the file's namespace: each compile rewrites everything after it.

Rules for a strategy that gives the same result twice

AlgoDesk reuses the same strategy from one run to the next instead of building a new one, because that is faster. Re-running a backtest, compiling, changing a chart's data and restarting a strategy all reuse it. Whatever your code left behind at the end of one run is still there when the next one starts, and the second run can trade differently from the first.

An optimisation runs many tests at the same time, each on a copy of the strategy. The copies start from the strategy's values as they are, and a list the strategy created once is the same list in every copy.

Follow these four rules:

  1. Set everything your strategy changes while it runs back to its starting value in Configured, which runs at the start of every run. That means counters, flags, prices you remember, the orders you keep, and lists.
  2. Make lists and other objects new there, with new, or set them to null. Do not empty them with .Clear(): an emptied list is still the one the other copies are using.
  3. Never keep anything that changes in a static field. Constants, and static readonly values that never change, are fine.
  4. Use SetDefaults only for names and the starting values of parameters. Create series, lists, indicators and helper objects in Configured.

The indicators a strategy calls are built fresh for every run. Rules 1 and 2 apply to strategies and to indicators you put on a chart; rule 3 applies to every script.

The built-in Button Trader strategy shows rules 1 and 2. In Configured it calls this:

private void ResetTradeState()
{
    _actionOrder = null;
    _stopOrder = null;
    _targetOrder = null;
    _cancelsSent = new HashSet<string>();
    _bracketHalted = false;
    _flipQuantity = 0;
    _insideOwnCall = false;
    ResetBracketPrices();
}

To check your own strategy, run the same backtest twice without changing anything. The numbers must match exactly.

Removing a script

In a chart's Indicators or Strategies window, right-click a script and choose Remove. AlgoDesk asks, for example "Delete the script file of "MA Crossover"? This cannot be undone." Choose Yes to go on.

Removing a script compiles your scripts, so if a running strategy has a position or working orders, AlgoDesk then asks the compile button's question; see Compiling. Don't stop removes nothing, and AlgoDesk says "Remove failed!".

AlgoDesk then deletes the script's file and compiles. If the scripts no longer compile without it, for example because a strategy uses that indicator, AlgoDesk puts the file back and says "Unable to delete file, see log for details."