AlgoMate Creator

AlgoMates are currently supported in C#. As of 3/1/2019, we plan on working towards adding python support.

Users can create and customize AlgoMates to their desire, however the more complex the AlgoMate, the more strain it may put on your user machine (dedicated per user). In order to have smooth sailing, you may need to upgrade to a higher tier of account (entry level machine will support 3-10 AlgoMates).

New AlgoMate Window

Compiling your AlgoMate

We check to make sure there are no coding errors in your AlgoMate. If you compile and hit an error, you will receive a prompt via a window underneath that will notify you of the issue.

AlgoMate Output

Once your AlgoMate is up and running, you can see what orders were executed, which orders are still open, as well as an active/full log of everything the AlgoMate has done and what it has interacted with (data, orders, exchanges, errors, etc).

Sample AlgoMates

We've taken the liberty of providing you, the user with some sample AlgoMates. Users are unable to run these sample AlgoMates, but are able to use them as a base to modify and save.

Hello World Example

using System;
using Algomate.Scripting;
using Algomate.Scripting.Reports;
namespace Algomate.Scripting.SampleScripts
{
// This is the Algomate basic template script.
// This script sets a target price for an exchange (spot) buy of Bitcoin using
// USD as the fiat for a volume of .1 BTC.
// This is the framework to be used to create an algorithm in C#
public class HelloWorld : Trader
{
public ExchangeName Exchange { get; private set; } = ExchangeName.Bitfinex;
public Currency Currency { get; private set; } = Currency.Bitcoin;
private Instrument Instrument { get; set; }
public override void LoginCallback()
{
Instrument = new Instrument(new CurrencyPair(Currency, Currency.Usd), Exchange);
if (!Subscribe(Instrument, MarketDepthType.Ask)) {
throw new Exception("Could not subscribe to market data");
}
}
public override void MarketDataCallback(MarketData marketData)
{
if (marketData.DepthEntries[0].Price != 7000m || marketData.DepthEntries[0].Quantity < 0.1m)
{
return;
}
var order = new Order(
Instrument,
OrderType.Market,
OrderSide.Buy,
7000m,
0.1m
);
if (Submit(order))
{
StopExecution();
}
}
}
}

Martingale Example

using System;
using Algomate.Scripting;
using Algomate.Scripting.Reports;
using System.Collections.Generic;
using System.Linq;
namespace Algomate.Scripting.SampleScripts
{
public class Martingale : Trader
{
// The instrument to trade on
public Instrument Inst { get; private set; }
// The price to start the strategy at (can also get started on a drop)
public decimal StartPrice { get; private set; }
// How much to trade on each rise / drop
public decimal Volume { get; private set; }
// Choose to buy or sell at the start
public OrderSide WinSide { get; private set ; }
// How much should it move in order to be considered a win
// This value must be positive for a 'buy' win, negative for a 'sell' win
public decimal WinInterval { get; private set; }
// How much should it move in order to be considered a loss
// This value must be the opposite sign of `WinInterval`
public decimal LoseInterval { get; private set ; }
// These following values are not inputs, and are set / modified internally
// for easier calculation.
// The opposite of the `WinSide` always
public OrderSide LoseSide { get; private set ; }
// The target price to be considered a win and to restart a new set of orders
public decimal WinPrice { get; private set; }
// The target price to be considered a loss and remake orders at the new average
public decimal LosePrice { get; private set; }
// The order that will be filled on a win, updated on a loss, redone on a win
public Order WinOrder { get; private set; }
// Number of consecutive losses, used to calculate the new average buy price
public int NumLosses { get; private set; }
public Martingale()
{
// Inputs
Inst = new Instrument(CurrencyPair.BtcUsd, ExchangeName.stubexchange);
StartPrice = 5000.0m;
Volume = .01m;
WinInterval = 10.0m;
LoseInterval = -15.0m;
WinSide = OrderSide.Buy;
// Internal calculation variables
LoseSide = WinSide == OrderSide.Buy ? OrderSide.Sell : OrderSide.Buy;
WinPrice = StartPrice;
LosePrice = StartPrice + LoseInterval;
NumLosses = 0;
}
public override void LoginCallback()
{
var mdType = WinSide == OrderSide.Buy ? MarketDepthType.Ask : MarketDepthType.Bid;
if (Subscribe(Inst, mdType) == false)
{
LogMessage("Error subscribing to instrument");
}
}
public override void MarketDataCallback(MarketData md)
{
if (md.DepthEntries.Count == 0)
{
LogMessage("No market data entries provided");
return;
}
var price = md.DepthEntries.First().Price;
if (WinHit(price))
{
WinPrice = price + WinInterval;
LosePrice = price + LoseInterval;
NumLosses = 0;
Submit(new Order(Inst, OrderType.Market, WinSide, 0.0M, Volume));
WinOrder = new Order(Inst, OrderType.Limit, LoseSide, WinPrice, Volume);
Submit(WinOrder);
}
if (LoseHit(price))
{
if (WinOrder == null) // in case we start dropping before ever starting
{
WinPrice = price + WinInterval;
}
else
{
NumLosses += 1;
var averagePricePaid = ( (WinOrder.Price - WinInterval) * NumLosses + price) / (NumLosses + 1);
WinPrice = averagePricePaid + WinInterval;
Cancel(WinOrder);
}
var totalVolume = Volume * (NumLosses + 1);
LosePrice = price + LoseInterval;
Submit(new Order(Inst, OrderType.Market, WinSide, 0.0M, Volume));
WinOrder = new Order(Inst, OrderType.Limit, LoseSide, WinPrice, totalVolume);
Submit(WinOrder);
}
}
private bool WinHit(decimal currentPrice)
{
return WinSide == OrderSide.Buy ? currentPrice >= WinPrice : currentPrice <= WinPrice;
}
private bool LoseHit(decimal currentPrice)
{
return LoseSide == OrderSide.Buy ? currentPrice >= LosePrice : currentPrice <= LosePrice;
}
}
}

