One Cancels Other (OCO) orders?

How to create strategies and indicators
Message
Author
Matt_mm
Posts: 17
Joined: Sat Jun 06, 2009 8:40 pm

One Cancels Other (OCO) orders?

#1 Postby Matt_mm » Thu Jul 09, 2009 5:59 am

Hi all,

I'm trying to open multiple positions when the first position closes then the others close also (even if they're still pending).

I've tried using MagicNumber to specify related trades to close at once but this doesn't seem to work. How would I do this?

Kind regards,

Matt

User avatar
Terranin
Site Admin
Posts: 833
Joined: Sat Oct 21, 2006 4:39 pm

Re: One Cancels Other (OCO) orders?

#2 Postby Terranin » Thu Jul 09, 2009 10:01 am

Matt_mm wrote:Hi all,

I'm trying to open multiple positions when the first position closes then the others close also (even if they're still pending).

I've tried using MagicNumber to specify related trades to close at once but this doesn't seem to work. How would I do this?

Kind regards,

Matt


Magic number works if you use it correctly. For example if your orders were marked with some magic number:

Code: Select all

if OrderClosed(handle) then
  begin
    for i:=OrdersTotal - 1 downto 0 do
     if OrderSelect(i, SELECT_BY_POS, MODE_TRADES) then
       if OrderMagicNumber = <some number> then
         CloseOrder(OrderHandle);
  end;


Note: you should select orders in opposite direction, because when you close them your order will be broken if you select them from 0 to ...
Hasta la vista
Mike

Matt_mm
Posts: 17
Joined: Sat Jun 06, 2009 8:40 pm

#3 Postby Matt_mm » Thu Jul 09, 2009 5:18 pm

Thanks Mike,

Perhaps it was my incorrect for loop. I'll give this a go.

Kind regards,

Matt

Matt_mm
Posts: 17
Joined: Sat Jun 06, 2009 8:40 pm

#4 Postby Matt_mm » Fri Jul 10, 2009 6:02 am

No luck for some reason here's my code:

Code: Select all

for i:=0 to OrdersTotal do
    begin
      if OrderSelect(i, SELECT_BY_TICKET, MODE_HISTORY) then
      begin
        if OrderClosed(i) then
        begin
          TempMagicNum:= OrderMagicNumber;
          for ii:=0 to OrdersTotal do
          begin
            if OrderSelect(i, SELECT_BY_POS, MODE_TRADES) then
            begin
              if (OrderMagicNumber = TempMagicNum) then
              begin
                if (OrderType = tp_BuyStop) or (OrderType = tp_SellStop) then
                begin
                  DeleteOrder(OrderTicket);
                end;
                if (OrderType = tp_Buy) or (OrderType = tp_Sell) then
                begin
                  CloseOrder(OrderTicket);
                end;
              end;
            end;
          end;
        end;
      end;
    end;

User avatar
Terranin
Site Admin
Posts: 833
Joined: Sat Oct 21, 2006 4:39 pm

#5 Postby Terranin » Fri Jul 10, 2009 2:00 pm

Matt_mm wrote:No luck for some reason here's my code:

Code: Select all

for i:=0 to OrdersTotal do
    begin
      if OrderSelect(i, SELECT_BY_TICKET, MODE_HISTORY) then
      begin
        if OrderClosed(i) then
        begin
          TempMagicNum:= OrderMagicNumber;
          for ii:=0 to OrdersTotal do
          begin
            if OrderSelect(i, SELECT_BY_POS, MODE_TRADES) then
            begin
              if (OrderMagicNumber = TempMagicNum) then
              begin
                if (OrderType = tp_BuyStop) or (OrderType = tp_SellStop) then
                begin
                  DeleteOrder(OrderTicket);
                end;
                if (OrderType = tp_Buy) or (OrderType = tp_Sell) then
                begin
                  CloseOrder(OrderTicket);
                end;
              end;
            end;
          end;
        end;
      end;
    end;


Lots of mistakes. Even difficult to understand what you want to do with this code.

OrdersTotal is only for opened positions. You can not search with it in history records. For history you should use HistoryTotal.

