Can't get Margin per position
Can't get Margin per position
14 Jan 2021, 16:20
I can see the Margin per position in the Positions Tab but I can't find a definition for it in Code.
Is it not part of the API? It would a very useful workaround to Margin not being available from PendingOrders.
My idea to avoid the long drawn out process of calculating margin before placing an order, is to run in demo and write the margin used per lot to a file after the first position is made and use that in the future to roughly calculate the Margin used to ensure I don't have too many orders that when filled could use too much Margin and trigger the smart stopout.
I think there should be a Symbol.MarginPerLot to aide in this.
Any other solutions would be appreciated.
While definitions were added a few years ago to calculate risk easily (Symbol.PipSize, Symbol.PipValue, Symbol.VolumeInUnitsMin & Symbol.NormalizeVolumeInUnits), I can't find any that were added for Margin.
Example code:
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class NewcBot : Robot
{
[Parameter(DefaultValue = 0.0)]
public double Parameter { get; set; }
protected override void OnStart()
{
// Put your initialization logic here
}
protected override void OnTick()
{
Position[] positions = Positions.FindAll("Long");
var longpos = positions;
foreach (var pos in longpos)
{
Print(pos.SymbolName);
// Margin does not exist
Print(pos.Margin);
}
}
protected override void OnStop()
{
// Put your deinitialization logic here
}
}
}
Replies
prosteel1
14 Jan 2021, 18:07
( Updated at: 14 Jan 2021, 18:11 )
RE:
PanagiotisCharalampous said:
Hi prosteel1,
This information is not available in the API, you will need to calculate it yourself. You can find a useful discussion here.
Best Regards,
Panagiotis
Thanks for the reply. I saw that but I think it would take 1000 lines of code and still not cover all available pairs of forex, indicies, metals etc.
I'll try my workaround idea as it's probably 50 lines and 1% of the time to code.
It is very strange that it is not available in the API as it is in the positions tab, and in the positions info window and the orders info window and even on the mobile app too. The code is there in ctrader but just no entry in the API.
I think this in an important metric to have as running a cbot with multiple instances and not knowing this could result in an account being wiped out and so it is a major consideration when calculating risk.
@prosteel1
prosteel1
18 Jan 2021, 13:12
( Updated at: 18 Jan 2021, 14:06 )
RE: RE:
This workaround seems to be working well. I've added my code to the sample trend cbot.
Instructions
The name of the cBot must not contain spaces. This is so the ToString() works.
The cBot needs to be run on each pair until a position is made to determine the Margin per Unit requirement. 1 minute timeframe is quick to make a trade.
When the cBot starts it creates a subdirectory called Margin.
When the cBot is stopped it writes a file specific to the broker, pair and base currency that contains the Margin per Unit.
The next time the cBot starts it reads from this file.
The Margin used is stored in the comment of orders to aid in summing the total margin of orders.
This example is intended to be run on multiple pairs at once and not use more Margin than MarginUsedMax
// -------------------------------------------------------------------------------------------------
//
// This code is a modified cTrader Automate API example.
//
// This cBot is intended to be used as a sample and does not guarantee any particular outcome or
// profit of any kind. Use it at your own risk.
//
// All changes to this file might be lost on the next application update.
// If you are going to modify this file please make a copy using the "Duplicate" command.
//
// The "MargincBot" will buy when fast period moving average crosses the slow period moving average and sell when
// the fast period moving average crosses the slow period moving average. The orders are closed when an opposite signal
// is generated. There can only by one Buy or Sell order at any time.
//
// ---- Instructions -----
// The name of the cBot must not contain spaces. This is so the ToString() works.
// The cBot needs to be run on each pair until a position is made to determine the Margin per Unit requirement.
// When the cBot starts it creates a subdirectory called Margin
// When the cBot is stopped it writes a file specific to the broker, pair and base currency
// that contains the Margin per Unit.
// The next time the cBot starts it reads from this file.
// The Margin used is stored in the comment of orders to aid in summing the total margin of orders.
//
// This example is intended to be run on multiple pairs at once and not use more Margin than MarginUsedMax
//
//
// -------------------------------------------------------------------------------------------------
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;
using System.IO;
namespace cAlgo
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)]
public class MargincBot : Robot
{
[Parameter("Quantity (Lots)", Group = "Volume", DefaultValue = 0.4, MinValue = 0.01, Step = 0.01)]
public double Quantity { get; set; }
[Parameter("MA Type", Group = "Moving Average")]
public MovingAverageType MAType { get; set; }
[Parameter("Source", Group = "Moving Average")]
public DataSeries SourceSeries { get; set; }
[Parameter("Slow Periods", Group = "Moving Average", DefaultValue = 10)]
public int SlowPeriods { get; set; }
[Parameter("Fast Periods", Group = "Moving Average", DefaultValue = 5)]
public int FastPeriods { get; set; }
private MovingAverage slowMa;
private MovingAverage fastMa;
private const string label = "Sample Trend cBot";
[Parameter("MarginUsedMax", DefaultValue = 0.1, MinValue = 0.1, Step = 0.1)]
public double MarginUsedMax { get; set; }
string DataDir;
string MarginDir;
string MarginFile;
double MarginUsed = 0;
double MarginUsedLast = 0;
double MarginPerUnit = 0;
double _margin;
protected override void OnStart()
{
fastMa = Indicators.MovingAverage(SourceSeries, FastPeriods, MAType);
slowMa = Indicators.MovingAverage(SourceSeries, SlowPeriods, MAType);
DataDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\Documents\\cAlgo\\Sources\\Robots\\" + ToString() + "\\" + ToString();
MarginUsedLast = Account.Margin;
MarginDir = DataDir + "\\Margin\\";
MarginFile = MarginDir + Account.BrokerName + "-" + Symbol.Name + "-" + Account.Currency + ".csv";
if (!Directory.Exists(MarginDir))
{
Directory.CreateDirectory(MarginDir);
}
if (File.Exists(MarginFile) == true)
{
Print("MarginFile exists");
string s;
using (StreamReader sr = File.OpenText(MarginFile))
{
s = sr.ReadLine();
if (s != null)
{
Print("MarginFile contains: " + s);
MarginPerUnit = Convert.ToDouble(s);
}
}
}
Positions.Opened += PositionsOnOpened;
Positions.Closed += PositionsOnClosed;
}
protected override void OnTick()
{
var longPosition = Positions.Find(label, SymbolName, TradeType.Buy);
var shortPosition = Positions.Find(label, SymbolName, TradeType.Sell);
var currentSlowMa = slowMa.Result.Last(0);
var currentFastMa = fastMa.Result.Last(0);
var previousSlowMa = slowMa.Result.Last(1);
var previousFastMa = fastMa.Result.Last(1);
if (previousSlowMa > previousFastMa && currentSlowMa <= currentFastMa && longPosition == null)
{
if (shortPosition != null)
ClosePosition(shortPosition);
_margin = CalculateMargin(VolumeInUnits);
if (_margin != -1 && _margin + OrdersMargin() < (Math.Min(Account.Balance, Account.Equity) * MarginUsedMax))
{
var Comment = string.Format("{0}", _margin);
ExecuteMarketOrder(TradeType.Buy, SymbolName, VolumeInUnits, label, null, null, Comment);
}
}
else if (previousSlowMa < previousFastMa && currentSlowMa >= currentFastMa && shortPosition == null)
{
if (longPosition != null)
ClosePosition(longPosition);
_margin = CalculateMargin(VolumeInUnits);
if (_margin != -1 && _margin + OrdersMargin() < (Math.Min(Account.Balance, Account.Equity) * MarginUsedMax))
{
var Comment = string.Format("{0}", _margin);
ExecuteMarketOrder(TradeType.Sell, SymbolName, VolumeInUnits, label, null, null, Comment);
}
}
}
protected override void OnStop()
{
if (RunningMode == RunningMode.RealTime && MarginPerUnit > 0)
{
if (File.Exists(MarginFile) == true)
{
// Read file and check if different to MarginFile
Print("MarginFile exists");
string s;
using (StreamReader sr = File.OpenText(MarginFile))
{
s = sr.ReadLine();
}
if (s != null && MarginPerUnit > 0 && MarginPerUnit != Convert.ToDouble(s))
{
Print("MarginFile contains: " + Convert.ToDouble(s) + " which is different to MarginPerUnit = " + MarginPerUnit + " so updating MarginFile");
using (StreamWriter sw = File.CreateText(MarginFile))
{
sw.Write(Math.Round(MarginPerUnit, 5));
}
}
}
else
{
Print("MarginFile does not exist so creating it");
using (StreamWriter sw = File.CreateText(MarginFile))
{
sw.Write(Math.Round(MarginPerUnit, 5));
}
}
}
}
private void PositionsOnOpened(PositionOpenedEventArgs args)
{
var pos = args.Position;
MarginUsed = Account.Margin - MarginUsedLast;
if (pos.SymbolName == SymbolName)
{
if (Account.Margin > MarginUsedLast)
{
MarginPerUnit = Math.Round((MarginUsed / pos.VolumeInUnits), 5);
Print("MarginPerUnit = " + MarginPerUnit + ", MarginUsed = " + MarginUsed + ", MarginUsedLast = " + MarginUsedLast + ", pos.VolumeInUnits = " + pos.VolumeInUnits);
}
}
else
{
MarginUsedLast = Account.Margin;
}
}
private void PositionsOnClosed(PositionClosedEventArgs args)
{
MarginUsedLast = Account.Margin;
}
private double VolumeInUnits
{
get { return Symbol.QuantityToVolumeInUnits(Quantity); }
}
private double OrdersMargin()
{
double ordermargin = 0;
if (PendingOrders.Count > 0)
{
foreach (var order in PendingOrders)
{
if (order.Comment != "")
{
var splitted = order.Comment.Split(' ');
double margin = Convert.ToDouble(splitted[0]);
if (margin > 0)
{
ordermargin += margin;
}
}
}
}
return ordermargin;
}
private double CalculateMargin(double Vol)
{
if (MarginPerUnit > 0 && Account.Margin + (MarginPerUnit * Vol) <= (Math.Min(Account.Balance, Account.Equity) * MarginUsedMax))
{
Print("MarginPerUnit is known = " + MarginPerUnit + ", and the MarginUsed after this trade is = " + (Account.Margin + (MarginPerUnit * Vol)) + ", Which is below " + (Math.Min(Account.Balance, Account.Equity) * MarginUsedMax));
return Math.Round((MarginPerUnit * Vol), 5);
}
else if (MarginPerUnit == 0 && Account.Margin <= (Math.Min(Account.Balance, Account.Equity) * MarginUsedMax))
{
Print("MarginPerUnit is not known = " + MarginPerUnit + ", but the MarginUsed before this trade is = " + Account.Margin + ", Which is below " + (Math.Min(Account.Balance, Account.Equity) * MarginUsedMax));
return 0;
}
else
{
Print("Trade not made because it would use more than allowed Margin");
return -1;
}
}
}
}
@prosteel1
PanagiotisCharalampous
14 Jan 2021, 16:35
Hi prosteel1,
This information is not available in the API, you will need to calculate it yourself. You can find a useful discussion here.
Best Regards,
Panagiotis
Join us on Telegram
@PanagiotisCharalampous