How to open position on hedging and non-hedging accounts
Brief
The article describes how to open a position on hedging and non-hedging accounts.
Details
How to Open Position
Positions are opened by orders. So, to open a position, create a request to make an order.
When the order is created, it appears in the Orders
table. It stays in the table until it is executed.
Depending on market liquidity and position maintenance (that is, the way trade operations are performed on an account), there are a few possible results of the order execution. To process the results, it is necessary to monitor table events.
Section 1 of the article discusses how to process the basic case when the result of the order execution is a newly opened position. The section also discusses how to the process the case when the order is rejected.
Sections 2 and 3 discuss other cases that may happen on the Real market when the order is sent on external execution, namely, partial execution.
Section 4 discusses how to process all the possible results of the order execution.
1. Most Usual Case
1.1. Create and Send Order Request
To request an order creation, you should fill an instance of O2GValueMap
with corresponding parameters.
The set of parameters depends on a particular order type. Please refer to the list of order types for the detailed description on how to fill the value map for each particular order type.
Below is an example of creating and sending a request for an Open Market order.
public void CreateTrueMarketOrder(string sOfferID, string sAccountID, int iAmount, string sBuySell)
{
O2GRequestFactory factory = Program.Session.getRequestFactory();
O2GValueMap valuemap = factory.createValueMap();
valuemap.setString(O2GRequestParamsEnum.Command, Constants.Commands.CreateOrder);
valuemap.setString(O2GRequestParamsEnum.OrderType, Constants.Orders.TrueMarketOpen);
// The identifier of the account the order should be placed for.
valuemap.setString(O2GRequestParamsEnum.AccountID, sAccountID);
// The identifier of the instrument the order should be placed for.
valuemap.setString(O2GRequestParamsEnum.OfferID, sOfferID);
// The order direction: Constants.Sell for "Sell", Constants.Buy for "Buy".
valuemap.setString(O2GRequestParamsEnum.BuySell, sBuySell);
// The quantity of the instrument to be bought or sold.
valuemap.setInt(O2GRequestParamsEnum.Amount, iAmount);
// The custom identifier of the order.
valuemap.setString(O2GRequestParamsEnum.CustomID, "TrueMarketOrder");
O2GRequest request = factory.createOrderRequest(valuemap);
if (request != null)
{
mRequestID = request.RequestID;
Program.Session.sendRequest(request);
}
else
{
Console.WriteLine("Cannot create request; probably some arguments are missing or incorrect");
}
}
RequestID
should be saved. It is necessary to monitor the order creation.
1.2. Process Request Response
After sending a request for an order creation to the server, process results of request execution. Possible results are as follows:
Request completed
It means that the order has been validated and accepted.
Actually, the onRequestCompleted
event does not mean that the order has been successfully executed.
So you should monitor the execution of your order to know the results of the execution.
Important: The onRequestCompleted
event should not be used as the confirmation of an order creation.
Instead, you should monitor appearance of the order in the Orders
table (see the "Monitor Appearing Order in Orders Table" section below).
Request failed
It means that the order has not passed the initial verification. For example, an incorrect account was specified, there is insufficient margin, etc.
To process notifications about the request response, an instance of the class implementing the IO2GResponseListener
interface should be subscribed to a session object by using the subscribeResponse
method. To process notifications about request failures, you should use the IO2GResponseListener.onRequestFailed
method.
Below is an example of the IO2GResponseListener
interface implementation.
class ResponseListener : IO2GResponseListener
{
public void onRequestCompleted(string requestId, O2GResponse response)
{
//the method should NOT be used as the confirmation of an order creation
}
public void onRequestFailed(string requestId, string error)
{
Program.CannotOpenPosition(error);
}
public void onTablesUpdates(O2GResponse data)
{
//the method should be implemented when table manager is not used
}
}
1.3. Result of Request Execution: Order and Trade
When the order is created, it appears in the Orders
table. It stays in the table until it is executed. When the order is executed, a position is opened and appears in the Trades
table. To monitor the appearance the order and the trade in the tables, it is necessary to subscribe to table events.
1.3.1. How to Subscribe to Table Events
There are two ways to subscribe for table events:
OnTableUpdates
You should handle table updates in the onTablesUpdates()
method by implementing the IO2GResponseListener
interface.
You should create an instance of the O2GTablesUpdatesReader
interface by using O2GResponseReaderFactory
.
TableListener
Create a table listener class that implements the IO2GTableListener
interface. Then create an object of that class and subscribe the table listener object to a table event. For details, please read “How to use table manager in ForexConnect API.”
You should subscribe to the following events:
onAdded
for Orders table (further it will be referred as onOrderAdded
event),
onAdded
for Trades table (furhter it will be referred as onTradeAdded
event).
1.3.2. How to Monitor Appearance of Order in Orders Table
As it has been said above, the onRequestCompleted
event cannot be used as the confirmation of the order creation. You should check if the order is actually added to the Orders table. To do so, check OnOrderAdded
events for the Orders
table. You should make sure that the event satisfies the following criteria:
The RequestID
order field must be the same as the RequestID
of the order request you have sent;
The Type
order field must be the same as the type of your order. It is important because the table can contain several orders with the same RequestID. For example, if a market order with stop and limit orders attached is created, the table contains several orders with the same RequestID
. So you should use the Type
field to uniquely identify the order.
Once you get such an event, you should retrieve OrderID
. OrderID
is necessary to monitor further order execution.
1.3.3. How to Monitor Appearance of Position in Trades Table
To monitor appearance of a position in the Trades
table, check OnTradeAdded
events for the Trades
table.
When the event occurs, it is necessary to check that the added trade is opened by your order. To do so, make sure that the OpenOrderID
field of the added trade is the same as the OrderID
of your order.
1.4. Order Rejection
Sometimes there is no liquidity on the market. It may lead to failure to fill an order and consequently to the order rejection.
There is also possible a case when the market price significantly moves away from the order price.
For the Open Range and the Open Limit orders, it may also lead to the order rejection.
In case of rejection, a rejection message will appear in the Messages table.
To process this case, it is necessary to monitor OnAdded
events of Messages table (further it will be referred as onMessageAdded
event).
To make sure that an OnMessageAdded
event occurs for your order,
it is necessary to check that the Feature
field of the message is the same as "Market conditions" and
the message text
contains your OrderID
.
1.5. Diagram and Sample for Processing Happy Day Case
Below is the state machine diagram that demonstrates what events should be processed to monitor the creation and the execution of an order and what actions should be performed depending on the order status.
Below is an example of the diagram implementation.
[hide]
using System;
using System.Collections.Generic;
using System.Text;
using fxcore2;
namespace OrdersMonitor
{
/// <summary>
/// Helper class for monitoring creation open positions using a open order.
/// On no dealing desk more than one position can be create. It is depends on
/// liquidity on forex market, The class stores all open positions
/// </summary>
internal class OrderMonitor
{
static public bool IsOpeningOrder(O2GOrderRow order)
{
return order.Type.StartsWith("O");
}
private enum OrderState
{
OrderExecuting,
OrderExecuted,
OrderCanceled,
OrderRejected
}
private volatile OrderState mState;
private O2GTradeRow mTrade;
private volatile int mTotalAmount;
private volatile int mRejectAmount;
private O2GOrderRow mOrder;
private string mRejectMessage;
public enum ExecutionResult
{
Executing,
Executed,
PartialRejected,
FullyRejected,
Canceled
};
/// <summary>
/// ctor
/// </summary>
/// <param name="order">Order for monitoring of execution</param>
public OrderMonitor(O2GOrderRow order)
{
mOrder = order;
mRejectAmount = 0;
mState = OrderState.OrderExecuting;
mResult = ExecutionResult.Executing;
mTrade = null;
}
/// <summary>
/// Process trade adding during order execution
/// </summary>
public void OnTradeAdded(O2GTradeRow tradeRow)
{
String tradeOrderID = tradeRow.OpenOrderID;
String orderID = mOrder.OrderID;
if (tradeOrderID == orderID)
{
mTrade= tradeRow;
if (mState == OrderState.OrderExecuted ||
mState == OrderState.OrderRejected ||
mState == OrderState.OrderCanceled)
{
if (IsAllTradeReceived())
SetResult(true);
}
}
}
/// <summary>
/// Process order data changing during execution
/// </summary>
public void OnOrderChanged(O2GOrderRow orderRow)
{
//STUB
}
/// <summary>
/// Process order deletion as result of execution
/// </summary>
public void OnOrderDeleted(O2GOrderRow orderRow)
{
String deletedOrderID = orderRow.OrderID;
String orderID = mOrder.OrderID;
if (deletedOrderID == orderID)
{
// Store Reject amount
if (OrderRowStatus.Rejected.Equals(orderRow.Status))
{
mState = OrderState.OrderRejected;
mRejectAmount = orderRow.Amount;
mTotalAmount = orderRow.OriginAmount - mRejectAmount;
if (!string.IsNullOrEmpty(mRejectMessage) && IsAllTradeReceived())
SetResult(true);
}
else if (OrderRowStatus.Canceled.Equals(orderRow.Status))
{
mState = OrderState.OrderCanceled;
mRejectAmount = orderRow.Amount;
mTotalAmount = orderRow.OriginAmount - mRejectAmount;
if (IsAllTradeReceived())
SetResult(false);
}
else
{
mRejectAmount = 0;
mTotalAmount = orderRow.OriginAmount;
mState = OrderState.OrderExecuted;
if (IsAllTradeReceived())
SetResult(true);
}
}
}
/// <summary>
/// Process reject message as result of order execution
/// </summary>
public void OnMessageAdded(O2GMessageRow messageRow)
{
if (mState == OrderState.OrderRejected ||
mState == OrderState.OrderExecuting)
{
bool IsRejectMessage = CheckAndStoreMessage(messageRow);
if (mState == OrderState.OrderRejected && IsRejectMessage)
SetResult(true);
}
}
/// <summary>
/// Event about order execution is completed and all affected trades as opened/closed, all reject/cancel processed
/// </summary>
public event EventHandler OrderCompleted;
/// <summary>
/// Result of Order execution
/// </summary>
public ExecutionResult Result
{
get
{
return mResult;
}
}
private volatile ExecutionResult mResult;
/// <summary>
/// Order execution is completed (with any result)
/// </summary>
public bool IsOrderCompleted
{
get
{
return (mResult != ExecutionResult.Executing);
}
}
/// <summary>
/// Monitored order
/// </summary>
public O2GOrderRow Order
{
get
{
return mOrder;
}
}
/// <summary>
/// List of Closed Trades which were opened as effects of order execution
/// </summary>
public O2GTradeRow Trade
{
get
{
return mTrade;
}
}
/// <summary>
/// Amount of rejected part of order
/// </summary>
public int RejectAmount
{
get
{
return mRejectAmount;
}
}
/// <summary>
/// Info message with a reason of reject
/// </summary>
public string RejectMessage
{
get
{
return mRejectMessage;
}
}
private void SetResult(bool success)
{
if (success)
{
if (mRejectAmount == 0)
mResult = ExecutionResult.Executed;
else
mResult = (mTrade == null) ? ExecutionResult.FullyRejected : ExecutionResult.PartialRejected;
}
else
mResult = ExecutionResult.Canceled;
if (OrderCompleted != null)
OrderCompleted(this, EventArgs.Empty);
}
private bool IsAllTradeReceived()
{
if (mState == OrderState.OrderExecuting)
return false;
int currenTotalAmount = 0;
if (mTrade != null)
{
currenTotalAmount += mTrade.Amount;
}
return currenTotalAmount == mTotalAmount;
}
private bool CheckAndStoreMessage(O2GMessageRow message)
{
String feature;
feature = message.Feature;
if (MessageFeature.MarketCondition.Equals(feature))
{
String text = message.Text;
int findPos = text.IndexOf(mOrder.OrderID);
if (findPos > -1)
{
mRejectMessage = text;
return true;
}
}
return false;
}
}
internal class OrderRowStatus
{
public static string Rejected = "R";
public static string Canceled = "C";
public static string Executed = "F";
//...
}
internal class MessageFeature
{
public static String MarketCondition = "5";
//...
}
}
An example on how to subscribe the order monitor by using onTableUpdates
is given below.
[hide]
class ResponseListener : IO2GResponseListener
{
private EventWaitHandle mEvent = null;
private OrderMonitor mOrderMonitor = null;
private O2GSession mSession;
private string mRequestID;
/// <summary>
/// ctor
/// </summary>
public TableListener(O2GSession Session)
{
mSession = Session;
mEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
}
/// <summary>
/// destructor
/// </summary>
~TableListener()
{
if (mEvent != null)
{
mEvent.Close();
mEvent = null;
}
}
/// <summary>
/// Wait for the event
/// </summary>
/// <param name="timeout"></param>
/// <returns></returns>
public bool WaitForEvent(int timeout)
{
return mEvent.WaitOne(timeout);
}
#region IO2GResponseListener Members
public void onRequestCompleted(string requestId, O2GResponse response)
{
}
public void onRequestFailed(string requestId, string error)
{
Program.CannotOpenPosition(error);
mEvent.Set();
}
public void onTablesUpdates(O2GResponse data)
{
O2GResponseReaderFactory factory = mSession.getResponseReaderFactory();
if (factory != null)
{
O2GTablesUpdatesReader reader = factory.createTablesUpdatesReader(data);
for (int ii = 0; ii < reader.Count; ii++)
{
switch (reader.getUpdateTable(ii))
{
case O2GTableType.Accounts:
O2GAccountRow account = reader.getAccountRow(ii);
//Show balance updates
Console.WriteLine("Balance: {0}", account.Balance);
break;
case O2GTableType.Orders:
O2GOrderRow order = reader.getOrderRow(ii);
switch (reader.getUpdateType(ii))
{
case O2GTableUpdateType.Insert:
if ((OrderMonitor.IsClosingOrder(order) || OrderMonitor.IsOpeningOrder(order)) &&
mOrderMonitor == null)
{
mOrderMonitor = new OrderMonitor(order);
}
break;
case O2GTableUpdateType.Delete:
if (mOrderMonitor != null)
{
mOrderMonitor.OnOrderDeleted(order);
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
break;
}
break;
case O2GTableType.Trades:
O2GTradeRow trade = reader.getTradeRow(ii);
if (reader.getUpdateType(ii) == O2GTableUpdateType.Insert)
{
if (mOrderMonitor != null)
{
mOrderMonitor.OnTradeAdded(trade);
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
}
break;
case O2GTableType.Messages:
O2GMessageRow message = reader.getMessageRow(ii);
if (reader.getUpdateType(ii) == O2GTableUpdateType.Insert)
{
if (mOrderMonitor != null)
{
mOrderMonitor.OnMessageAdded(message);
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
}
break;
}
}
}
}
#endregion
/// <summary>
/// Open a position
/// </summary>
public void CreateTrueMarketOrder(string sOfferID, string sAccountID, int iAmount, string sBuySell)
{
O2GRequestFactory factory = Program.Session.getRequestFactory();
O2GValueMap valuemap = factory.createValueMap();
valuemap.setString(O2GRequestParamsEnum.Command, Constants.Commands.CreateOrder);
valuemap.setString(O2GRequestParamsEnum.OrderType, Constants.Orders.TrueMarketOpen);
// The identifier of the account the order should be placed for.
valuemap.setString(O2GRequestParamsEnum.AccountID, sAccountID);
// The identifier of the instrument the order should be placed for.
valuemap.setString(O2GRequestParamsEnum.OfferID, sOfferID);
// The order direction: Constants.Sell for "Sell", Constants.Buy for "Buy".
valuemap.setString(O2GRequestParamsEnum.BuySell, sBuySell);
// The quantity of the instrument to be bought or sold.
valuemap.setInt(O2GRequestParamsEnum.Amount, iAmount);
// The custom identifier of the order.
valuemap.setString(O2GRequestParamsEnum.CustomID, "TrueMarketOrder");
O2GRequest request = factory.createOrderRequest(valuemap);
if (request != null)
{
mRequestID = request.RequestID;
Program.Session.sendRequest(request);
mEvent.WaitOne(); //Infinite waiting
}
else
{
Console.WriteLine("Cannot create request; probably some arguments are missing or incorrect");
}
}
void PrintResult()
{
if (mOrderMonitor != null)
{
OrderMonitor.ExecutionResult result = mOrderMonitor.Result;
O2GTradeRow trade = null;
O2GOrderRow order = mOrderMonitor.Order;
String orderID = order.OrderID;
trade = mOrderMonitor.Trade;
switch (result)
{
case OrderMonitor.ExecutionResult.Canceled:
if (trades.Count > 0)
{
PrintTrade(trade, orderID);
Console.WriteLine("A part of the order has been canceled. Amount = {0}", mOrderMonitor.RejectAmount);
}
else
{
Console.WriteLine("The order: OrderID = {0} has been canceled.", orderID);
Console.WriteLine("The cancel amount = {0}.", mOrderMonitor.RejectAmount);
}
break;
case OrderMonitor.ExecutionResult.FullyRejected:
Console.WriteLine("The order has been rejected. OrderID = {0}", orderID);
Console.WriteLine("The rejected amount = {0}", mOrderMonitor.RejectAmount);
Console.WriteLine("Rejection cause: {0}", mOrderMonitor.RejectMessage);
break;
case OrderMonitor.ExecutionResult.PartialRejected:
PrintTrade(trade, orderID);
Console.WriteLine("A part of the order has been rejected. Amount = {0}", mOrderMonitor.RejectAmount);
Console.WriteLine("Rejection cause: {0} ", mOrderMonitor.RejectMessage);
break;
case OrderMonitor.ExecutionResult.Executed:
PrintTrade(trade, orderID);
break;
}
}
}
void PrintTrade(O2GTradeRow trade, string orderID)
{
if (trade == null)
return;
Console.WriteLine("For the order: OrderID = {0} the following position have been opened:", orderID);
String tradeID = trade.TradeID;
int amount = trade.Amount;
double rate = trade.OpenRate;
Console.WriteLine("Trade ID: {0}; Amount: {1}; Rate: {2}", tradeID, amount, rate);
}
}
An example on how to subscribe the order monitor by using TableListener
can be found below.
[hide]
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using fxcore2;
using OrdersMonitor;
namespace OpenPosition
{
class TableListener : IO2GTableListener
{
private EventWaitHandle mEvent = null;
private OrderMonitor mOrderMonitor = null;
private O2GSession mSession;
private O2GAccountsTable mAccounts;
private O2GOrdersTable mOrders;
private O2GTradesTable mTrades;
private O2GMessagesTable mMessages;
private string mRequestID;
/// <summary>
/// ctor
/// </summary>
public TableListener(O2GSession Session)
{
mSession = Session;
mEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
}
/// <summary>
/// destructor
/// </summary>
~TableListener()
{
if (mEvent != null)
{
mEvent.Close();
mEvent = null;
}
}
/// <summary>
/// Wait for the event
/// </summary>
/// <param name="timeout"></param>
/// <returns></returns>
public bool WaitForEvent(int timeout)
{
return mEvent.WaitOne(timeout);
}
#region IO2GTableListener Members
public void onAdded(string rowID, O2GRow rowData)
{
O2GTableType type = rowData.TableType;
switch (type)
{
case O2GTableType.Orders:
O2GOrderRow orderRow = (O2GOrderRow)rowData;
if (mRequestID == orderRow.RequestID)
{
Console.WriteLine("The order has been added. Order ID: {0}, Rate: {1}, Time In Force: {2}",
orderRow.OrderID,
orderRow.Rate,
orderRow.TimeInForce);
mOrderMonitor = new OrderMonitor(orderRow);
}
break;
case O2GTableType.Trades:
O2GTradeRow tradeRow = (O2GTradeRow)rowData;
if (mOrderMonitor != null)
{
mOrderMonitor.OnTradeAdded(tradeRow);
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
break;
case O2GTableType.Messages:
O2GMessageRow messageRow = (O2GMessageRow)rowData;
if (mOrderMonitor != null)
{
mOrderMonitor.OnMessageAdded(messageRow);
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
break;
}
}
public void onChanged(string rowID, O2GRow rowData)
{
if (rowData.TableType == O2GTableType.Accounts)
{
O2GAccountTableRow account = (O2GAccountTableRow)rowData;
Console.WriteLine("Balance: {0}, Equity: {1}", account.Balance, account.Equity);
}
}
public void onDeleted(string rowID, O2GRow rowData)
{
if (rowData.TableType == O2GTableType.Orders)
{
O2GOrderRow orderRow = (O2GOrderRow)rowData;
if (mRequestID == orderRow.RequestID)
{
Console.WriteLine("The order has been deleted. Order ID: {0}", orderRow.OrderID);
mOrderMonitor.OnOrderDeleted(orderRow);
if (mOrderMonitor != null)
{
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
}
}
}
public void onStatusChanged(O2GTableStatus status)
{
}
#endregion
/// <summary>
/// Open a position
/// </summary>
public void CreateTrueMarketOrder(string sOfferID, string sAccountID, int iAmount, string sBuySell)
{
O2GRequestFactory factory = Program.Session.getRequestFactory();
O2GValueMap valuemap = factory.createValueMap();
valuemap.setString(O2GRequestParamsEnum.Command, Constants.Commands.CreateOrder);
valuemap.setString(O2GRequestParamsEnum.OrderType, Constants.Orders.TrueMarketOpen);
// The identifier of the account the order should be placed for.
valuemap.setString(O2GRequestParamsEnum.AccountID, sAccountID);
// The identifier of the instrument the order should be placed for.
valuemap.setString(O2GRequestParamsEnum.OfferID, sOfferID);
// The order direction: Constants.Sell for "Sell", Constants.Buy for "Buy".
valuemap.setString(O2GRequestParamsEnum.BuySell, sBuySell);
// The quantity of the instrument to be bought or sold.
valuemap.setInt(O2GRequestParamsEnum.Amount, iAmount);
// The custom identifier of the order.
valuemap.setString(O2GRequestParamsEnum.CustomID, "TrueMarketOrder");
O2GRequest request = factory.createOrderRequest(valuemap);
if (request != null)
{
mRequestID = request.RequestID;
SubscribeEvents();
Program.Session.sendRequest(request);
mEvent.WaitOne(); //Infinite waiting
Thread.Sleep(200); //wait account update receiving
UnsubscribeEvents();
}
else
{
Console.WriteLine("Cannot create request; probably some arguments are missing or incorrect");
}
}
public void CannotOpenPosition(string sError)
{
Console.WriteLine("Cannot open position, error: {0}", sError);
mEvent.Set();
}
void PrintResult()
{
if (mOrderMonitor != null)
{
OrderMonitor.ExecutionResult result = mOrderMonitor.Result;
O2GTradeRow trade;
O2GOrderRow order = mOrderMonitor.Order;
String orderID = order.OrderID;
trades = mOrderMonitor.Trade;
switch (result)
{
case OrderMonitor.ExecutionResult.Canceled:
if (trade != null)
{
PrintTrade(trade, orderID);
Console.WriteLine("A part of the order has been canceled. Amount = {0}", mOrderMonitor.RejectAmount);
}
else
{
Console.WriteLine("The order: OrderID = {0} has been canceled.", orderID);
Console.WriteLine("The cancel amount = {0}.", mOrderMonitor.RejectAmount);
}
break;
case OrderMonitor.ExecutionResult.FullyRejected:
Console.WriteLine("The order has been rejected. OrderID = {0}", orderID);
Console.WriteLine("The rejected amount = {0}", mOrderMonitor.RejectAmount);
Console.WriteLine("Rejection cause: {0}", mOrderMonitor.RejectMessage);
break;
case OrderMonitor.ExecutionResult.PartialRejected:
PrintTrade(trade, orderID);
Console.WriteLine("A part of the order has been rejected. Amount = {0}", mOrderMonitor.RejectAmount);
Console.WriteLine("Rejection cause: {0} ", mOrderMonitor.RejectMessage);
break;
case OrderMonitor.ExecutionResult.Executed:
PrintTrade(trade, orderID);
break;
}
}
}
void PrintTrade(O2GTradeRow trade, string orderID)
{
if (trade == null)
return;
Console.WriteLine("For the order: OrderID = {0} the following position have been opened:", orderID);
String tradeID = trade.TradeID;
int amount = trade.Amount;
double rate = trade.OpenRate;
Console.WriteLine("Trade ID: {0}; Amount: {1}; Rate: {2}", tradeID, amount, rate);
}
void SubscribeEvents()
{
O2GTableManager manager = mSession.getTableManager();
mOrders = (O2GOrdersTable)manager.getTable(O2GTableType.Orders);
mTrades = (O2GTradesTable)manager.getTable(O2GTableType.Trades);
mMessages = (O2GMessagesTable)manager.getTable(O2GTableType.Messages);
mAccounts = (O2GAccountsTable)manager.getTable(O2GTableType.Accounts);
mAccounts.subscribeUpdate(O2GTableUpdateType.Update, this);
mOrders.subscribeUpdate(O2GTableUpdateType.Insert, this);
mOrders.subscribeUpdate(O2GTableUpdateType.Delete, this);
mTrades.subscribeUpdate(O2GTableUpdateType.Insert, this);
mMessages.subscribeUpdate(O2GTableUpdateType.Insert, this);
}
void UnsubscribeEvents()
{
if (mOrders != null)
{
mOrders.unsubscribeUpdate(O2GTableUpdateType.Insert, this);
mOrders.unsubscribeUpdate(O2GTableUpdateType.Delete, this);
}
if (mTrades != null)
{
mTrades.unsubscribeUpdate(O2GTableUpdateType.Insert, this);
}
if (mMessages != null)
mMessages.unsubscribeUpdate(O2GTableUpdateType.Insert, this);
if (mAccounts != null)
mAccounts.unsubscribeUpdate(O2GTableUpdateType.Update, this);
}
}
class ResponseListener : IO2GResponseListener
{
#region IO2GResponseListener Members
public void onRequestCompleted(string requestId, O2GResponse response)
{
}
public void onRequestFailed(string requestId, string error)
{
Program.CannotOpenPosition(error);
}
public void onTablesUpdates(O2GResponse data)
{
}
#endregion
}
}
2. What is Partial Execution
2.1. No Dealing Desk
In the no dealing desk execution model, a broker passes on to his clients the best prices provided by one of the liquidity providers with a fixed mark-up
for each currency pair. A broker relies on these external providers with regard to currency pricing.
Although this model promotes efficiency and competition for market pricing,
there are certain limitations on liquidity that can affect the final execution of an order.
A partial fill occurs when only a part of the order can be executed at the price requested due to the limited liquidity.
The rest of the order will be filled at the next best available price or cancelled entirely depending on the order type (see the “Order Time in Force” section for details).
A partial fill is possible on Real accounts only.
Partial orders give a trader the opportunity to enter a part of the position instead of rejecting the entire order.
2.2. Order Time in Force
With different order types a trader has different time-in-force options that provide additional instructions on how long an order should remain active until it is executed or expires:
Fill Or Kill (FOK);
Immediate Or Cancel (IOC);
Good Till Cancelled (GTC);
Day (DAY).
Please see Orders State Machines for the description of time-in-force options for different order types.
An order with the GTC
and the IOC
time-in-force options can be executed partially. It means that one order may result in a number of positions opened/closed at different prices. A GTC
order is filled in portions until it is completely executed. An IOC
order is filled in portions to the maximum extent possible (until the liquidity pool dries up), the rest of the order is rejected. In case of partial closing, the system closes a position in the filled amount and opens a new position in the remaining amount. The system does so until the order is entirely filled (GTC
orders) or the liquidity dries up (IOC
orders).
3. Process Partial Execution
In case of partial filling, one order results in a number of positions opened at different prices. Depending on the order time in force, some part(s) of the order may also be rejected.
To monitor the partial execution, it is necessary to process the following events:
OnTradeAdded
The OpenOrderID
field of the added trade must be the same as the OrderID of your order.
OnMessageAdded
The Feature
field of the message must be the same as "Market conditions" and the message text must contain your OrderID.
To define whether or not the order execution is completed, you need to:
Process the onDeleted
event event of Orders table (further it will be referred as onOrderDeleted
event). When the order execution is completed, the order is deleted. In case of deletion, the order status is 'F' if the order is filled completely and ‘R’ if the order is rejected completely or partially. If the order is filled completely, a new trade(s) is inserted in the Trades table. If the order is rejected completely or partially, a rejection message is inserted in the Messages table. The rejected amount is contained in the order amount field.
Note that the order deletion, the trade addition, and the message rejection notification can be received randomly.
Check if the total amount of resulting opened positions (including the amount of rejected order parts, if any) matches the amount of the order specified in the order request.
4. All Variants of Order Execution
In addition to the described execution results (an opened position, a completely or partially rejected order), there is also possible a case when the order execution results in closure of the existing position(s).
This case is possible on accounts where hedging is not allowed. Note that hedging is not allowed on all the U.S. accounts. On such accounts, opening of a position for the symbol that already has an open position(s) of the opposite trade operation always causes closure of the open position(s). Note that if the amount of an open order exceeds the total amount of the currently opened positions on that symbol, the result of the order execution will be closure of the existing positions and opening of a new position in the rest of the amount.
For example, there are three long (buy) positions for EUR/USD totaling 50k. You create an open sell order in the amount of 70k for EUR/USD. The result of the order execution will be closure of the existing positions and opening of a new position in the amount of 20k.
To monitor the position(s) closure, it is necessary to check onAdded
events of ClosedTrades table (further it will be referred as onClosedTradeAdded
event). The CloseOrderID
field of the closed trade must be the same as the OrderID
of your order.
Please see below the summary table of all possible variants of the order execution and execution results.
Variant of Order Execution |
Execution Result |
An opened position |
A new position will appear in the Trades table. The position amount must be equal to the amount specified in the order request. |
A closed position |
A closed position will appear in the Closed Trades table. The position amount must not be less than the amount specified in the order request. |
A partially opened/closed position |
Either a new position will appear in the Trades table or a closed position will appear in the Closed Trades table. The position amount must be less than the amount specified in the order request. |
A completely rejected order |
A rejection message will appear in the Messages table. An order with the "R " status will appear in the Orders table. |
A partially rejected order |
Either a new position will appear in the Trades table or a closed position will appear in the Closed Trades table (depending on the account type).
A rejection message will appear in the Messages table. |
The state machine diagram below shows all possible execution variants and all events that must be processed to monitor the order execution. The diagram also shows what actions should be performed depending on the order status.
Below is an example of the diagram implementation.
[hide]
using System;
using System.Collections.Generic;
using System.Text;
using fxcore2;
namespace OrdersMonitor
{
/// <summary>
/// Helper class for monitoring creation open positions using a open order.
/// On no dealing desk more than one position can be create. It is depends on
/// liquidity on forex market, The class stores all open positions
/// </summary>
internal class OrderMonitor
{
static public bool IsOpeningOrder(O2GOrderRow order)
{
return order.Type.StartsWith("O");
}
static public bool IsClosingOrder(O2GOrderRow order)
{
return order.Type.StartsWith("C");
}
private enum OrderState
{
OrderExecuting,
OrderExecuted,
OrderCanceled,
OrderRejected
}
private volatile OrderState mState;
private List<O2GTradeRow> mTrades;
private List<O2GClosedTradeRow> mClosedTrades;
private volatile int mTotalAmount;
private volatile int mRejectAmount;
private O2GOrderRow mOrder;
private string mRejectMessage;
public enum ExecutionResult
{
Executing,
Executed,
PartialRejected,
FullyRejected,
Canceled
};
/// <summary>
/// ctor
/// </summary>
/// <param name="order">Order for monitoring of execution</param>
public OrderMonitor(O2GOrderRow order)
{
mOrder = order;
mRejectAmount = 0;
mState = OrderState.OrderExecuting;
mResult = ExecutionResult.Executing;
mTrades = new List<O2GTradeRow>();
mClosedTrades = new List<O2GClosedTradeRow>();
}
/// <summary>
/// Process trade adding during order execution
/// </summary>
public void OnTradeAdded(O2GTradeRow tradeRow)
{
String tradeOrderID = tradeRow.OpenOrderID;
String orderID = mOrder.OrderID;
if (tradeOrderID == orderID)
{
mTrades.Add(tradeRow);
if (mState == OrderState.OrderExecuted ||
mState == OrderState.OrderRejected ||
mState == OrderState.OrderCanceled)
{
if (IsAllTradeReceived())
SetResult(true);
}
}
}
/// <summary>
/// Process order data changing during execution
/// </summary>
public void OnOrderChanged(O2GOrderRow orderRow)
{
//STUB
}
/// <summary>
/// Process order deletion as result of execution
/// </summary>
public void OnOrderDeleted(O2GOrderRow orderRow)
{
String deletedOrderID = orderRow.OrderID;
String orderID = mOrder.OrderID;
if (deletedOrderID == orderID)
{
// Store Reject amount
if (OrderRowStatus.Rejected.Equals(orderRow.Status))
{
mState = OrderState.OrderRejected;
mRejectAmount = orderRow.Amount;
mTotalAmount = orderRow.OriginAmount - mRejectAmount;
if (!string.IsNullOrEmpty(mRejectMessage) && IsAllTradeReceived())
SetResult(true);
}
else if (OrderRowStatus.Canceled.Equals(orderRow.Status))
{
mState = OrderState.OrderCanceled;
mRejectAmount = orderRow.Amount;
mTotalAmount = orderRow.OriginAmount - mRejectAmount;
if (IsAllTradeReceived())
SetResult(false);
}
else
{
mRejectAmount = 0;
mTotalAmount = orderRow.OriginAmount;
mState = OrderState.OrderExecuted;
if (IsAllTradeReceived())
SetResult(true);
}
}
}
/// <summary>
/// Process reject message as result of order execution
/// </summary>
public void OnMessageAdded(O2GMessageRow messageRow)
{
if (mState == OrderState.OrderRejected ||
mState == OrderState.OrderExecuting)
{
bool IsRejectMessage = CheckAndStoreMessage(messageRow);
if (mState == OrderState.OrderRejected && IsRejectMessage)
SetResult(true);
}
}
/// <summary>
/// Process trade closing during order execution
/// </summary>
public void OnClosedTradeAdded(O2GClosedTradeRow closedTradeRow)
{
String orderID = mOrder.OrderID;
String closedTradeOrderID = closedTradeRow.CloseOrderID;
if (orderID == closedTradeOrderID)
{
mClosedTrades.Add(closedTradeRow);
if (mState == OrderState.OrderExecuted ||
mState == OrderState.OrderRejected ||
mState == OrderState.OrderCanceled)
{
if (IsAllTradeReceived())
SetResult(true);
}
}
}
/// <summary>
/// Event about order execution is completed and all affected trades as opened/closed, all reject/cancel processed
/// </summary>
public event EventHandler OrderCompleted;
/// <summary>
/// Result of Order execution
/// </summary>
public ExecutionResult Result
{
get
{
return mResult;
}
}
private volatile ExecutionResult mResult;
/// <summary>
/// Order execution is completed (with any result)
/// </summary>
public bool IsOrderCompleted
{
get
{
return (mResult != ExecutionResult.Executing);
}
}
/// <summary>
/// Monitored order
/// </summary>
public O2GOrderRow Order
{
get
{
return mOrder;
}
}
/// <summary>
/// List of Closed Trades which were opened as effects of order execution
/// </summary>
public ReadOnlyList<O2GTradeRow> Trades
{
get
{
return new ReadOnlyList<O2GTradeRow>(mTrades);
}
}
/// <summary>
/// List of Closed Trades which were closed as effects of order execution
/// </summary>
public ReadOnlyList<O2GClosedTradeRow> ClosedTrades
{
get
{
return new ReadOnlyList<O2GClosedTradeRow>(mClosedTrades);
}
}
/// <summary>
/// Amount of rejected part of order
/// </summary>
public int RejectAmount
{
get
{
return mRejectAmount;
}
}
/// <summary>
/// Info message with a reason of reject
/// </summary>
public string RejectMessage
{
get
{
return mRejectMessage;
}
}
private void SetResult(bool success)
{
if (success)
{
if (mRejectAmount == 0)
mResult = ExecutionResult.Executed;
else
mResult = (mTrades.Count == 0 && mClosedTrades.Count == 0) ? ExecutionResult.FullyRejected : ExecutionResult.PartialRejected;
}
else
mResult = ExecutionResult.Canceled;
if (OrderCompleted != null)
OrderCompleted(this, EventArgs.Empty);
}
private bool IsAllTradeReceived()
{
if (mState == OrderState.OrderExecuting)
return false;
int currenTotalAmount = 0;
for (int i = 0; i < mTrades.Count; i++)
{
currenTotalAmount += mTrades[i].Amount;
}
for (int i = 0; i < mClosedTrades.Count; i++)
{
currenTotalAmount += mClosedTrades[i].Amount;
}
return currenTotalAmount == mTotalAmount;
}
private bool CheckAndStoreMessage(O2GMessageRow message)
{
String feature;
feature = message.Feature;
if (MessageFeature.MarketCondition.Equals(feature))
{
String text = message.Text;
int findPos = text.IndexOf(mOrder.OrderID);
if (findPos > -1)
{
mRejectMessage = text;
return true;
}
}
return false;
}
}
internal class OrderRowStatus
{
public static string Rejected = "R";
public static string Canceled = "C";
public static string Executed = "F";
//...
}
internal class MessageFeature
{
public static String MarketCondition = "5";
//...
}
internal class ReadOnlyList<T> : IEnumerable<T>
{
private List<T> mList;
/// <summary>
/// ctor
/// </summary>
/// <param name="list"></param>
public ReadOnlyList(List<T> list)
{
mList = list;
}
public int Count
{
get
{
return mList.Count;
}
}
public T this[int index]
{
get
{
return mList[index];
}
}
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return (IEnumerator<T>)mList.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return (System.Collections.IEnumerator)mList.GetEnumerator();
}
#endregion
}
}
An example on how to subscribe the order monitor by using onTableUpdates
is provided below.
[hide]
class ResponseListener : IO2GResponseListener
{
private EventWaitHandle mEvent = null;
private OrderMonitor mOrderMonitor = null;
private O2GSession mSession;
private string mRequestID;
/// <summary>
/// ctor
/// </summary>
public TableListener(O2GSession Session)
{
mSession = Session;
mEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
}
/// <summary>
/// destructor
/// </summary>
~TableListener()
{
if (mEvent != null)
{
mEvent.Close();
mEvent = null;
}
}
/// <summary>
/// Wait for the event
/// </summary>
/// <param name="timeout"></param>
/// <returns></returns>
public bool WaitForEvent(int timeout)
{
return mEvent.WaitOne(timeout);
}
#region IO2GResponseListener Members
public void onRequestCompleted(string requestId, O2GResponse response)
{
}
public void onRequestFailed(string requestId, string error)
{
Program.CannotOpenPosition(error);
mEvent.Set();
}
public void onTablesUpdates(O2GResponse data)
{
O2GResponseReaderFactory factory = mSession.getResponseReaderFactory();
if (factory != null)
{
O2GTablesUpdatesReader reader = factory.createTablesUpdatesReader(data);
for (int ii = 0; ii < reader.Count; ii++)
{
switch (reader.getUpdateTable(ii))
{
case O2GTableType.Accounts:
O2GAccountRow account = reader.getAccountRow(ii);
//Show balance updates
Console.WriteLine("Balance: {0}", account.Balance);
break;
case O2GTableType.Orders:
O2GOrderRow order = reader.getOrderRow(ii);
switch (reader.getUpdateType(ii))
{
case O2GTableUpdateType.Insert:
if ((OrderMonitor.IsClosingOrder(order) || OrderMonitor.IsOpeningOrder(order)) &&
mOrderMonitor == null)
{
mOrderMonitor = new OrderMonitor(order);
}
break;
case O2GTableUpdateType.Delete:
if (mOrderMonitor != null)
{
mOrderMonitor.OnOrderDeleted(order);
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
break;
}
break;
case O2GTableType.Trades:
O2GTradeRow trade = reader.getTradeRow(ii);
if (reader.getUpdateType(ii) == O2GTableUpdateType.Insert)
{
if (mOrderMonitor != null)
{
mOrderMonitor.OnTradeAdded(trade);
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
}
break;
case O2GTableType.ClosedTrades:
O2GClosedTradeRow closedTrade = reader.getClosedTradeRow(ii);
if (reader.getUpdateType(ii) == O2GTableUpdateType.Insert)
{
if (mOrderMonitor != null)
{
mOrderMonitor.OnClosedTradeAdded(closedTrade);
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
}
break;
case O2GTableType.Messages:
O2GMessageRow message = reader.getMessageRow(ii);
if (reader.getUpdateType(ii) == O2GTableUpdateType.Insert)
{
if (mOrderMonitor != null)
{
mOrderMonitor.OnMessageAdded(message);
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
}
break;
}
}
}
}
#endregion
/// <summary>
/// Open a position
/// </summary>
public void CreateTrueMarketOrder(string sOfferID, string sAccountID, int iAmount, string sBuySell)
{
O2GRequestFactory factory = Program.Session.getRequestFactory();
O2GValueMap valuemap = factory.createValueMap();
valuemap.setString(O2GRequestParamsEnum.Command, Constants.Commands.CreateOrder);
valuemap.setString(O2GRequestParamsEnum.OrderType, Constants.Orders.TrueMarketOpen);
// The identifier of the account the order should be placed for.
valuemap.setString(O2GRequestParamsEnum.AccountID, sAccountID);
// The identifier of the instrument the order should be placed for.
valuemap.setString(O2GRequestParamsEnum.OfferID, sOfferID);
// The order direction: Constants.Sell for "Sell", Constants.Buy for "Buy".
valuemap.setString(O2GRequestParamsEnum.BuySell, sBuySell);
// The quantity of the instrument to be bought or sold.
valuemap.setInt(O2GRequestParamsEnum.Amount, iAmount);
// The custom identifier of the order.
valuemap.setString(O2GRequestParamsEnum.CustomID, "TrueMarketOrder");
O2GRequest request = factory.createOrderRequest(valuemap);
if (request != null)
{
mRequestID = request.RequestID;
Program.Session.sendRequest(request);
mEvent.WaitOne(); //Infinite waiting
}
else
{
Console.WriteLine("Cannot create request; probably some arguments are missing or incorrect");
}
}
void PrintResult()
{
if (mOrderMonitor != null)
{
OrderMonitor.ExecutionResult result = mOrderMonitor.Result;
ReadOnlyList<O2GTradeRow> trades;
ReadOnlyList<O2GClosedTradeRow> closedTrades;
O2GOrderRow order = mOrderMonitor.Order;
String orderID = order.OrderID;
trades = mOrderMonitor.Trades;
closedTrades = mOrderMonitor.ClosedTrades;
switch (result)
{
case OrderMonitor.ExecutionResult.Canceled:
if (trades.Count > 0)
{
PrintTrades(trades, orderID);
PrintClosedTrades(closedTrades, orderID);
Console.WriteLine("A part of the order has been canceled. Amount = {0}", mOrderMonitor.RejectAmount);
}
else
{
Console.WriteLine("The order: OrderID = {0} has been canceled.", orderID);
Console.WriteLine("The cancel amount = {0}.", mOrderMonitor.RejectAmount);
}
break;
case OrderMonitor.ExecutionResult.FullyRejected:
Console.WriteLine("The order has been rejected. OrderID = {0}", orderID);
Console.WriteLine("The rejected amount = {0}", mOrderMonitor.RejectAmount);
Console.WriteLine("Rejection cause: {0}", mOrderMonitor.RejectMessage);
break;
case OrderMonitor.ExecutionResult.PartialRejected:
PrintTrades(trades, orderID);
PrintClosedTrades(closedTrades, orderID);
Console.WriteLine("A part of the order has been rejected. Amount = {0}", mOrderMonitor.RejectAmount);
Console.WriteLine("Rejection cause: {0} ", mOrderMonitor.RejectMessage);
break;
case OrderMonitor.ExecutionResult.Executed:
PrintTrades(trades, orderID);
PrintClosedTrades(closedTrades, orderID);
break;
}
}
}
void PrintTrades(ReadOnlyList<O2GTradeRow> trades, string orderID)
{
if (trades.Count == 0)
return;
Console.WriteLine("For the order: OrderID = {0} the following positions have been opened:", orderID);
for (int i = 0; i < trades.Count; i++)
{
O2GTradeRow trade = trades[i];
String tradeID = trade.TradeID;
int amount = trade.Amount;
double rate = trade.OpenRate;
Console.WriteLine("Trade ID: {0}; Amount: {1}; Rate: {2}", tradeID, amount, rate);
}
}
void PrintClosedTrades(ReadOnlyList<O2GClosedTradeRow> closedTrades, string orderID)
{
if (closedTrades.Count == 0)
return;
Console.WriteLine("For the order: OrderID = {0} the following positions have been closed: ", orderID);
for (int i = 0; i < closedTrades.Count; i++)
{
O2GClosedTradeRow closedTrade = closedTrades[i];
String tradeID = closedTrade.TradeID;
int amount = closedTrade.Amount;
double rate = closedTrade.CloseRate;
Console.WriteLine("Closed Trade ID: {0}; Amount: {1}; Closed Rate: {2}", tradeID, amount, rate);
}
}
}
An example on how to subscribe the order monitor by using TableListener
can be studied below.
[hide]
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using fxcore2;
using OrdersMonitor;
namespace OpenPosition
{
class TableListener : IO2GTableListener
{
private EventWaitHandle mEvent = null;
private OrderMonitor mOrderMonitor = null;
private O2GSession mSession;
private O2GAccountsTable mAccounts;
private O2GOrdersTable mOrders;
private O2GTradesTable mTrades;
private O2GClosedTradesTable mClosedTrades;
private O2GMessagesTable mMessages;
private string mRequestID;
/// <summary>
/// ctor
/// </summary>
public TableListener(O2GSession Session)
{
mSession = Session;
mEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
}
/// <summary>
/// destructor
/// </summary>
~TableListener()
{
if (mEvent != null)
{
mEvent.Close();
mEvent = null;
}
}
/// <summary>
/// Wait for the event
/// </summary>
/// <param name="timeout"></param>
/// <returns></returns>
public bool WaitForEvent(int timeout)
{
return mEvent.WaitOne(timeout);
}
#region IO2GTableListener Members
public void onAdded(string rowID, O2GRow rowData)
{
O2GTableType type = rowData.TableType;
switch (type)
{
case O2GTableType.Orders:
O2GOrderRow orderRow = (O2GOrderRow)rowData;
if (mRequestID == orderRow.RequestID)
{
if ((OrderMonitor.IsClosingOrder(orderRow) || OrderMonitor.IsOpeningOrder(orderRow)) &&
mOrderMonitor == null)
{
Console.WriteLine("The order has been added. Order ID: {0}, Rate: {1}, Time In Force: {2}",
orderRow.OrderID,
orderRow.Rate,
orderRow.TimeInForce);
mOrderMonitor = new OrderMonitor(orderRow);
}
}
break;
case O2GTableType.Trades:
O2GTradeRow tradeRow = (O2GTradeRow)rowData;
if (mOrderMonitor != null)
{
mOrderMonitor.OnTradeAdded(tradeRow);
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
break;
case O2GTableType.ClosedTrades:
O2GClosedTradeRow closedTradeRow = (O2GClosedTradeRow)rowData;
if (mOrderMonitor != null)
{
mOrderMonitor.OnClosedTradeAdded(closedTradeRow);
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
break;
case O2GTableType.Messages:
O2GMessageRow messageRow = (O2GMessageRow)rowData;
if (mOrderMonitor != null)
{
mOrderMonitor.OnMessageAdded(messageRow);
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
break;
}
}
public void onChanged(string rowID, O2GRow rowData)
{
if (rowData.TableType == O2GTableType.Accounts)
{
O2GAccountTableRow account = (O2GAccountTableRow)rowData;
Console.WriteLine("Balance: {0}, Equity: {1}", account.Balance, account.Equity);
}
}
public void onDeleted(string rowID, O2GRow rowData)
{
if (rowData.TableType == O2GTableType.Orders)
{
O2GOrderRow orderRow = (O2GOrderRow)rowData;
if (mRequestID == orderRow.RequestID)
{
Console.WriteLine("The order has been deleted. Order ID: {0}", orderRow.OrderID);
mOrderMonitor.OnOrderDeleted(orderRow);
if (mOrderMonitor != null)
{
if (mOrderMonitor.IsOrderCompleted)
{
PrintResult();
mEvent.Set();
}
}
}
}
}
public void onStatusChanged(O2GTableStatus status)
{
}
#endregion
/// <summary>
/// Open a position
/// </summary>
public void CreateTrueMarketOrder(string sOfferID, string sAccountID, int iAmount, string sBuySell)
{
O2GRequestFactory factory = Program.Session.getRequestFactory();
O2GValueMap valuemap = factory.createValueMap();
valuemap.setString(O2GRequestParamsEnum.Command, Constants.Commands.CreateOrder);
valuemap.setString(O2GRequestParamsEnum.OrderType, Constants.Orders.TrueMarketOpen);
// The identifier of the account the order should be placed for.
valuemap.setString(O2GRequestParamsEnum.AccountID, sAccountID);
// The identifier of the instrument the order should be placed for.
valuemap.setString(O2GRequestParamsEnum.OfferID, sOfferID);
// The order direction: Constants.Sell for "Sell", Constants.Buy for "Buy".
valuemap.setString(O2GRequestParamsEnum.BuySell, sBuySell);
// The quantity of the instrument to be bought or sold.
valuemap.setInt(O2GRequestParamsEnum.Amount, iAmount);
// The custom identifier of the order.
valuemap.setString(O2GRequestParamsEnum.CustomID, "TrueMarketOrder");
O2GRequest request = factory.createOrderRequest(valuemap);
if (request != null)
{
mRequestID = request.RequestID;
SubscribeEvents();
Program.Session.sendRequest(request);
mEvent.WaitOne(); //Infinite waiting
Thread.Sleep(200); //wait account update receiving
UnsubscribeEvents();
}
else
{
Console.WriteLine("Cannot create request; probably some arguments are missing or incorrect");
}
}
public void CannotOpenPosition(string sError)
{
Console.WriteLine("Cannot open position, error: {0}", sError);
mEvent.Set();
}
void PrintResult()
{
if (mOrderMonitor != null)
{
OrderMonitor.ExecutionResult result = mOrderMonitor.Result;
ReadOnlyList<O2GTradeRow> trades;
ReadOnlyList<O2GClosedTradeRow> closedTrades;
O2GOrderRow order = mOrderMonitor.Order;
String orderID = order.OrderID;
trades = mOrderMonitor.Trades;
closedTrades = mOrderMonitor.ClosedTrades;
switch (result)
{
case OrderMonitor.ExecutionResult.Canceled:
if (trades.Count > 0)
{
PrintTrades(trades, orderID);
PrintClosedTrades(closedTrades, orderID);
Console.WriteLine("A part of the order has been canceled. Amount = {0}", mOrderMonitor.RejectAmount);
}
else
{
Console.WriteLine("The order: OrderID = {0} has been canceled.", orderID);
Console.WriteLine("The cancel amount = {0}.", mOrderMonitor.RejectAmount);
}
break;
case OrderMonitor.ExecutionResult.FullyRejected:
Console.WriteLine("The order has been rejected. OrderID = {0}", orderID);
Console.WriteLine("The rejected amount = {0}", mOrderMonitor.RejectAmount);
Console.WriteLine("Rejection cause: {0}", mOrderMonitor.RejectMessage);
break;
case OrderMonitor.ExecutionResult.PartialRejected:
PrintTrades(trades, orderID);
PrintClosedTrades(closedTrades, orderID);
Console.WriteLine("A part of the order has been rejected. Amount = {0}", mOrderMonitor.RejectAmount);
Console.WriteLine("Rejection cause: {0} ", mOrderMonitor.RejectMessage);
break;
case OrderMonitor.ExecutionResult.Executed:
PrintTrades(trades, orderID);
PrintClosedTrades(closedTrades, orderID);
break;
}
}
}
void PrintTrades(ReadOnlyList<O2GTradeRow> trades, string orderID)
{
if (trades.Count == 0)
return;
Console.WriteLine("For the order: OrderID = {0} the following positions have been opened:", orderID);
for (int i = 0; i < trades.Count; i++)
{
O2GTradeRow trade = trades[i];
String tradeID = trade.TradeID;
int amount = trade.Amount;
double rate = trade.OpenRate;
Console.WriteLine("Trade ID: {0}; Amount: {1}; Rate: {2}", tradeID, amount, rate);
}
}
void PrintClosedTrades(ReadOnlyList<O2GClosedTradeRow> closedTrades, string orderID)
{
if (closedTrades.Count == 0)
return;
Console.WriteLine("For the order: OrderID = {0} the following positions have been closed: ", orderID);
for (int i = 0; i < closedTrades.Count; i++)
{
O2GClosedTradeRow closedTrade = closedTrades[i];
String tradeID = closedTrade.TradeID;
int amount = closedTrade.Amount;
double rate = closedTrade.CloseRate;
Console.WriteLine("Closed Trade ID: {0}; Amount: {1}; Closed Rate: {2}", tradeID, amount, rate);
}
}
void SubscribeEvents()
{
O2GTableManager manager = mSession.getTableManager();
mOrders = (O2GOrdersTable)manager.getTable(O2GTableType.Orders);
mTrades = (O2GTradesTable)manager.getTable(O2GTableType.Trades);
mMessages = (O2GMessagesTable)manager.getTable(O2GTableType.Messages);
mClosedTrades = (O2GClosedTradesTable)manager.getTable(O2GTableType.ClosedTrades);
mAccounts = (O2GAccountsTable)manager.getTable(O2GTableType.Accounts);
mAccounts.subscribeUpdate(O2GTableUpdateType.Update, this);
mOrders.subscribeUpdate(O2GTableUpdateType.Insert, this);
mOrders.subscribeUpdate(O2GTableUpdateType.Delete, this);
mTrades.subscribeUpdate(O2GTableUpdateType.Insert, this);
mClosedTrades.subscribeUpdate(O2GTableUpdateType.Insert, this);
mMessages.subscribeUpdate(O2GTableUpdateType.Insert, this);
}
void UnsubscribeEvents()
{
if (mOrders != null)
{
mOrders.unsubscribeUpdate(O2GTableUpdateType.Insert, this);
mOrders.unsubscribeUpdate(O2GTableUpdateType.Delete, this);
}
if (mTrades != null)
{
mTrades.unsubscribeUpdate(O2GTableUpdateType.Insert, this);
}
if (mClosedTrades != null)
mClosedTrades.unsubscribeUpdate(O2GTableUpdateType.Insert, this);
if (mMessages != null)
mMessages.unsubscribeUpdate(O2GTableUpdateType.Insert, this);
if (mAccounts != null)
mAccounts.unsubscribeUpdate(O2GTableUpdateType.Update, this);
}
}
class ResponseListener : IO2GResponseListener
{
#region IO2GResponseListener Members
public void onRequestCompleted(string requestId, O2GResponse response)
{
}
public void onRequestFailed(string requestId, string error)
{
Program.CannotOpenPosition(error);
}
public void onTablesUpdates(O2GResponse data)
{
}
#endregion
}
}
back