Функции работы с сигналами
GetSignalInfo(int id) – возврат сигнала по номеру
GetLastSignalInfo() – возврат информации по последнему сигналу
GetSignalInfo(SignalType signalType, int quantityFromEnd = 0) – возврат сигналов заданного типа, суммарный объем которых не превышает заданное количество (если
quantityFromEnd отлично от нуля)
SignalType.All – любое направление сигнала
SignalType.Close – сигналы закрытия позиции
SignalType.Open – сигналы открытия позиции
Результат исполнения – объект или список объектов, содержащих следующие поля
• SignalID – номер сигнала
• OrderType – тип заявки (купить/продать). (enumOrderType.Buy, enumOrderType.Sell)
• ActionType – название действия (команды)
• ActionSuperType – название сигнала
• OperationSize - объём
• OperationExecuted – исполненный объём
• OperationRest – объём оставшийся для исполнения
• PriceOfSignal – цена сигнала
• PriceOfOrder – цена заявки
• PriceOfTrade – цена исполнения
• PriceAdditional – второй ценовой уровень (если предусмотрен. Напр. В заявке типа BRS)
• DateTime – время сигнала
• CurrentPosition – позиция по роботу после обработки сигнала
• IsStatusFilled – исполнена ли заявка по сигналу
• IsStatusTerminal – в конечном ли статусе заяка по сигналу (исполнена, отменена, отклонена)
Для тестирования нет понятия частичного исполнения, скорее всего, будет картина чуть красивее чем в жизни

Подсмотреть пример работы с сигналами можно в коде StepByStep_V2 из библиотеки стратегий.
На всякий случай, копия кода здесь:
Код: Выделить всё
function Initialize()
{
StrategyName = "StepByStep_v2";
AddInput("Input1", Inputs.Candle, 5, true, "GAZP=МБ ЦК");
AddParameter("StartQ", 100, "Стартовое кол-во", 0);
AddParameter("Q", 10, "Кол-во докупки", 0);
AddParameter("DeltaPercent", 1, "% изменения цены для докупки и продажи", 0);
AddGlobalVariable("inited", Types.Boolean, false);
AddGlobalVariable("lastSignalId", Types.Int, 0);
AddGlobalVariable("xPosition", Types.Double, 0.0);
AddGlobalVariable("LastPrice", Types.DoubleDictionary);
AddGlobalVariable("LastPriceCount", Types.Int, 0);
AddGlobalVariable("LongLimitStart", Types.Double, 0);
AddGlobalVariable("Last", Types.Double, 0);
AddGlobalVariable("MinPrice", Types.Double, 0);
AddGlobalVariable("Type", Types.Double, 1);
AddChartIndicator("MY.str_Invest_Grid", new Dictionary <string, string>{{"Price", "MinPrice"},{"PL", "DeltaPercent"},{"Delta", "DeltaPercent"}, {"Type", "Type"}});
}
function OnUpdate()
{
double pos = CurrentPosition();
var signal = GetSignalInfo(lastSignalId);
var activeOrder = GetActiveOrders().FirstOrDefault(ao => ao.SignalId == lastSignalId);
// StartQ - позиция робота при старте
// Правило 1. если первый запуск и робот не проинициализирован, покупаем StartQ
if (pos < StartQ && !inited)
{
if ( (Q%LotSize() != 0) || (StartQ%LotSize() != 0) )
{
ShowMessage("StartQ или Q не кратно лоту.Робот остановлен!");
Stop();
}
if(LongLimitStart == 0)
{
LongLimitStart = LongLimit;
SetLongLimit(StartQ);
}
// Покупка StartQ
if (StartQ > 0)
{
EnterLongLimit(Input1.Close[0]*(1.0 + 0.01 * OrderSlippage));
var lastSignal = GetLastSignalInfo();
if(lastSignal != null)
lastSignalId = lastSignal.SignalID;
return;
}
}
var lastPriceSorted = new SortedList<double, double>(LastPrice);
if(pos >= StartQ && !inited)
{
inited = true;
if(LongLimitStart != 0)
SetLongLimit(LongLimitStart);
var signals = GetSignalInfo(SignalType.Open, (int)pos);
var initSum = signals.Where(s => s.ActionSuperType == AvailableActions.Initialization).Sum(s => s.OperationExecuted * Input.Close[0]);
var boughtSum = signals.Where(s => s.ActionSuperType != AvailableActions.Initialization).Sum(s => s.OperationExecuted * s.PriceOfTrade);
var avpStart = (initSum + boughtSum) / (pos);
var avp = avpStart;
double b = xPosition;
while (pos - b > 0)
{
lastPriceSorted.Add(avp, Math.Min(Q, pos - b));
avp += avpStart * 0.01* DeltaPercent;
b = b + Q;
}
xPosition = pos;
}
if(!inited)
{
CancelActiveOrders(true);
return;
}
if(activeOrder != null && activeOrder.IsStatusFilled && pos == xPosition)
{
return;//ждём сделки
}
//корректировка уровней
if(pos > xPosition)//позиция увеличилась
{
if(!lastPriceSorted.ContainsKey(signal.PriceOfTrade))
lastPriceSorted.Add(signal.PriceOfTrade, signal.OperationExecuted);
else
lastPriceSorted[signal.PriceOfTrade] += signal.OperationExecuted;
}
else if(pos < xPosition && lastPriceSorted.Count >= 1)
{
var b = xPosition;
while(b > pos)
{
if(lastPriceSorted.Count == 0) break;
var firstKey = lastPriceSorted.First().Key;
if(b - pos<lastPriceSorted[firstKey])
{
lastPriceSorted[firstKey] -= (b - pos);//Исполнили часть уровня
b -= (b - pos);
}
else
{
b -= lastPriceSorted[firstKey];//Исполнили весь уровень
lastPriceSorted.Remove(firstKey);
}
}
}
xPosition = pos;//позиция учтена выше
var lastSignalFilled = GetLastSignalInfo();
if(lastPriceSorted.Count <= 0)
{
MinPrice = lastSignalFilled != null ? lastSignalFilled.PriceOfTrade : Input1.Close[0];
}
else
{
MinPrice = lastPriceSorted.First().Key;
}
//Правило 2. Если цена упала и кол-во меньше допустимого,
// то покупаем и добавляем цену покупки в начало списка
if (Input1.Close[0] <= MinPrice* (1.0 - 0.01 * DeltaPercent) && pos + Q <= LongLimit )
{
if(activeOrder != null && !activeOrder.IsStatusTerminal && activeOrder.OrderDirection == OrderDirection.Buy)
{
return;//ждём исполнения уже выставленной заявки
}
EnterLongLimit(MinPrice* (1.0 - 0.01 * (DeltaPercent - OrderSlippage)), Q);
var lastSignal = GetLastSignalInfo();
if(lastSignal != null)
lastSignalId = lastSignal.SignalID;
}
//Правило 3. Если цена выше цены из начала списка, то продаем и удаляем 0-й элемент списка
if (Input1.Close[0] >= MinPrice* (1.0 + 0.01 * DeltaPercent) && pos > 0 )
{
if(activeOrder != null && !activeOrder.IsStatusTerminal && activeOrder.OrderDirection == OrderDirection.Sell)
{
return;//ждём исполнения уже выставленной заявки
}
CloseLongLimit(MinPrice* (1.0 + 0.01 * (DeltaPercent - OrderSlippage)), Q);
var lastSignal = GetLastSignalInfo();
if(lastSignal != null)
lastSignalId = lastSignal.SignalID;
}
LastPrice = new Dictionary<double, double>(lastPriceSorted);
LastPriceCount = LastPrice.Count;
}