You can not use OrdersTotal and SELECT_BY_TICKET because it means you need to pass ticket but not a position number.

If you search in history then every order there is closed already.

If you close order - it changes HistoryTotal, you can not do this in the loop using HistoryTotal, at the same time it changes OrdersTotal and also affects other loop.

etc...

The correct algorithm would be to track new order by saving its handle -

Code: Select all

OrderHandle := SendInstantOrder(....)

....

if OrderClosed(OrderHandle) then <do something>


when you found situation that order was closed delete all other orders with same MagicNumber

Code: Select all

OrderSelect(OrderHande, SELECT_BY_TICKET, MODE_HISTORY);
   MagicNumber := OrderMagicNumber;


    for i:=OrdersTotal - 1 downto 0 do
     if OrderSelect(i, SELECT_BY_POS, MODE_TRADES) then
       if OrderMagicNumber = MagicNumber then
         if (OrderType = tp_Buy) or (OrderType = tp_Sell) then
           CloseOrder(OrderHandle)
         else
           DeleteOrder(OrderHandle);
Hasta la vista

Mike

Matt_mm
Posts: 17
Joined: Sat Jun 06, 2009 8:40 pm

#6 Postby Matt_mm » Sun Jul 12, 2009 6:35 am

Thanks Terranin, that puts a lot in perspective.

I've got a related issue. I got the code to work but going through all the history continuously uses too much CPU. So I used your example to store any open positions in an array and remove them from the array once they're closed. Problem is I still need a big array so I added a variable to count the positions (since I couldn't dynamically change the array size in Delphi once it's been set once).

If I use the array length - length(array) - it works but is still processor intensive, when I use a variable to keep track of how many positions are in the array (PosNum) and use that in the loop it stops working and doesn't close the positions. Here's the code:

Code: Select all

MagicNum: integer = 0;
OpenPositions: array[0..50] of integer;
PosNum: integer = 0;


begin
   if (OpenShort) then
   begin
      OpenPositions[PosNum]:= OrderNumber;
      PosNum:= PosNum+1;
      SendInstantOrder(Symbol, op_Sell, PositionSize, StopPrice, ProfitPrice, '', MagicNum, OrderNumber);
      if (Stack) then
      begin
         SendPendingOrder(Symbol, op_SellStop, PositionSize, StopPrice, ProfitPrice, (EntryPrice-(Round(TakeProfit*0.7)*Point)), '', MagicNum, OrderNumber);
         SendPendingOrder(Symbol, op_SellStop, PositionSize, StopPrice, ProfitPrice, (EntryPrice-(Round(TakeProfit*0.8)*Point)), '', MagicNum, OrderNumber);
         SendPendingOrder(Symbol, op_SellStop, PositionSize, StopPrice, ProfitPrice, (EntryPrice-(Round(TakeProfit*0.9)*Point)), '', MagicNum, OrderNumber);
         MagicNum:= MagicNum +1;
      end;
   end;


And the one cancels other code:


Code: Select all

ArrayLen:= length(OpenPositions);
   for i:=0 to ArrayLen do
   begin
      if OrderClosed(OpenPositions[i]) then
      begin
         OrderSelect(OpenPositions[i], SELECT_BY_TICKET, MODE_HISTORY);
         TempMagicNum:= OrderMagicNumber;
         for ii:=0 to OrdersTotal do
         begin
            if OrderSelect(ii, SELECT_BY_POS, MODE_TRADES) then
            begin
               if (OrderMagicNumber = TempMagicNum) then
               begin
                  if (OrderType = tp_BuyStop) or (OrderType = tp_SellStop) then
                  begin
                     DeleteOrder(OrderTicket);
                  end;
                  if (OrderType = tp_Buy) or (OrderType = tp_Sell) then
                  begin
                     CloseOrder(OrderTicket);
                  end;
               end;
            end;
         end;
         for delIndex:= i to PosNum do
         begin
            OpenPositions[delIndex] := OpenPositions[delIndex+1];
            OpenPositions[ArrayLen] := 0;
         end;
         PosNum:= PosNum - 1;
      end;
   end;
end;


If instead of using "for i:=0 to ArrayLen do" I tried using the PosNum variable but as soon as I do this the code stops closing the positions.

