Orders

This notebook provides an example of

  • a MarketOrder
  • a simplyfied way for a MarketOrder by using contrib.requests.MarketOrderRequest
  • a LimitOrder with an expiry datetime by using GTD and contrib.requests.LimitOrderRequest
  • canceling a GTD order

create a marketorder request with a TakeProfit and a StopLoss order when it gets filled.


In [9]:
import json
import oandapyV20
import oandapyV20.endpoints.orders as orders
from exampleauth import exampleauth

accountID, access_token = exampleauth.exampleAuth()
client = oandapyV20.API(access_token=access_token)


# create a market order to enter a LONG position 10000 EUR_USD, stopLoss @1.07 takeProfit @1.10 ( current: 1.055)
# according to the docs at developer.oanda.com the requestbody looks like:

mktOrder = {
  "order": {
    "timeInForce": "FOK",      # Fill-or-kill
    "instrument": "EUR_USD",
    "positionFill": "DEFAULT",
    "type": "MARKET",
    "units": 10000,            # as integer
    "takeProfitOnFill": {
      "timeInForce": "GTC",    # Good-till-cancelled
      "price": 1.10            # as float
    },
    "stopLossOnFill": {
      "timeInForce": "GTC",
      "price": "1.07"          # as string
    }
  }
}
r = orders.OrderCreate(accountID=accountID, data=mktOrder)

print("Request: ", r)
print("MarketOrder specs: ", json.dumps(mktOrder, indent=2))


Request:  v3/accounts/101-004-1435156-001/orders
MarketOrder specs:  {
  "order": {
    "timeInForce": "FOK",
    "instrument": "EUR_USD",
    "stopLossOnFill": {
      "timeInForce": "GTC",
      "price": "1.07"
    },
    "positionFill": "DEFAULT",
    "units": 10000,
    "takeProfitOnFill": {
      "timeInForce": "GTC",
      "price": 1.1
    },
    "type": "MARKET"
  }
}

Well that looks fine, but constructing orderbodies that way is not really what we want. Types are not checked for instance and all the defaults need to be supplied.

This kind of datastructures can become complex, are not easy to read or construct and are prone to errors.

Types and definitions

Oanda uses several types and definitions througout their documentation. These types are covered by the oandapyV20.types package and the definitions by the oandapyV20.definitions package.

Contrib.requests

The oandapyV20.contrib.requests package offers classes providing an easy way to construct the data for the data parameter of the OrderCreate endpoint or the TradeCRCDO (Create/Replace/Cancel Dependent Orders). The oandapyV20.contrib.requests package makes use of the oandapyV20.types and oandapyV20.definitions.

Let's improve the previous example by making use of oandapyV20.contrib.requests:


In [1]:
import json
import oandapyV20
import oandapyV20.endpoints.orders as orders
from oandapyV20.contrib.requests import (
    MarketOrderRequest,
    TakeProfitDetails,
    StopLossDetails)
from exampleauth import exampleauth

accountID, access_token = exampleauth.exampleAuth()
client = oandapyV20.API(access_token=access_token)

# create a market order to enter a LONG position 10000 EUR_USD
mktOrder = MarketOrderRequest(instrument="EUR_USD",
                              units=10000,
                              takeProfitOnFill=TakeProfitDetails(price=1.10).data,
                              stopLossOnFill=StopLossDetails(price=1.07).data
                              ).data
r = orders.OrderCreate(accountID=accountID, data=mktOrder)

print("Request: ", r)
print("MarketOrder specs: ", json.dumps(mktOrder, indent=2))


Request:  v3/accounts/101-004-1435156-001/orders
MarketOrder specs:  {
  "order": {
    "timeInForce": "FOK",
    "instrument": "EUR_USD",
    "positionFill": "DEFAULT",
    "type": "MARKET",
    "units": "10000",
    "takeProfitOnFill": {
      "timeInForce": "GTC",
      "price": "1.10000"
    },
    "stopLossOnFill": {
      "timeInForce": "GTC",
      "price": "1.07000"
    }
  }
}

As you can see, the specs contain price values that were converted to strings and the defaults positionFill and timeInForce were added. Using contrib.requests makes it very easy to construct the orderdata body for order requests. Parameters for those requests are also validated.

Next step, place the order:


In [2]:
rv = client.request(r)
print("Response: {}\n{}".format(r.status_code, json.dumps(rv, indent=2)))


