Author |
Topic: OSO orders bug in COM API (13 messages, Page 1 of 1) |
||||
---|---|---|---|---|---|
Moderators: VPfau | |||||
WWatson2582 Posts: 43 Joined: May 03, 2018 |
I have encountered the following bug in the COM API (latest released version).
After submitting a OSO order using GFAPI()->Orders->SendOSOOrders() method, the two linked orders are submitted twice, or in other words, are each duplicated twice. After the bracket order is completed and the position would normally be again flat, the duplicate order causes an extra closing transaction to be executed, often leaving a non-flat position. I have created some sample code to demonstrate this using as a base your COM API sample application. All modifications are done to the file CppCOMSampleDlg.cpp. To demonstrate this bug: 1) Add the following global variable declaration to the top of the file: double lastPrice; 2) Change the CCppCOMSampleDlg::OnPriceChanged method as follows: void __stdcall CCppCOMSampleDlg::OnPriceChanged(GF_Api_COM::IGFComClientPtr client, GF_Api_COM::IContractPtr contract, GF_Api_COM::IPricePtr price) { lastPrice = price->LastPrice; if(m_ShowPriceUpdates) { log Symbol PriceToString(price->LastPrice) LastVol << std::endl; } } 3) Change the CCppCOMSampleDlg::OnBnClickedSendorder() as follows: void CCppCOMSampleDlg::OnBnClickedSendorder() { CheckConnectedGFAPI(); COrderDlg dlg(GFAPI()); bool orderError = false; if(dlg.DoModal() == IDOK) { GF_Api_COM::IOrderDraftBuilderPtr builder; builder.CreateInstance(__uuidof(GF_Api_COM::OrderDraftBuilder)); GF_Api_COM::IAccountListPtr accounts = GFAPI()->Accounts->Get(); GF_Api_COM::IAccountPtr account = accounts->GetAt(0); GF_Api_COM::IAccountIDPtr accountID = account->id; builder = builder ->WithAccountID(accountID) ->WithSide(dlg.m_Buy ? GF_Api_COM::OrderSide_Buy : GF_Api_COM::OrderSide_Sell) ->WithQuantity(dlg.m_Qty) ->WithContractID(GFAPI()->Contracts->Get_2((LPCSTR)dlg.m_SelectedContract)->id) ->WithOrderType((GF_Api_COM::OrderType)dlg.m_SelectedType) ->WithFlags(dlg.m_GTC ? GF_Api_COM::OrderFlags_GTC : GF_Api_COM::OrderFlags_None); double tickSize = GFAPI()->Contracts->Get_2((LPCSTR)dlg.m_SelectedContract)->tickSize; // To set up bracket order distances later GF_Api_COM::IOrderDraftPtr mainOrder = builder->Build(); if (GFAPI()->Orders->Drafts->Validate_2(mainOrder)->Count > 0) { log << "Main Order has invalid parts and will not be sent" << std::endl; orderError = true; } GF_Api_COM::IOrderDraftBuilderPtr target; target.CreateInstance(__uuidof(GF_Api_COM::OrderDraftBuilder)); target = target ->WithAccountID(accountID) ->WithSide(GF_Api_COM::OrderSide_Sell) ->WithQuantity(dlg.m_Qty) ->WithContractID(GFAPI()->Contracts->Get_2((LPCSTR)dlg.m_SelectedContract)->id) ->WithOrderType(GF_Api_COM::OrderType_Limit) ->WithFlags(dlg.m_GTC ? GF_Api_COM::OrderFlags_GTC : GF_Api_COM::OrderFlags_None) ->WithPrice(lastPrice + 15 * tickSize); GF_Api_COM::IOrderDraftPtr profitTarget = target->Build(); if (GFAPI()->Orders->Drafts->Validate_2(profitTarget)->Count > 0) { log << "Profit Target Order has invalid parts and will not be sent" << std::endl; orderError = true; } GF_Api_COM::IOrderDraftBuilderPtr loss; loss.CreateInstance(__uuidof(GF_Api_COM::OrderDraftBuilder)); loss = loss ->WithAccountID(accountID) ->WithSide(GF_Api_COM::OrderSide_Sell) ->WithQuantity(dlg.m_Qty) ->WithContractID(GFAPI()->Contracts->Get_2((LPCSTR)dlg.m_SelectedContract)->id) ->WithOrderType(GF_Api_COM::OrderType_Stop) ->WithFlags(dlg.m_GTC ? GF_Api_COM::OrderFlags_GTC : GF_Api_COM::OrderFlags_None) ->WithPrice(lastPrice - 15 * tickSize); GF_Api_COM::IOrderDraftPtr stopLoss = loss->Build(); if (GFAPI()->Orders->Drafts->Validate_2(stopLoss)->Count > 0) { log << "Stop Loss Order has invalid parts and will not be sent" << std::endl; orderError = true; } if (!orderError) { GF_Api_COM::IOrderListPtr orders = GFAPI()->Orders->SendOSOOrders(mainOrder, profitTarget, stopLoss, GF_Api_COM::OSOGroupingMethod_ByFirstPrice); if (!orders) log << "Cannot send OSO orders" << std::endl; else log << "OSO Orders have been sent. " << std::endl GetAt(0)->id->Value GetAt(0)->ToString << std::endl GetAt(1)->id->Value GetAt(1)->ToString << std::endl GetAt(2)->id->Value GetAt(2)->ToString << std::endl; } } } To demonstrate a bug with OSO orders after connecting to the GAIN server: 1) Subscribe to prices for a contract, e.g. ESH21 2) Click on the "Send Order" button in the main application dialog box. 3) Assure that the Order dialog box is completed with a Market Buy order for the same symbol subscribed to in step #1 An OSO bracket order will be constructed and submitted using a 15-tick Limit order and 15-tick Stop order. Rather than just the primary order and two linked orders submitted, you should see duplicate orders for each of the linked orders. |
||||
SPikalov Posts: 25 Joined: |
Hello.
I repeated your test and found no trouble. Please repeat the test and provide the account name and order IDs. Thanks, Sergey |
||||
WWatson2582 Posts: 43 Joined: May 03, 2018 |
We've done this multiple times using our own software calling GFAPI()->Orders->SendOSOOrders() as well as the sample COM API code (modified as given in the first message) always with the same result.
The account is mine ("WWatson2582") using the test server api.gainfutures.com. In my most recent test, the initial main order was 211862711 and the OCO bracket orders were 211862712 and 211862713. The additional incorrect orders were 211862714 and 211862715. The net effect of these extra orders is that when the bracket order is supposed to flatten the position, it is instead left short or long depending on which side of the bracket is filled (both orders on that side are filled simultaneously.) |
||||
WWatson2582 Posts: 43 Joined: May 03, 2018 |
I just heard that a customer of ours testing the COM API with our product had an bracket order go from LONG 3 to SHORT 3 because of this bug but fortunately caught it and were able to flatten their position manually. We are going to disable OSO orders until this is resolved, but this was a real money account so it appears the bug is not limited to the test server.
I would post screencaps of the entire transaction but don't see a way to do that here. |
||||
SPikalov Posts: 25 Joined: |
it’s not a bug it’s a feature :)
211862712 - this is a "fake" order that we use to group real orders 211862714 - this is a "real" or "sub" order that we send to an exchange. See order comment - ‘Sub order for 211862712’ Sub orders creation algorithm depends on the grouping method you choose. See GF.Servers.Msgs.Values.Orders. OSOGroupingMethod In your case, it was OSOGroupingMethod. ByFirstPrice 211862713 -> 211862715 The same situation. You can open the Trader app and submit OSO orders. I think after that you will understand the OSO algorithm more clearly |
||||
SPikalov Posts: 25 Joined: |
Sorry,
GF.Servers.Msgs.Values.Orders. OSOGroupingMethod => GF.Api.Values.Orders.OSOGroupingMethod |
||||
WWatson2582 Posts: 43 Joined: May 03, 2018 |
I will investigate this further. How does one specify the server to use with the Gain Trader app? My credentials don't seem to work with anything other than api.gainfutures.com
|
||||
WWatson2582 Posts: 43 Joined: May 03, 2018 |
Do I understand that you are saying that the "fake" orders are never being filled? When are they cancelled and why do we even see them on the client side of the API if they are not "real"?
Edited by WWatson2582 on Feb 11, 2021 09:53 AM |
||||
CMicciche902 Posts: 367 Joined: |
Details on the OSO grouping can be found here: https://gainfutures.com/gain-trader/additional-features/
Latest GAIN Trader Developer can be found at: https://api.gainfutures.com/WebAPI/api/Files/DownloadClientUpdateLast?brandId=0&clientTypeId=0&branchId=2 We don't have a server selector for public versions. GAIN Trader Developer points to api.gainfutures.com GAIN Trader Demo points to sim.gainfutures.com GAIN Trader (LIVE) points to prod.gainfutures.com If credentials need reset, I'll send an email with new password. Chris M
|
||||
WWatson2582 Posts: 43 Joined: May 03, 2018 |
It wouldn't let me log in, Chris, so I guess I need a credential reset.
|
||||
WWatson2582 Posts: 43 Joined: May 03, 2018 |
I'm not sure I understand why reporting non-existent orders to the client is a "feature", but I have worked around this by filtering out all orders whose "comments" field starts with "Sub order for" and things seem to be working fine on our end now. I know that according to your description I am technically filtering out the real order and holding on to the fake order, but for our purposes it works out and I see no way to otherwise distinguish real from fake.
So I guess my remaining question is: why are these fake orders coming through the API at all? What possible use could I on the client end have for them? Edited by WWatson2582 on Feb 19, 2021 09:51 AM |
||||
SPikalov Posts: 25 Joined: |
“I see no way to otherwise distinguish real from fake.”
See GF.Api.Orders.IOrder IOrder ParentOrder { get; } IReadOnlyList SubOrders { get; } or GF.Api.COM.Orders.IOrder IOrder ParentOrder { get; } IOrderList SubOrders { get; } “ So I guess my remaining question is: why are these fake orders coming through the API at all? What possible use could I on the client end have for them?” You can work with both parent orders and sub-orders. I.e. If you modify\cancel the parent order => sub orders will be modified\canceled corresponding way If you modify\cancel sub orders => the parent order will be modified\canceled corresponding way |
||||
WWatson2582 Posts: 43 Joined: May 03, 2018 |
We're working now fine, so you can consider this issue closed. Thanks!
|
||||