Code: Select all

for i:=0 to (PosNum) do


If I instead leave the original code it still doesn't work properly since it's taking so much load on the CPU that there is a delay and some of the positions don't open at the right time, probably because it's still trying to do other calculations. So I need a more efficient way of doing this?

Any help would be greatly appreciated.

Kind regards,

Matt[/code]

dackjaniels
Posts: 151
Joined: Tue Feb 24, 2009 1:03 pm

#7 Postby dackjaniels » Mon Jul 13, 2009 5:50 am

Hi Matt_mm,
Maybe this will help a little until Mike is able to reply...

Problem is I still need a big array so I added a variable to count the positions (since I couldn't dynamically change the array size in Delphi once it's been set once).


You can create dynamic arrays in delphi using something like this:

(Global Vars)

OpenPositions: array of integer;

(Init Proc)

SetLength(OpenPositions, 0); //Initialises array, setting its length to 0

(your proc to open orders)

Code: Select all

if (OpenShort) then
begin
  SetLength(OpenPositions, Length(OpenPositions) + 1); //increase length of array by 1 element ready to accept next order number
//THE LINE BELOW IS INCORRECT, PLEASE SEE SECOND POST BELOW FOR CORRECT USAGE
  OpenPositions[Length(OpenPositions)-1] := SendInstantOrder(Symbol, op_Sell, PositionSize, StopPrice, ProfitPrice, '', MagicNum, OrderNumber); //Saves orderhandle to newest array element
      if (Stack) then
      begin
         SendPendingOrder(Symbol, op_SellStop, PositionSize, StopPrice, ProfitPrice, (EntryPrice-(Round(TakeProfit*0.7)*Point)), '', MagicNum, OrderNumber);
         SendPendingOrder(Symbol, op_SellStop, PositionSize, StopPrice, ProfitPrice, (EntryPrice-(Round(TakeProfit*0.8)*Point)), '', MagicNum, OrderNumber);
         SendPendingOrder(Symbol, op_SellStop, PositionSize, StopPrice, ProfitPrice, (EntryPrice-(Round(TakeProfit*0.9)*Point)), '', MagicNum, OrderNumber);
         MagicNum:= MagicNum +1;
      end;
end;



Also in your original code...

Code: Select all

begin
   if (OpenShort) then
   begin
      OpenPositions[PosNum]:= OrderNumber;
      PosNum:= PosNum+1;
      SendInstantOrder(Symbol, op_Sell, PositionSize, StopPrice, ProfitPrice, '', MagicNum, OrderNumber);
      ...


The third line populates the [posnum] index of the array with the contents of the OrderNumber variable. At this point in time the OrderNumber variable contains the orderhandle of the previous order (or the initial value of the variable if no previous order was created). You would need to place this line after the SendInstantOrder line so it gets populated with the orderhandle of the order just sent or preferably, use the method I used in my first bit of code above, assigning the result of the SendInstantOrder function directly to the OpenPositions array (OpenPositions[Length(OpenPositions)-1] := SendInstantOrder(....)).

The OCO part could be something like this...

Code: Select all

//Length returns number of elements in array so array 0 to 9 has 10 elements and length function will return 10.

ArrayLen:= length(OpenPositions)-1; Upper boundary of array is now correctly set to Length(OpenPositions)-1
for i:=ArrayLen downto 0 do //start loop at end of array and work backwards, this way resizing the array (setlength below) should not affect loop
   begin
      if OrderClosed(OpenPositions[i]) then
      begin
         OrderSelect(OpenPositions[i], SELECT_BY_TICKET, MODE_HISTORY);
         TempMagicNum:= OrderMagicNumber;
         for ii:=OrdersTotal-1 downto 0 do //select orders in reverse as Mike suggested, also note OrdersTotal-1 is correct starting point in the loop
         begin
            if OrderSelect(ii, SELECT_BY_POS, MODE_TRADES) then
               if (OrderMagicNumber = TempMagicNum) then
                  if (OrderType = tp_BuyStop) or (OrderType = tp_SellStop) then
                     DeleteOrder(OrderTicket)
                  else
                     CloseOrder(OrderTicket);
         end;

         for delIndex:= i to ArrayLen-1 do
            OpenPositions[delIndex] := OpenPositions[delIndex+1]; //shifts all but last array element down by 1

            SetLength(OpenPositions, Length(OpenPositions)-1); //resize array removing last element
      end;
   end;