One off Script

A Script that performs an action and shuts itself off, versus continually looking for a condition.

using System;
using Algomate.Scripting;
using Algomate.Scripting.Reports;
namespace Algomate.Scripting.SampleScripts
{
// Demonstrates the ability for a trader to stop execution at a given time
public class OneOffScript : Trader
{
public ExchangeName Exchange { get; private set; } = ExchangeName.Bitfinex;
public Currency Currency { get; private set; } = Currency.Bitcoin;
private Instrument Instrument { get; set; }
public override void LoginCallback()
{
Instrument = new Instrument(new CurrencyPair(Currency, Currency.Usd), Exchange);
// Place market order of .1 BTC
var order = Order.GetMarket(
Instrument,
OrderSide.Buy,
0.1m
);
// Submit order
Submit(order);
// Stop trader execution
StopExecution();
}
}
}

Take Profit / Stop Loss

using System;
using Algomate.Scripting;
using Algomate.Scripting.Reports;
using System.Collections.Generic;
namespace Algomate.Scripting.SampleScripts
{
public class TakeProfitStopLoss : Trader
{
public ExchangeName Exchange { get; private set; } = ExchangeName.Binance;
public Currency Currency { get; private set; } = Currency.Bitcoin;
public Instrument Instrument { get; private set; }
private Dictionary<Guid, ExecutionParameters> ExecutionParameters = new Dictionary<Guid, ExecutionParameters>();
public override void LoginCallback()
{
Instrument = new Instrument(new CurrencyPair(Currency, Currency.Usd), Exchange);
if (!ExecuteOrder(
new Order(
Instrument,
OrderType.Limit,
OrderSide.Buy,
6400.0m,
1.2m
),
6400.0m * 0.98m,
6400.0m * 1.02m
)) {
throw new Exception("Unable to execute order");
}
}
public override void ExecutionReportCallback(ExecutionReport executionReport)
{
try {
if (executionReport.State == OrderState.Filled && ExecutionParameters.TryGetValue(executionReport.ClientId, out ExecutionParameters executionParameters))
{
if (executionReport.ClientId.Equals(executionParameters.Order.ClientId))
{
ExecuteParametersOrder(executionParameters);
} else {
ResolveParametersOrder(executionParameters);
}
}
}
catch(Exception ex)
{
throw ex;
}
}
public bool ExecuteOrder(Order order, decimal stopLoss, decimal takeProfit)
{
var executionParameters = new ExecutionParameters(order, stopLoss, takeProfit);
if (!Submit(order)) {
return false;
}
ExecutionParameters.Add(order.ClientId, executionParameters);
ExecutionParameters.Add(executionParameters.StopLoss.ClientId, executionParameters);
ExecutionParameters.Add(executionParameters.TakeProfit.ClientId, executionParameters);
return true;
}
public void ExecuteParametersOrder(ExecutionParameters executionParameters)
{
if (!Submit(executionParameters.StopLoss))
{
throw new Exception("Could not submit stop loss");
}
if (!Submit(executionParameters.TakeProfit))
{
Cancel(executionParameters.StopLoss);
throw new Exception("Could not submit take profit");
}
ExecutionParameters.Remove(executionParameters.Order.ClientId);
}
public void ResolveParametersOrder(ExecutionParameters executionParameters)
{
if (executionParameters.StopLoss.State == OrderState.Filled)
{
Cancel(executionParameters.TakeProfit);
} else if (executionParameters.TakeProfit.State == OrderState.Filled)
{
Cancel(executionParameters.StopLoss);
}
ExecutionParameters.Remove(executionParameters.StopLoss.ClientId);
ExecutionParameters.Remove(executionParameters.TakeProfit.ClientId);
}
}
public class ExecutionParameters
{
public decimal StopLossPrice { get; private set; }
public decimal TakeProfitPrice { get; private set; }
public Order Order { get; private set; }
public Order StopLoss { get; private set; }
public Order TakeProfit { get; private set; }
public ExecutionParameters(Order order, decimal stopLoss, decimal takeProfit)
{
Order = order;
StopLossPrice = stopLoss;
TakeProfitPrice = takeProfit;
StopLoss = new Order(
order.Inst,
OrderType.Stop,
order.Side == OrderSide.Buy ? OrderSide.Sell : OrderSide.Buy,
stopLoss,
order.GetTotalQuantity()
);
TakeProfit = new Order(
order.Inst,
OrderType.Limit,
order.Side == OrderSide.Buy ? OrderSide.Sell : OrderSide.Buy,
takeProfit,
order.GetTotalQuantity()
);
}
}
}