Response: 201
{
  "orderCancelTransaction": {
    "time": "2017-03-09T13:17:59.319422181Z",
    "userID": 1435156,
    "batchID": "7576",
    "orderID": "7576",
    "id": "7577",
    "type": "ORDER_CANCEL",
    "accountID": "101-004-1435156-001",
    "reason": "STOP_LOSS_ON_FILL_LOSS"
  },
  "lastTransactionID": "7577",
  "orderCreateTransaction": {
    "timeInForce": "FOK",
    "instrument": "EUR_USD",
    "batchID": "7576",
    "accountID": "101-004-1435156-001",
    "units": "10000",
    "takeProfitOnFill": {
      "timeInForce": "GTC",
      "price": "1.10000"
    },
    "time": "2017-03-09T13:17:59.319422181Z",
    "userID": 1435156,
    "positionFill": "DEFAULT",
    "id": "7576",
    "type": "MARKET_ORDER",
    "stopLossOnFill": {
      "timeInForce": "GTC",
      "price": "1.07000"
    },
    "reason": "CLIENT_ORDER"
  },
  "relatedTransactionIDs": [
    "7576",
    "7577"
  ]
}

Lets analyze that. We see an orderCancelTransaction and reason STOP_LOSS_ON_FILL_LOSS. So the order was not placed ? Well it was placed and cancelled right away. The marketprice of EUR_USD is at the moment of this writing 1.058. So the stopLoss order at 1.07 makes no sense. The status_code of 201 is as the specs say: http://developer.oanda.com/rest-live-v20/order-ep/ .

Lets change the stopLoss level below the current price and place the order once again.


In [3]:
mktOrder = MarketOrderRequest(instrument="EUR_USD",
                              units=10000,
                              takeProfitOnFill=TakeProfitDetails(price=1.10).data,
                              stopLossOnFill=StopLossDetails(price=1.05).data
                              ).data
r = orders.OrderCreate(accountID=accountID, data=mktOrder)
rv = client.request(r)

print("Response: {}\n{}".format(r.status_code, json.dumps(rv, indent=2)))


Response: 201
{
  "orderFillTransaction": {
    "accountBalance": "102107.4442",
    "instrument": "EUR_USD",
    "batchID": "7578",
    "pl": "0.0000",
    "accountID": "101-004-1435156-001",
    "units": "10000",
    "tradeOpened": {
      "tradeID": "7579",
      "units": "10000"
    },
    "financing": "0.0000",
    "price": "1.05563",
    "userID": 1435156,
    "orderID": "7578",
    "time": "2017-03-09T13:22:13.832587780Z",
    "id": "7579",
    "type": "ORDER_FILL",
    "reason": "MARKET_ORDER"
  },
  "lastTransactionID": "7581",
  "orderCreateTransaction": {
    "timeInForce": "FOK",
    "instrument": "EUR_USD",
    "batchID": "7578",
    "accountID": "101-004-1435156-001",
    "units": "10000",
    "takeProfitOnFill": {
      "timeInForce": "GTC",
      "price": "1.10000"
    },
    "time": "2017-03-09T13:22:13.832587780Z",
    "userID": 1435156,
    "positionFill": "DEFAULT",
    "id": "7578",
    "type": "MARKET_ORDER",
    "stopLossOnFill": {
      "timeInForce": "GTC",
      "price": "1.05000"
    },
    "reason": "CLIENT_ORDER"
  },
  "relatedTransactionIDs": [
    "7578",
    "7579",
    "7580",
    "7581"
  ]
}

We now see an orderFillTransaction for 10000 units EUR_USD with reason MARKET_ORDER.

Lets retrieve the orders. We should see the stopLoss and takeProfit orders as pending:


In [4]:
r = orders.OrdersPending(accountID=accountID)
rv = client.request(r)
print("Response:\n", json.dumps(rv, indent=2))


Response:
 {
  "lastTransactionID": "7581",
  "orders": [
    {
      "createTime": "2017-03-09T13:22:13.832587780Z",
      "triggerCondition": "TRIGGER_DEFAULT",
      "timeInForce": "GTC",
      "price": "1.05000",
      "tradeID": "7579",
      "id": "7581",
      "state": "PENDING",
      "type": "STOP_LOSS"
    },
    {
      "createTime": "2017-03-09T13:22:13.832587780Z",
      "triggerCondition": "TRIGGER_DEFAULT",
      "timeInForce": "GTC",
      "price": "1.10000",
      "tradeID": "7579",
      "id": "7580",
      "state": "PENDING",
      "type": "TAKE_PROFIT"
    },
    {
      "createTime": "2017-03-09T11:45:48.928448770Z",
      "triggerCondition": "TRIGGER_DEFAULT",
      "timeInForce": "GTC",
      "price": "1.05000",
      "tradeID": "7572",
      "id": "7574",
      "state": "PENDING",
      "type": "STOP_LOSS"
    },
    {
      "createTime": "2017-03-07T09:18:51.563637768Z",
      "triggerCondition": "TRIGGER_DEFAULT",
      "timeInForce": "GTC",
      "price": "1.05000",
      "tradeID": "7562",
      "id": "7564",
      "state": "PENDING",
      "type": "STOP_LOSS"
    },
    {
      "createTime": "2017-03-07T09:08:04.219010730Z",
      "triggerCondition": "TRIGGER_DEFAULT",
      "timeInForce": "GTC",
      "price": "1.05000",
      "tradeID": "7558",
      "id": "7560",
      "state": "PENDING",
      "type": "STOP_LOSS"
    }
  ]
}