NOTE: I have not tested any of the above code, just trying to show you the principles involved.

If you have any questions please ask.

Steve
Last edited by dackjaniels on Tue Jul 14, 2009 3:36 am, edited 1 time in total.

Matt_mm
Posts: 17
Joined: Sat Jun 06, 2009 8:40 pm

#8 Postby Matt_mm » Tue Jul 14, 2009 12:56 am

Thanks Dackjaniels!

It really helped explaining some of your logic too, I was wondering why Mike had used the downto loop but when you explained it I realised why.

The changes to the code work great! (I'd tried using a dynamic array first but maybe because of my loop or incorrect understanding of length it wasn't working).

The only thing that still doesn't work is I can't assign an integer var to an order

Code: Select all

OpenPositions[ArrayLen] := SendInstantOrder(etc);


An error comes back saying I'm trying to assign a boolean value to an integer.

I also couldn't use Mike's new function for time updated in 2.1. I installed 2.1 over the top of 2.0 and copied the library files from the Forex Tester folder to replace my existing but maybe I need to do a clean install for the libraries to install properly?

dackjaniels
Posts: 151
Joined: Tue Feb 24, 2009 1:03 pm

#9 Postby dackjaniels » Tue Jul 14, 2009 3:33 am

Hey Matt_mm,
My bad, I made a mistake that's why you are getting the boolean/integer error. I've done this plenty of times, guess I just had a [premature] 'senior' moment. :)

The SendInstantOrder function returns a boolean, if it's successful it returns TRUE othewise it returns FALSE. It also returns the OrderHandle which is an integer.

Here's a better example of use:

Code: Select all

If SendInstantOrder(Symbol, op_Sell, PositionSize, StopPrice, ProfitPrice, '', MagicNum, OpenPositions[Length(OpenPositions)-1]); then
  Print('Order successfully placed')  //Printed if order is successful
else
  Print('Error placing Order'); //Printed if an error occurs placing order


Note the destination variable for the orderhandle is the last parameter passed to the function.


Regarding the time function, I am assuming you are referring to the new TimeCurrent function included in FT that returns the time of the most recent tick (emulating getting the current time from the server in live trading).

I have had this working in the past so would guess it's an issue regarding the StrategyInterfaceUnit.pas file. Just be sure that you are compiling against the correct (latest) version. Delphi will usually look for this file in the folder where you save your delphi projects so just double-check it has been copied there correctly.

For example, if you have two folders where you save your indicator and strategy delphi projects...

Documents\Borland Delphi Projects\Indicators
Documents\BorlandDelphi Projects\Strategies

ensure you have the following files also (copied from the corresponding ForexTester2\Examples\Indicators\Delphi and C:\ForexTester2\Examples\Strategies\Delphi sub folders)

Documents\Borland Delphi Projects\Indicators\TechnicalFunctions.pas
Documents\BorlandDelphi Projects\Strategies\TechnicalFunctions.pas
Documents\Borland Delphi Projects\Indicators\IndicatorInterfaceUnit.pas
Documents\BorlandDelphi Projects\Strategies\StrategyInterfaceUnit.pas

NOTE: The \Indicators\TechnicalFuntions.pas and \Strategies\TechnicalFuntions.pas files are different, do not copy the same file to both locations as I once did :oops:

Regards,
Steve

Matt_mm
Posts: 17
Joined: Sat Jun 06, 2009 8:40 pm

#10 Postby Matt_mm » Tue Jul 14, 2009 5:37 pm

Thanks Dackjaniels!

That should solve my last problems.

Thanks Mike too!

dackjaniels
Posts: 151
Joined: Tue Feb 24, 2009 1:03 pm

#11 Postby dackjaniels » Wed Jul 15, 2009 6:27 am

Glad I could help Matt :)


Return to “FT API”

Who is online

Users browsing this forum: No registered users and 19 guests