Sell Away

Sell all coins of a specified type, this covers the use-case for miners who want to automatically sell something when it comes into their account.

using System;
using Algomate.Scripting;
using Algomate.Scripting.Reports;
namespace Algomate.Scripting.SampleScripts
{
// The SellAway script has a simple job: create a market sell order at "Exchange"
// whenever there is any balance available for the currency pair
// "SellFromCurrency"/"SellToCurrency", e.g. "BTC/USD" in the example.
// Optionally set "TargetSellPrice" to only sell when the price is above a certain
// threshold.
public class SellAway : Trader
{
// Define where you would like to sell with these fields
public ExchangeName Exchange { get; private set; } = ExchangeName.Binance;
public Currency SellFromCurrency { get; private set; } = new Currency("BTC");
public Currency SellToCurrency { get; private set; } = new Currency("USD");
// Optionally set this if you only want to sell if above a certain price, note
// that no orders will be placed until a bid price has been retrieved
public decimal? TargetSellPrice { get; set; } = null;
// The following are used internally for order creation / market data fetching
public Instrument SellInstrument { get; private set; }
private decimal? LastBidPrice { get; set; } = null; // this is only populated if TargetSellPrice is set
public override void LoginCallback()
{
SellInstrument = new Instrument(new CurrencyPair(SellFromCurrency, SellToCurrency), Exchange);
if (TargetSellPrice.HasValue)
{
this.Subscribe(SellInstrument, MarketDepthType.Bid);
}
if (!RequestPositionReport(Exchange, SellFromCurrency))
{
throw new Exception("Could not request position report");
}
}
// Optional callback for keeping track of the latest bid
public override void MarketDataCallback(MarketData md)
{
if (md.DepthEntries.Count > 0)
{
var mdEntry = md.DepthEntries[0];
if (mdEntry.DepthType == MarketDepthType.Bid)
{
LastBidPrice = mdEntry.Price;
}
}
}
public override void PositionReportCallback(PositionReport positionReport)
{
if (positionReport.Exchange != Exchange && positionReport.Currency != SellFromCurrency)
{
LogMessage("Wrong position returned, ignoring");
}
else
{
// Collecting all cash balances
decimal balance = 0;
foreach(var position in positionReport.Positions)
{
if (position.Type == PositionAmountType.Cash) {
balance += position.Amount;
}
}
bool targetHit = true;
if (TargetSellPrice.HasValue)
{
if (LastBidPrice.HasValue)
{
LogMessage(string.Format("Last bid price: {0}, target: {1}", LastBidPrice.Value, TargetSellPrice.Value));
targetHit = LastBidPrice.Value >= TargetSellPrice.Value;
}
else
{
LogMessage("No market data received yet, can't be sure if target hit");
targetHit = false;
}
}
if (targetHit && balance > 0)
{
if (!CreateSellOrder(balance))
{
throw new Exception("Could not submit sell order");
}
}
}
// Request position once more, recursing once again into this function
if (!RequestPositionReport(Exchange, SellFromCurrency))
{
throw new Exception("Could not request position report");
}
}
private bool CreateSellOrder(decimal quantity)
{
LogMessage(string.Format("Creating sell order for balance: {0}", quantity));
var order = new Order(
SellInstrument,
OrderType.Market,
OrderSide.Sell,
0,
quantity
);
return Submit(order);
}
}
}

Order Batching

An example of wanting to sell an amount and breaking it up into smaller orders.

using System;
using Algomate.Scripting;
using Algomate.Scripting.Reports;
namespace Algomate.Scripting.SampleScripts
{
public class OrderBatching : Trader
{
public Instrument Instrument { get; private set; } = new Instrument(new CurrencyPair(new Currency("IOT"), Currency.Bitcoin), ExchangeName.Bitfinex);
public decimal SellPoint;
public decimal BatchSize;
public decimal RemainingQuantity;
public override void LoginCallback()
{
SellPoint = 7000m;
BatchSize = 500m;
RemainingQuantity = 20000m;
if (!Subscribe(Instrument, MarketDepthType.Bid))
{
throw new Exception("Could not subscribe to market data");
}
}
public override void MarketDataCallback(MarketData marketData)
{
if (RemainingQuantity <= 0)
{
StopExecution();
return;
}
if (marketData.DepthEntries[0].Price >= SellPoint)
{
decimal SellQuantity = BatchSize;
if (RemainingQuantity < BatchSize) {
SellQuantity = RemainingQuantity;
}
if (Submit(new Order(
Instrument,
OrderType.Market,
OrderSide.Sell,
SellPoint,
SellQuantity
)))
{
RemainingQuantity -= SellQuantity;
}
}
}
}
}