Depending on the state of your account you should see at least the orders associated with the previously executed marketorder. The relatedTransactionIDs should be in the orders output of OrdersPending().

Now lets cancel all pending TAKE_PROFIT orders:


In [5]:
r = orders.OrdersPending(accountID=accountID)
rv = client.request(r)
idsToCancel = [order.get('id') for order in rv['orders'] if order.get('type') == "TAKE_PROFIT"]
for orderID in idsToCancel:
    r = orders.OrderCancel(accountID=accountID, orderID=orderID)
    rv = client.request(r)
    print("Request: {} ... response: {}".format(r, json.dumps(rv, indent=2)))


Request: v3/accounts/101-004-1435156-001/orders/7580/cancel ... response: {
  "orderCancelTransaction": {
    "time": "2017-03-09T13:26:07.480994423Z",
    "userID": 1435156,
    "batchID": "7582",
    "orderID": "7580",
    "id": "7582",
    "type": "ORDER_CANCEL",
    "accountID": "101-004-1435156-001",
    "reason": "CLIENT_REQUEST"
  },
  "lastTransactionID": "7582",
  "relatedTransactionIDs": [
    "7582"
  ]
}

create a LimitOrder with a GTD "good-til-date"

Create a LimitOrder and let it expire: 2018-07-02T00:00:00 using GTD. Make sure it is in the future when you run this example!


In [12]:
from oandapyV20.contrib.requests import LimitOrderRequest

# make sure GTD_TIME is in the future 
# also make sure the price condition is not met
# and specify GTD_TIME as UTC or local
# GTD_TIME="2018-07-02T00:00:00Z" # UTC
GTD_TIME="2018-07-02T00:00:00"
ordr = LimitOrderRequest(instrument="EUR_USD",
                          units=10000,
                          timeInForce="GTD",
                          gtdTime=GTD_TIME,
                          price=1.08)
print(json.dumps(ordr.data, indent=4))
r = orders.OrderCreate(accountID=accountID, data=ordr.data)
rv = client.request(r)
print(json.dumps(rv, indent=2))


{
    "order": {
        "price": "1.08000",
        "timeInForce": "GTD",
        "positionFill": "DEFAULT",
        "type": "LIMIT",
        "instrument": "EUR_USD",
        "gtdTime": "2018-07-02T00:00:00",
        "units": "10000"
    }
}
{
  "relatedTransactionIDs": [
    "8923"
  ],
  "lastTransactionID": "8923",
  "orderCreateTransaction": {
    "price": "1.08000",
    "triggerCondition": "DEFAULT",
    "positionFill": "DEFAULT",
    "type": "LIMIT_ORDER",
    "requestID": "42440345970496965",
    "partialFill": "DEFAULT",
    "gtdTime": "2018-07-02T04:00:00.000000000Z",
    "batchID": "8923",
    "id": "8923",
    "userID": 1435156,
    "accountID": "101-004-1435156-001",
    "timeInForce": "GTD",
    "reason": "CLIENT_ORDER",
    "instrument": "EUR_USD",
    "time": "2018-06-10T12:06:30.259079220Z",
    "units": "10000"
  }
}

Request the pending orders


In [13]:
r = orders.OrdersPending(accountID=accountID)
rv = client.request(r)
print(json.dumps(rv, indent=2))


{
  "orders": [
    {
      "price": "1.08000",
      "triggerCondition": "DEFAULT",
      "state": "PENDING",
      "positionFill": "DEFAULT",
      "partialFill": "DEFAULT_FILL",
      "gtdTime": "2018-07-02T04:00:00.000000000Z",
      "id": "8923",
      "timeInForce": "GTD",
      "type": "LIMIT",
      "instrument": "EUR_USD",
      "createTime": "2018-06-10T12:06:30.259079220Z",
      "units": "10000"
    }
  ],
  "lastTransactionID": "8923"
}

Cancel the GTD order

Fetch the orderID from the pending orders and cancel the order.


In [14]:
r = orders.OrderCancel(accountID=accountID, orderID=8923)
rv = client.request(r)
print(json.dumps(rv, indent=2))


{
  "relatedTransactionIDs": [
    "8924"
  ],
  "orderCancelTransaction": {
    "accountID": "101-004-1435156-001",
    "time": "2018-06-10T12:07:35.453416669Z",
    "orderID": "8923",
    "reason": "CLIENT_REQUEST",
    "requestID": "42440346243149289",
    "type": "ORDER_CANCEL",
    "batchID": "8924",
    "id": "8924",
    "userID": 1435156
  },
  "lastTransactionID": "8924"
}

Request pendig orders once again ... the 8923 should be gone


In [15]:
r = orders.OrdersPending(accountID=accountID)
rv = client.request(r)
print(json.dumps(rv, indent=2))


{
  "orders": [],
  "lastTransactionID": "8924"
}