Topics
Replies
ctid4633759
12 Apr 2022, 20:01
( Updated at: 21 Dec 2023, 09:22 )
RE:
amusleh said:
Hi,
This might solve your issue:
using System; using System.Linq; using cAlgo.API; using cAlgo.API.Indicators; using cAlgo.API.Internals; namespace cAlgo.Robots { [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] public class IcebergDemo : Robot { [Parameter("Label", DefaultValue = "_")] public string _name { get; set; } [Parameter("Maximum Visible Lots", Group = "Iceberg Settings", DefaultValue = 1, MaxValue = 100, MinValue = 1, Step = 1)] public double _maxvisiblelots { get; set; } [Parameter("Total Lots Needed", Group = "Risk", DefaultValue = 1.0, MaxValue = 100.0, MinValue = 0.01, Step = 0.01)] public double _total_lots_needed { get; set; } [Parameter("Protection", Group = "Risk", DefaultValue = 7.5, MaxValue = 10000, MinValue = 0.1, Step = 0.1)] public double _protection { get; set; } [Parameter("Stop Loss", Group = "Risk", DefaultValue = 10, MaxValue = 10000, MinValue = 0.1, Step = 0.1)] public double _stoploss { get; set; } [Parameter("Take Profit", Group = "Risk", DefaultValue = 15, MaxValue = 10000, MinValue = 0.1, Step = 0.1)] public double _takeprofit { get; set; } private bool _buy, _sell; private MovingAverage _fast; private MovingAverage _slow; protected override void OnStart() { _fast = Indicators.MovingAverage(Bars.ClosePrices, 12, MovingAverageType.Exponential); _slow = Indicators.MovingAverage(Bars.ClosePrices, 26, MovingAverageType.Exponential); } protected override void OnBar() { var ActiveBuys = Positions.FindAll(_name, Symbol.Name, TradeType.Buy); var ActiveSells = Positions.FindAll(_name, Symbol.Name, TradeType.Sell); _buy = _fast.Result.HasCrossedAbove(_slow.Result.Last(1), 1); _sell = _fast.Result.HasCrossedBelow(_slow.Result.Last(1), 1); if (ActiveBuys.Length > 0) { for (var i = 0; i < ActiveBuys.Length; i++) { var buydistance = Math.Round((Symbol.Bid - ActiveBuys[0].EntryPrice) / Symbol.PipSize, 1); if (buydistance >= _protection) { Print("Buy #{0} To Breakeven", History.Count.ToString()); //modify ActiveBuys[0].ModifyStopLossPrice(ActiveBuys[0].EntryPrice); } } } if (ActiveSells.Length > 0) { for (var i = 0; i < ActiveBuys.Length; i++) { var selldistance = Math.Round((ActiveSells[0].EntryPrice - Symbol.Ask) / Symbol.PipSize, 1); if (selldistance >= _protection) { Print("Sell #{0} To Breakeven", History.Count.ToString()); //modify ActiveSells[0].ModifyStopLossPrice(ActiveSells[0].EntryPrice); } } } var volumes = GetVolumeSplits(_total_lots_needed, _maxvisiblelots); if (volumes.Sum() != Symbol.QuantityToVolumeInUnits(_total_lots_needed)) { throw new InvalidOperationException(string.Format("Volumes doesn't match, lots sum: {0} | lots to Split: {1} | max split size: {2} | # of splits: {3}", Symbol.VolumeInUnitsToQuantity(volumes.Sum()), _total_lots_needed, _maxvisiblelots, volumes.Length)); } if (_buy && ActiveBuys.Length == 0) { for (var i = 0; i < volumes.Length; i++) { ExecuteMarketOrder(TradeType.Buy, SymbolName, volumes[i], _name, _stoploss, _takeprofit, History.Count.ToString()); Print("History Count: {0}", History.Count.ToString()); } } if (_sell && ActiveSells.Length == 0) { for (var i = 0; i < volumes.Length; i++) { ExecuteMarketOrder(TradeType.Sell, SymbolName, volumes[i], _name, _stoploss, _takeprofit, History.Count.ToString()); Print("History Count: {0}", History.Count.ToString()); } } } private double[] GetVolumeSplits(double lots, double maxLotsPerSplit) { if (maxLotsPerSplit > lots) throw new InvalidOperationException("maxLotsPerSplit can't be greater than lots"); var modulus = lots % maxLotsPerSplit; var numberOfFragments = Convert.ToInt32((lots - modulus) / maxLotsPerSplit); if (modulus > 0) numberOfFragments++; var lotsPerFragement = lots / numberOfFragments; var unitsPerFragment = Symbol.QuantityToVolumeInUnits(lotsPerFragement); var unitsPerFragementNormalized = Symbol.NormalizeVolumeInUnits(unitsPerFragment, RoundingMode.Up); var volumes = new double[numberOfFragments]; for (var i = 0; i < numberOfFragments; i++) { volumes[i] = i == volumes.Length - 1 && modulus > 0 ? unitsPerFragment - ((unitsPerFragementNormalized - unitsPerFragment) * (volumes.Length - 1)) : unitsPerFragementNormalized; } return volumes; } } }
But it doesn't group the open positions based on their label/comment, I just followed your cBot design and added the loops for modification.
If you want to group open positions based on their label or comment you can use Linq group by method:
var positionsGroupedByLabel = Positions.GroupBy(position => position.Label); foreach (var positionsGroup in positionsGroupedByLabel) { // Iterate over a group of positions with the same label foreach (var position in positionsGroup) { // Do anything you want with the position here } } // you can group the positions based on comment like this/ // You can use any other property of the position instead of comment/label // for grouping them var positionsGroupedByComment = Positions.GroupBy(position => position.Comment);
Hi,
I've been using your code and it works well. I adjusted it to work with pending orders and I am getting an interesting error.
Wondering if you could shed some light on why this is happening:
The volumes are a match? I did not change much from the previous sample provided:
_total_lots_needed = Symbol.VolumeInUnitsToQuantity(_total_volume_needed);
double[] modvolumes = GetVolumeSplits(_total_lots_needed, _maxvisiblelots);
if (modvolumes.Sum() != Symbol.QuantityToVolumeInUnits(_total_lots_needed))
{
throw new InvalidOperationException(string.Format("Volumes Match Error, *Modify Volume Sum (in Lots): {0} | *Total Lots Needed: {1} | Max Visible Lots: {2} | # of Fragments: {3}", modvolumes.Sum(), Symbol.QuantityToVolumeInUnits(_total_lots_needed), _maxvisiblelots, modvolumes.Length));
}
var OrdersGroupedByComment = MyPendingOrders.GroupBy(Order => Order.Comment);
foreach (var OrderGroup in OrdersGroupedByComment)
{
foreach (var Order in OrderGroup)
{
if (OrderGroup.Count() == modvolumes.Length)
{
for (var i = 0; i < modvolumes.Length; i++)
{
Order.ModifyVolume(modvolumes[i]);
}
}
if (OrderGroup.Count() != modvolumes.Length)
{
Print("Order Group Count Not Equal To Modify Volume Length");
}
}
}
@ctid4633759
ctid4633759
11 Apr 2022, 19:55
RE:
amusleh said:
Hi,
You can use unique labels or comments for each group of split positions, like I used History.Count, then when you want to modify those positions just group them by label/comment and modify each one via a loop.
Hi,
I've been looking at this for a while and can't seem to get it figured out.
Would you mind just showing me a code sample to implement? I have below the bot modified to break even after halfway to TP (at the //modify area).
What would the loop look like using the comment (History.Count) as a filter?
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class IcebergDemo : Robot
{
[Parameter("Label", DefaultValue = "_")]
public string _name { get; set; }
[Parameter("Maximum Visible Lots", Group = "Iceberg Settings", DefaultValue = 1, MaxValue = 100, MinValue = 1, Step = 1)]
public double _maxvisiblelots { get; set; }
[Parameter("Total Lots Needed", Group = "Risk", DefaultValue = 1.0, MaxValue = 100.0, MinValue = 0.01, Step = 0.01)]
public double _total_lots_needed { get; set; }
[Parameter("Protection", Group = "Risk", DefaultValue = 7.5, MaxValue = 10000, MinValue = 0.1, Step = 0.1)]
public double _protection { get; set; }
[Parameter("Stop Loss", Group = "Risk", DefaultValue = 10, MaxValue = 10000, MinValue = 0.1, Step = 0.1)]
public double _stoploss { get; set; }
[Parameter("Take Profit", Group = "Risk", DefaultValue = 15, MaxValue = 10000, MinValue = 0.1, Step = 0.1)]
public double _takeprofit { get; set; }
private double _buydistance, _selldistance;
private bool _buy, _sell;
private MovingAverage _fast;
private MovingAverage _slow;
protected override void OnStart()
{
_buydistance = 0;
_selldistance = 0;
_fast = Indicators.MovingAverage(Bars.ClosePrices, 12, MovingAverageType.Exponential);
_slow = Indicators.MovingAverage(Bars.ClosePrices, 26, MovingAverageType.Exponential);
}
protected override void OnBar()
{
var ActiveBuy = Positions.Find(_name, Symbol.Name, TradeType.Buy);
var ActiveSell = Positions.Find(_name, Symbol.Name, TradeType.Sell);
_buy = _fast.Result.HasCrossedAbove(_slow.Result.Last(1), 1);
_sell = _fast.Result.HasCrossedBelow(_slow.Result.Last(1), 1);
if (ActiveBuy != null)
{
_buydistance = Math.Round((Symbol.Bid - ActiveBuy.EntryPrice) / Symbol.PipSize, 1);
if (_buydistance >= _protection)
{
Print("Buy #{0} To Breakeven", History.Count.ToString());
//modify
ActiveBuy.ModifyStopLossPrice(ActiveBuy.EntryPrice);
}
}
if (ActiveSell != null)
{
_selldistance = Math.Round((ActiveSell.EntryPrice - Symbol.Ask) / Symbol.PipSize, 1);
if (_selldistance >= _protection)
{
Print("Sell #{0} To Breakeven", History.Count.ToString());
//modify
ActiveSell.ModifyStopLossPrice(ActiveSell.EntryPrice);
}
}
double[] volumes = GetVolumeSplits(_total_lots_needed, _maxvisiblelots);
if (volumes.Sum() != Symbol.QuantityToVolumeInUnits(_total_lots_needed))
{
throw new InvalidOperationException(string.Format("Volumes doesn't match, lots sum: {0} | lots to Split: {1} | max split size: {2} | # of splits: {3}", Symbol.VolumeInUnitsToQuantity(volumes.Sum()), _total_lots_needed, _maxvisiblelots, volumes.Length));
}
if (_buy && ActiveBuy == null)
{
for (var i = 0; i < volumes.Length; i++)
{
ExecuteMarketOrder(TradeType.Buy, SymbolName, volumes[i], _name, _stoploss, _takeprofit, History.Count.ToString());
Print("History Count: {0}", History.Count.ToString());
}
_buydistance = 0;
}
if (_sell && ActiveSell == null)
{
for (var i = 0; i < volumes.Length; i++)
{
ExecuteMarketOrder(TradeType.Sell, SymbolName, volumes[i], _name, _stoploss, _takeprofit, History.Count.ToString());
Print("History Count: {0}", History.Count.ToString());
}
_selldistance = 0;
}
}
private double[] GetVolumeSplits(double lots, double maxLotsPerSplit)
{
if (maxLotsPerSplit > lots)
throw new InvalidOperationException("maxLotsPerSplit can't be greater than lots");
var modulus = lots % maxLotsPerSplit;
var numberOfFragments = Convert.ToInt32((lots - modulus) / maxLotsPerSplit);
if (modulus > 0)
numberOfFragments++;
var lotsPerFragement = lots / numberOfFragments;
var unitsPerFragment = Symbol.QuantityToVolumeInUnits(lotsPerFragement);
var unitsPerFragementNormalized = Symbol.NormalizeVolumeInUnits(unitsPerFragment, RoundingMode.Up);
var volumes = new double[numberOfFragments];
for (var i = 0; i < numberOfFragments; i++)
{
volumes[i] = i == volumes.Length - 1 && modulus > 0 ? unitsPerFragment - ((unitsPerFragementNormalized - unitsPerFragment) * (volumes.Length - 1)) : unitsPerFragementNormalized;
}
return volumes;
}
}
}
@ctid4633759
ctid4633759
11 Apr 2022, 14:30
RE:
amusleh said:
Hi,
The max lots per split can't be greater that lots? lots is the total volume you want to split in smaller parts, max lots if the maximum allowed value for a split of lots.
And during testing I found some issues on my code, please use this updated version:
using System; using System.Linq; using cAlgo.API; using cAlgo.API.Indicators; using cAlgo.API.Internals; namespace cAlgo.Robots { [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] public class IcebergDemo : Robot { [Parameter("Label", DefaultValue = "_")] public string _name { get; set; } [Parameter("Maximum Visible Lots", Group = "Iceberg Settings", DefaultValue = 1, MaxValue = 100, MinValue = 1, Step = 1)] public double _maxvisiblelots { get; set; } [Parameter("Total Lots Needed", Group = "Risk", DefaultValue = 1.0, MaxValue = 100.0, MinValue = 0.01, Step = 0.01)] public double _total_lots_needed { get; set; } [Parameter("Stop Loss", Group = "Risk", DefaultValue = 10, MaxValue = 10000, MinValue = 0.1, Step = 0.1)] public double _stoploss { get; set; } [Parameter("Take Profit", Group = "Risk", DefaultValue = 15, MaxValue = 10000, MinValue = 0.1, Step = 0.1)] public double _takeprofit { get; set; } private bool _buy, _sell; private MovingAverage _fast; private MovingAverage _slow; protected override void OnStart() { _fast = Indicators.MovingAverage(Bars.ClosePrices, 12, MovingAverageType.Exponential); _slow = Indicators.MovingAverage(Bars.ClosePrices, 26, MovingAverageType.Exponential); } protected override void OnBar() { var ActiveBuy = Positions.Find(_name, Symbol.Name, TradeType.Buy); var ActiveSell = Positions.Find(_name, Symbol.Name, TradeType.Sell); _buy = _fast.Result.HasCrossedAbove(_slow.Result.Last(1), 1); _sell = _fast.Result.HasCrossedBelow(_slow.Result.Last(1), 1); double[] volumes = GetVolumeSplits(_total_lots_needed, _maxvisiblelots); if (volumes.Sum() != Symbol.QuantityToVolumeInUnits(_total_lots_needed)) { throw new InvalidOperationException(string.Format("Volumes doesn't match, lots sum: {0} | lots to Split: {1} | max split size: {2} | # of splits: {3}", Symbol.VolumeInUnitsToQuantity(volumes.Sum()), _total_lots_needed, _maxvisiblelots, volumes.Length)); } if (_buy && ActiveBuy == null) { for (var i = 0; i < volumes.Length; i++) { ExecuteMarketOrder(TradeType.Buy, SymbolName, volumes[i], _name, _stoploss, _takeprofit, History.Count.ToString()); } } if (_sell && ActiveSell == null) { for (var i = 0; i < volumes.Length; i++) { ExecuteMarketOrder(TradeType.Sell, SymbolName, volumes[i], _name, _stoploss, _takeprofit, History.Count.ToString()); } } } private double[] GetVolumeSplits(double lots, double maxLotsPerSplit) { if (maxLotsPerSplit > lots) throw new InvalidOperationException("maxLotsPerSplit can't be greater than lots"); var modulus = lots % maxLotsPerSplit; var numberOfFragments = Convert.ToInt32((lots - modulus) / maxLotsPerSplit); if (modulus > 0) numberOfFragments++; var lotsPerFragement = lots / numberOfFragments; var unitsPerFragment = Symbol.QuantityToVolumeInUnits(lotsPerFragement); var unitsPerFragementNormalized = Symbol.NormalizeVolumeInUnits(unitsPerFragment, RoundingMode.Up); var volumes = new double[numberOfFragments]; for (var i = 0; i < numberOfFragments; i++) { volumes[i] = i == volumes.Length - 1 && modulus > 0 ? unitsPerFragment - ((unitsPerFragementNormalized - unitsPerFragment) * (volumes.Length - 1)) : unitsPerFragementNormalized; } return volumes; } } }
I tested with 3.75 and 3.5 lots and 1 for max lots per split and it worked fine.
I modified your code, the method itself does all the calculation, you don't have to repeat it on your calculate method, just use the method output for your orders.
Also I used History.Count for order comments, to link splits in history for checking.
Hi,
Your last version works perfectly!
Regarding my question of max per split being more than the total, I apologize, as I know now what it was that I wasn't understanding.
The final bit regarding this order splitting bot: how would you modify entire split groups?
For example, if we have 4 split positions of 1.5 lots each (6 lots total), and wanted to modify the SL/TP/volume of those 4 positions (basically treating those 4 positions as if they were 1 6 lot position to be modified).
Usually, ModifyPosition uses the Label and/or TradeType as the identifier, but if we have 2 larger buy positions that are each divided into smaller groups of, say 4 each, how can the bot identify that the 4 that are the ones that make up larger position X as opposed to Y?
@ctid4633759
ctid4633759
10 Apr 2022, 12:08
( Updated at: 21 Dec 2023, 09:22 )
RE:
amusleh said:
Hi,
I check my own code with Lots set to 4, and the result was matching and it printed True.
I tried so find the issue on your code but as it's very long and complicated I gave up.
You can use this method to get volume splits/fragments:
/// <summary> /// Splits the lots in volume units and returns an array of volumes /// USe the returned array to execute orders for each volume /// </summary> /// <param name="lots">Volume in lots</param> /// <param name="maxLotsPerSplit">Maximum lots per split/fragment</param> /// <returns>double[]</returns> private double[] GetVolumeSplits(double lots, double maxLotsPerSplit) { var modulus = lots % maxLotsPerSplit; var numberOfFragments = Convert.ToInt32((lots - modulus) / maxLotsPerSplit); if (modulus > 0) numberOfFragments++; var lotsPerFragement = lots / numberOfFragments; var unitsPerFragment = Symbol.QuantityToVolumeInUnits(lotsPerFragement); var unitsPerFragementNormalized = Symbol.NormalizeVolumeInUnits(unitsPerFragment, RoundingMode.Up); var volumes = new double[numberOfFragments]; for (var i = 0; i < numberOfFragments; i++) { volumes[i] = modulus > 0 && i == volumes.Length - 1 ? unitsPerFragment - (unitsPerFragment + Symbol.VolumeInUnitsMin - unitsPerFragementNormalized) : unitsPerFragementNormalized; } return volumes; }
Once you calculated the x% volume, then pass it to this method with maximum lots per split/fragment, then it will split the volume and returns an array.
Use the array with a loop for executing orders.
Hi,
I implemented the above method precisely and am getting the exact same problem I did previously.
Some images to highlight this issue:
^ Works perfectly with max visible = 1
^ Here is the big problem. Why is the order short 0.01?
As a bonus:
^ If the max visible = the lots required, the order executes as needed.
Here is the code used:
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;
using System.Collections.Generic;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class IcebergDemo : Robot
{
[Parameter("Label", DefaultValue = "_")]
public string _name { get; set; }
[Parameter("Maximum Visible Lots", Group = "Iceberg Settings", DefaultValue = 1, MaxValue = 100, MinValue = 1, Step = 1)]
public double _maxvisiblelots { get; set; }
[Parameter("Total Lots Needed", Group = "Risk", DefaultValue = 1.0, MaxValue = 100.0, MinValue = 0.01, Step = 0.01)]
public double _total_lots_needed { get; set; }
[Parameter("Stop Loss", Group = "Risk", DefaultValue = 10, MaxValue = 10000, MinValue = 0.1, Step = 0.1)]
public double _stoploss { get; set; }
[Parameter("Take Profit", Group = "Risk", DefaultValue = 15, MaxValue = 10000, MinValue = 0.1, Step = 0.1)]
public double _takeprofit { get; set; }
double _lots_per_frag, _units_per_frag, _units_per_frag_normalized, _modulus;
int _fragcount;
bool _buy, _sell;
MovingAverage _fast;
MovingAverage _slow;
protected override void OnStart()
{
_fast = Indicators.MovingAverage(Bars.ClosePrices, 12, MovingAverageType.Exponential);
_slow = Indicators.MovingAverage(Bars.ClosePrices, 26, MovingAverageType.Exponential);
}
protected override void OnBar()
{
var ActiveBuy = Positions.Find(_name, Symbol.Name, TradeType.Buy);
var ActiveSell = Positions.Find(_name, Symbol.Name, TradeType.Sell);
_buy = _fast.Result.HasCrossedAbove(_slow.Result.Last(1), 1);
_sell = _fast.Result.HasCrossedBelow(_slow.Result.Last(1), 1);
_modulus = _total_lots_needed % _maxvisiblelots;
_fragcount = Convert.ToInt32((_total_lots_needed - _modulus) / _maxvisiblelots);
if (_modulus > 0)
{
_fragcount++;
}
_lots_per_frag = _total_lots_needed / _fragcount;
_units_per_frag = Symbol.QuantityToVolumeInUnits(_lots_per_frag);
_units_per_frag_normalized = Symbol.NormalizeVolumeInUnits(_units_per_frag, RoundingMode.Up);
double[] volumes = GetVolumeSplits(_total_lots_needed, _maxvisiblelots);
if (_buy && ActiveBuy == null)
{
for (var i = 0; i < _fragcount; i++)
{
volumes[i] = _modulus > 0 && i == volumes.Length - 1 ? _units_per_frag - (_units_per_frag + Symbol.VolumeInUnitsMin - _units_per_frag_normalized) : _units_per_frag_normalized;
ExecuteMarketOrder(TradeType.Buy, SymbolName, volumes[i], _name, _stoploss, _takeprofit);
}
}
if (_sell && ActiveSell == null)
{
for (var i = 0; i < _fragcount; i++)
{
volumes[i] = _modulus > 0 && i == volumes.Length - 1 ? _units_per_frag - (_units_per_frag + Symbol.VolumeInUnitsMin - _units_per_frag_normalized) : _units_per_frag_normalized;
ExecuteMarketOrder(TradeType.Sell, SymbolName, volumes[i], _name, _stoploss, _takeprofit);
}
}
}
private double[] GetVolumeSplits(double lots, double maxLotsPerSplit)
{
var modulus = lots % maxLotsPerSplit;
var numberOfFragments = Convert.ToInt32((lots - modulus) / maxLotsPerSplit);
if (modulus > 0)
numberOfFragments++;
var lotsPerFragement = lots / numberOfFragments;
var unitsPerFragment = Symbol.QuantityToVolumeInUnits(lotsPerFragement);
var unitsPerFragementNormalized = Symbol.NormalizeVolumeInUnits(unitsPerFragment, RoundingMode.Up);
var volumes = new double[numberOfFragments];
for (var i = 0; i < numberOfFragments; i++)
{
volumes[i] = modulus > 0 && i == volumes.Length - 1 ? unitsPerFragment - (unitsPerFragment + Symbol.VolumeInUnitsMin - unitsPerFragementNormalized) : unitsPerFragementNormalized;
}
return volumes;
}
}
}
@ctid4633759
ctid4633759
09 Apr 2022, 20:54
RE:
amusleh said:
Hi,
You can use this:
using System; using System.Linq; using cAlgo.API; using cAlgo.API.Internals; namespace NewcBot { [Robot(AccessRights = AccessRights.None)] public class NewcBot : Robot { [Parameter("Max Lost Per Fragment", DefaultValue = 1)] public double MaxLotsPerFragment { get; set; } [Parameter("Lots", DefaultValue = 3.75)] public double Lots { get; set; } protected override void OnStart() { var modulus = Lots % MaxLotsPerFragment; var numberOfFragments = Convert.ToInt32((Lots - modulus) / MaxLotsPerFragment); if (modulus > 0) { numberOfFragments++; } var lotsPerFragement = Lots / numberOfFragments; var unitsPerFragment = Symbol.QuantityToVolumeInUnits(lotsPerFragement); var unitsPerFragementNormalized = Symbol.NormalizeVolumeInUnits(unitsPerFragment, RoundingMode.Up); var volumes = new double[numberOfFragments]; for (var i = 0; i < numberOfFragments; i++) { volumes[i] = modulus > 0 && i == volumes.Length - 1 ? unitsPerFragment - (unitsPerFragment + Symbol.VolumeInUnitsMin - unitsPerFragementNormalized) : unitsPerFragementNormalized; ExecuteMarketOrder(TradeType.Buy, SymbolName, volumes[i]); } // Check if the sum of fragments is equal to total volume (lots = 3.75) Print(volumes.Sum() == Symbol.QuantityToVolumeInUnits(Lots)); } } }
It can give you an idea on how to do it, but the code definitely needs some improvements and optimizations.
You can use decimal instead of double but as the cTrader automate API is built around double it will make it much complicated.
For these kind of calculations double is not a good type, as it can cause rounding errors and non deterministic results on different CPUs.
Hi,
Please disregard the previous reply I sent you as I immediately figured out what the problem was there. (no stop loss or take profit in the sample)
See below my current situation:
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;
using System.Collections.Generic;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class IcebergDemo : Robot
{
[Parameter("Label", DefaultValue = "_")]
public string _name { get; set; }
[Parameter("Maximum Visible Lots", Group = "Iceberg Settings", DefaultValue = 1, MaxValue = 100, MinValue = 1, Step = 1)]
public double _maxvisiblelots { get; set; }
[Parameter("Total Lots Needed", Group = "Risk", DefaultValue = 1.0, MaxValue = 100.0, MinValue = 0.01, Step = 0.01)]
public double _total_lots_needed { get; set; }
[Parameter("Stop Loss", Group = "Risk", DefaultValue = 10, MaxValue = 10000, MinValue = 0.1, Step = 0.1)]
public double _stoploss { get; set; }
[Parameter("Take Profit", Group = "Risk", DefaultValue = 15, MaxValue = 10000, MinValue = 0.1, Step = 0.1)]
public double _takeprofit { get; set; }
double _lots_per_frag, _units_per_frag, _units_per_frag_normalized, _modulus;
int _fragcount;
int _calctype;
bool _buy, _sell;
MovingAverage _fast;
MovingAverage _slow;
protected override void OnStart()
{
_fast = Indicators.MovingAverage(Bars.ClosePrices, 12, MovingAverageType.Exponential);
_slow = Indicators.MovingAverage(Bars.ClosePrices, 26, MovingAverageType.Exponential);
}
protected override void OnBar()
{
var ActiveBuy = Positions.Find(_name, Symbol.Name, TradeType.Buy);
var ActiveSell = Positions.Find(_name, Symbol.Name, TradeType.Sell);
_buy = _fast.Result.HasCrossedAbove(_slow.Result.Last(1), 1);
_sell = _fast.Result.HasCrossedBelow(_slow.Result.Last(1), 1);
_modulus = _total_lots_needed % _maxvisiblelots;
_fragcount = Convert.ToInt32((_total_lots_needed - _modulus) / _maxvisiblelots);
if (_modulus > 0)
{
_fragcount++;
}
_lots_per_frag = _total_lots_needed / _fragcount;
_units_per_frag = Symbol.QuantityToVolumeInUnits(_lots_per_frag);
_units_per_frag_normalized = Symbol.NormalizeVolumeInUnits(_units_per_frag, RoundingMode.Up);
var volumes = new double[_fragcount];
if (_buy && ActiveBuy == null)
{
_calctype = GetCalcType();
if (_calctype == 1)
{
Print("Type 1");
_fragcount = Convert.ToInt32(GetFragCount());
_lots_per_frag = GetLotsPerFrag();
var _volume = Symbol.QuantityToVolumeInUnits(_lots_per_frag);
for (int i = 0; i < _fragcount; i++)
{
ExecuteMarketOrder(TradeType.Buy, Symbol.Name, _volume, _name, _stoploss, _takeprofit);
}
}
if (_calctype == 0)
{
Print("Type 0");
for (var i = 0; i < _fragcount; i++)
{
volumes[i] = _modulus > 0 && i == volumes.Length - 1 ? _units_per_frag - (_units_per_frag + Symbol.VolumeInUnitsMin - _units_per_frag_normalized) : _units_per_frag_normalized;
ExecuteMarketOrder(TradeType.Buy, SymbolName, volumes[i], _name, _stoploss, _takeprofit);
}
}
if (_calctype == 2)
{
Print("Type 2");
throw new Exception("Volume Error");
}
}
if (_sell && ActiveSell == null)
{
_calctype = GetCalcType();
if (_calctype == 1)
{
Print("Type 1");
_fragcount = Convert.ToInt32(GetFragCount());
_lots_per_frag = GetLotsPerFrag();
var _volume = Symbol.QuantityToVolumeInUnits(_lots_per_frag);
for (int i = 0; i < _fragcount; i++)
{
ExecuteMarketOrder(TradeType.Sell, Symbol.Name, _volume, _name, _stoploss, _takeprofit);
}
}
if (_calctype == 0)
{
Print("Type 0");
for (var i = 0; i < _fragcount; i++)
{
volumes[i] = _modulus > 0 && i == volumes.Length - 1 ? _units_per_frag - (_units_per_frag + Symbol.VolumeInUnitsMin - _units_per_frag_normalized) : _units_per_frag_normalized;
ExecuteMarketOrder(TradeType.Sell, SymbolName, volumes[i], _name, _stoploss, _takeprofit);
}
}
if (_calctype == 2)
{
Print("Type 2");
throw new Exception("Volume Error");
}
}
}
private double GetFragCount()
{
if (_total_lots_needed <= _maxvisiblelots)
{
return 1;
}
else if (_total_lots_needed > _maxvisiblelots)
{
var result = Math.Ceiling(_total_lots_needed / _maxvisiblelots);
return result;
}
else
return 1;
}
private double GetLotsPerFrag()
{
var _frags_available = _fragcount;
var _lots_per_frag = Math.Floor((_total_lots_needed / _frags_available) * 100) / 100;
return _lots_per_frag;
}
private int GetCalcType()
{
var volumesforcalctype = new double[_fragcount];
for (var i = 0; i < _fragcount; i++)
{
volumesforcalctype[i] = _modulus > 0 && i == volumesforcalctype.Length - 1 ? _units_per_frag - (_units_per_frag + Symbol.VolumeInUnitsMin - _units_per_frag_normalized) : _units_per_frag_normalized;
}
if (volumesforcalctype.Sum() % _fragcount == 0)
{
return 1;
}
if (volumesforcalctype.Sum() % _fragcount != 0)
{
return 0;
}
return 2;
}
}
}
Testing the sample provided leads to 3.75 being split between 4 fragments as 0.94, 0.94, 0.94, 0.93 (3.75).
However, testing the sample provided with 1 fragment (eg. max lots per frag = 4) leads to the total volume being 3.74 (0.01 too little)
The code I provided is built on the following principles:
If we CAN equally divide the total volume among fragments (Type 1), use the old method I originally provided.
If we CANNOT equally divide total volume among fragments (Type 0), use the new method provided by you.
If anything else happens (Type 2), throw an exception.
I tried measuring the Calculation Type by checking if the remainder of Volumes.Sum() divided by the fragment count is Zero or not Zero, but this leads to the undesired result that in Max Visible Lots = 1, the division is done the old way (0.94 x 4 = 3.76)
Would you have a look at the code and check what I'm doing wrong?
Thanks.
@ctid4633759
ctid4633759
09 Apr 2022, 14:35
RE:
amusleh said:
Hi,
You can use this:
using System; using System.Linq; using cAlgo.API; using cAlgo.API.Internals; namespace NewcBot { [Robot(AccessRights = AccessRights.None)] public class NewcBot : Robot { [Parameter("Max Lost Per Fragment", DefaultValue = 1)] public double MaxLotsPerFragment { get; set; } [Parameter("Lots", DefaultValue = 3.75)] public double Lots { get; set; } protected override void OnStart() { var modulus = Lots % MaxLotsPerFragment; var numberOfFragments = Convert.ToInt32((Lots - modulus) / MaxLotsPerFragment); if (modulus > 0) { numberOfFragments++; } var lotsPerFragement = Lots / numberOfFragments; var unitsPerFragment = Symbol.QuantityToVolumeInUnits(lotsPerFragement); var unitsPerFragementNormalized = Symbol.NormalizeVolumeInUnits(unitsPerFragment, RoundingMode.Up); var volumes = new double[numberOfFragments]; for (var i = 0; i < numberOfFragments; i++) { volumes[i] = modulus > 0 && i == volumes.Length - 1 ? unitsPerFragment - (unitsPerFragment + Symbol.VolumeInUnitsMin - unitsPerFragementNormalized) : unitsPerFragementNormalized; ExecuteMarketOrder(TradeType.Buy, SymbolName, volumes[i]); } // Check if the sum of fragments is equal to total volume (lots = 3.75) Print(volumes.Sum() == Symbol.QuantityToVolumeInUnits(Lots)); } } }
It can give you an idea on how to do it, but the code definitely needs some improvements and optimizations.
You can use decimal instead of double but as the cTrader automate API is built around double it will make it much complicated.
For these kind of calculations double is not a good type, as it can cause rounding errors and non deterministic results on different CPUs.
Hi,
Thanks for the code provided. I implemented it into a very basic MA crossover, but in backtesting, the log prints false.
Is my implementation incorrect?
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;
using System.Collections.Generic;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class IcebergDemo : Robot
{
[Parameter("Label", DefaultValue = "_")]
public string _name { get; set; }
[Parameter("Total Volume Needed (Units)", Group = "Iceberg Settings", DefaultValue = 100000, MaxValue = 10000000, MinValue = 1000, Step = 1000)]
public double _total_volume_needed { get; set; }
[Parameter("Maximum Visible Lots", Group = "Iceberg Settings", DefaultValue = 1, MaxValue = 100, MinValue = 1, Step = 1)]
public double _maxvisiblelots { get; set; }
[Parameter("Risk Percent", Group = "Risk", DefaultValue = 1.0, MaxValue = 100.0, MinValue = 0.01, Step = 0.01)]
public double _riskpercent { get; set; }
[Parameter("Stop Loss", Group = "Risk", DefaultValue = 10, MaxValue = 10000, MinValue = 0.1, Step = 0.1)]
public double _stoploss { get; set; }
[Parameter("Take Profit", Group = "Risk", DefaultValue = 15, MaxValue = 10000, MinValue = 0.1, Step = 0.1)]
public double _takeprofit { get; set; }
double _lots_per_frag, _units_per_frag, _units_per_frag_normalized, _total_lots_needed;
int _fragcount;
bool _buy, _sell;
MovingAverage _fast;
MovingAverage _slow;
protected override void OnStart()
{
_fast = Indicators.MovingAverage(Bars.ClosePrices, 12, MovingAverageType.Exponential);
_slow = Indicators.MovingAverage(Bars.ClosePrices, 26, MovingAverageType.Exponential);
}
protected override void OnBar()
{
var ActiveBuy = Positions.Find(_name, Symbol.Name, TradeType.Buy);
var ActiveSell = Positions.Find(_name, Symbol.Name, TradeType.Sell);
_buy = _fast.Result.HasCrossedAbove(_slow.Result.Last(1), 1);
_sell = _fast.Result.HasCrossedBelow(_slow.Result.Last(1), 1);
_total_lots_needed = Symbol.VolumeInUnitsToQuantity(_total_volume_needed);
var modulus = _total_lots_needed % _maxvisiblelots;
_fragcount = Convert.ToInt32((_total_lots_needed - modulus) / _maxvisiblelots);
if (modulus > 0)
{
_fragcount++;
}
_lots_per_frag = _total_lots_needed / _fragcount;
_units_per_frag = Symbol.QuantityToVolumeInUnits(_lots_per_frag);
_units_per_frag_normalized = Symbol.NormalizeVolumeInUnits(_units_per_frag, RoundingMode.Up);
if (_buy && ActiveBuy == null)
{
var volumes = new double[_fragcount];
for (var i = 0; i < _fragcount; i++)
{
volumes[i] = modulus > 0 && i == volumes.Length - 1 ? _units_per_frag - (_units_per_frag + Symbol.VolumeInUnitsMin - _units_per_frag_normalized) : _units_per_frag_normalized;
ExecuteMarketOrder(TradeType.Buy, SymbolName, volumes[i]);
}
// Check if the sum of fragments is equal to total volume (lots = 3.75)
Print(volumes.Sum() == Symbol.QuantityToVolumeInUnits(_total_lots_needed));
}
if (_sell && ActiveSell == null)
{
var volumes = new double[_fragcount];
for (var i = 0; i < _fragcount; i++)
{
volumes[i] = modulus > 0 && i == volumes.Length - 1 ? _units_per_frag - (_units_per_frag + Symbol.VolumeInUnitsMin - _units_per_frag_normalized) : _units_per_frag_normalized;
ExecuteMarketOrder(TradeType.Sell, SymbolName, volumes[i]);
}
// Check if the sum of fragments is equal to total volume (lots = 3.75)
Print(volumes.Sum() == Symbol.QuantityToVolumeInUnits(_total_lots_needed));
}
}
}
}
@ctid4633759
ctid4633759
09 Sep 2021, 13:22
RE:
amusleh said:
Hi,
Not sure what exactly you want to do with all those extra code, but if you want to show a text based on type of last three bar you can do it like this:
using cAlgo.API; using System.Linq; namespace cAlgo { [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] public class ThreeStrike : Indicator { [Parameter("Vertical Alignment", Group = "Position", DefaultValue = VerticalAlignment.Top)] public VerticalAlignment vAlignment { get; set; } [Parameter("Horizontal Alignment", Group = "Position", DefaultValue = HorizontalAlignment.Right)] public HorizontalAlignment hAlignment { get; set; } private ChartStaticText _text; protected override void Initialize() { _text = Chart.DrawStaticText("idtext_red", string.Empty, this.vAlignment, this.hAlignment, Color.Chocolate); } public override void Calculate(int index) { if (index < 2) return; var lastThreeBars = new Bar[] { Bars.Last(0), Bars.Last(1), Bars.Last(2) }; if (lastThreeBars.All(bar => bar.Close > bar.Open)) { _text.Text = "Three Bull"; _text.Color = Color.Green; } else if (lastThreeBars.All(bar => bar.Close < bar.Open)) { _text.Text = "Three Bear"; _text.Color = Color.Red; } else { _text.Text = "Neutral"; _text.Color = Color.Gray; } } } }
Hi amusleh, thanks for the help.
I have added another step where the next bar should be an engulfing bar, which works, but I'm having a bit of trouble creating a vertical line as an entry signal where the conditions are met.
using cAlgo.API;
using System.Linq;
namespace cAlgo
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class ThreeStrike : Indicator
{
[Parameter("Vertical Alignment", Group = "Position", DefaultValue = VerticalAlignment.Top)]
public VerticalAlignment vAlignment { get; set; }
[Parameter("Horizontal Alignment", Group = "Position", DefaultValue = HorizontalAlignment.Right)]
public HorizontalAlignment hAlignment { get; set; }
private ChartStaticText _text;
protected override void Initialize()
{
_text = Chart.DrawStaticText("idtext_red", string.Empty, this.vAlignment, this.hAlignment, Color.Chocolate);
}
public override void Calculate(int index)
{
if (index < 2)
return;
var lastThreeBars = new Bar[]
{
Bars.Last(1),
Bars.Last(2),
Bars.Last(3)
};
if (lastThreeBars.All(bar => bar.Close > bar.Open))
{
_text.Text = "Wait For Red Engulfing";
_text.Color = Color.Red;
if (Bars.ClosePrices.Last(0) < Bars.OpenPrices.Last(1))
{
_text.Text = "Sell";
_text.Color = Color.Red;
Chart.DrawVerticalLine("Sell", System.DateTime.Now, Color.OrangeRed);
}
}
else if (lastThreeBars.All(bar => bar.Close < bar.Open))
{
_text.Text = "Wait For Green Engulfing";
_text.Color = Color.Green;
if (Bars.ClosePrices.Last(0) > Bars.OpenPrices.Last(1))
{
_text.Text = "Buy";
_text.Color = Color.Green;
Chart.DrawVerticalLine("Buy", System.DateTime.Now, Color.LimeGreen);
}
}
else
{
_text.Text = "Do Not Trade";
_text.Color = Color.Gray;
}
}
}
}
@ctid4633759
ctid4633759
13 Apr 2022, 19:00
RE:
amusleh said:
Please excuse the long code, I don't want to omit something and make it harder for you to help me:
Parameters are as is and the pair is AUDUSD
@ctid4633759