Repository: hootnot/oanda-api-v20 Branch: master Commit: fe8a101cbcf9 Files: 187 Total size: 481.8 KB Directory structure: gitextract_zbddjf3w/ ├── .gitignore ├── .landscape.yml ├── .travis.yml ├── CHANGELOG.rst ├── LICENSE.md ├── MANIFEST.in ├── README.rst ├── docs/ │ ├── Makefile │ ├── conf.py │ ├── contrib/ │ │ ├── factories/ │ │ │ └── instrumentscandlesfactory.rst │ │ ├── factories.rst │ │ ├── generic/ │ │ │ └── generic.rst │ │ ├── generic.rst │ │ ├── orders/ │ │ │ ├── limitorderrequest.rst │ │ │ ├── marketorderrequest.rst │ │ │ ├── mitorderrequest.rst │ │ │ ├── positionscloserequest.rst │ │ │ ├── stoplossorderrequest.rst │ │ │ ├── stoporderrequest.rst │ │ │ ├── takeprofitorderrequest.rst │ │ │ ├── tradecloserequest.rst │ │ │ └── trailingstoplossorderrequest.rst │ │ ├── orders.rst │ │ ├── support/ │ │ │ ├── clientextensions.rst │ │ │ ├── stoplossdetails.rst │ │ │ ├── takeprofitdetails.rst │ │ │ └── trailingstoplossdetails.rst │ │ └── support.rst │ ├── dirstruct.py │ ├── endpoints/ │ │ ├── accounts/ │ │ │ ├── accountchanges.rst │ │ │ ├── accountconfiguration.rst │ │ │ ├── accountdetails.rst │ │ │ ├── accountinstruments.rst │ │ │ ├── accountlist.rst │ │ │ └── accountsummary.rst │ │ ├── accounts.rst │ │ ├── forexlabs/ │ │ │ ├── autochartist.rst │ │ │ ├── calendar.rst │ │ │ ├── commitmentsoftraders.rst │ │ │ ├── historicalpositionratios.rst │ │ │ ├── orderbookdata.rst │ │ │ └── spreads.rst │ │ ├── forexlabs.rst │ │ ├── instruments/ │ │ │ ├── instrumentlist.rst │ │ │ ├── instrumentorderbook.rst │ │ │ └── instrumentpositionbook.rst │ │ ├── instruments.rst │ │ ├── orders/ │ │ │ ├── ordercancel.rst │ │ │ ├── orderclientextensions.rst │ │ │ ├── ordercreate.rst │ │ │ ├── orderdetails.rst │ │ │ ├── orderlist.rst │ │ │ ├── orderreplace.rst │ │ │ └── orderspending.rst │ │ ├── orders.rst │ │ ├── positions/ │ │ │ ├── openpositions.rst │ │ │ ├── positionclose.rst │ │ │ ├── positiondetails.rst │ │ │ └── positionlist.rst │ │ ├── positions.rst │ │ ├── pricing/ │ │ │ ├── pricinginfo.rst │ │ │ └── pricingstream.rst │ │ ├── pricing.rst │ │ ├── trades/ │ │ │ ├── opentrades.rst │ │ │ ├── tradeCRCDO.rst │ │ │ ├── tradeclientextensions.rst │ │ │ ├── tradeclose.rst │ │ │ ├── tradedetails.rst │ │ │ └── tradeslist.rst │ │ ├── trades.rst │ │ ├── transactions/ │ │ │ ├── transactiondetails.rst │ │ │ ├── transactionidrange.rst │ │ │ ├── transactionlist.rst │ │ │ ├── transactionssinceid.rst │ │ │ └── transactionsstream.rst │ │ └── transactions.rst │ ├── examples.rst │ ├── index.rst │ ├── installation.rst │ ├── oanda-api-v20.rst │ ├── oandapyV20.contrib.rst │ ├── oandapyV20.definitions.accounts.rst │ ├── oandapyV20.definitions.instruments.rst │ ├── oandapyV20.definitions.orders.rst │ ├── oandapyV20.definitions.pricing.rst │ ├── oandapyV20.definitions.rst │ ├── oandapyV20.definitions.trades.rst │ ├── oandapyV20.definitions.transactions.rst │ ├── oandapyV20.endpoints.rst │ ├── oandapyV20.types.rst │ └── types/ │ ├── AccountID.rst │ ├── AccountUnits.rst │ ├── ClientComment.rst │ ├── ClientID.rst │ ├── ClientTag.rst │ ├── DateTime.rst │ ├── OrderID.rst │ ├── OrderIdentifier.rst │ ├── OrderSpecifier.rst │ ├── PriceValue.rst │ ├── TradeID.rst │ └── Units.rst ├── examples/ │ └── README.rst ├── jupyter/ │ ├── account.txt │ ├── accounts.ipynb │ ├── exampleauth/ │ │ ├── __init__.py │ │ └── exampleauth.py │ ├── exceptions.ipynb │ ├── historical.ipynb │ ├── index.ipynb │ ├── orders.ipynb │ ├── positions.ipynb │ ├── streams.ipynb │ ├── token.txt │ └── trades.ipynb ├── oandapyV20/ │ ├── __init__.py │ ├── contrib/ │ │ ├── __init__.py │ │ ├── factories/ │ │ │ ├── __init__.py │ │ │ └── history.py │ │ ├── generic.py │ │ └── requests/ │ │ ├── __init__.py │ │ ├── baserequest.py │ │ ├── extensions.py │ │ ├── limitorder.py │ │ ├── marketorder.py │ │ ├── mitorder.py │ │ ├── onfill.py │ │ ├── positionclose.py │ │ ├── stoplossorder.py │ │ ├── stoporder.py │ │ ├── takeprofitorder.py │ │ ├── tradeclose.py │ │ └── trailingstoplossorder.py │ ├── definitions/ │ │ ├── __init__.py │ │ ├── accounts.py │ │ ├── instruments.py │ │ ├── orders.py │ │ ├── pricing.py │ │ ├── primitives.py │ │ ├── trades.py │ │ └── transactions.py │ ├── endpoints/ │ │ ├── __init__.py │ │ ├── accounts.py │ │ ├── apirequest.py │ │ ├── decorators.py │ │ ├── forexlabs.py │ │ ├── instruments.py │ │ ├── orders.py │ │ ├── positions.py │ │ ├── pricing.py │ │ ├── responses/ │ │ │ ├── __init__.py │ │ │ ├── accounts.py │ │ │ ├── forexlabs.py │ │ │ ├── instruments.py │ │ │ ├── orders.py │ │ │ ├── positions.py │ │ │ ├── pricing.py │ │ │ ├── trades.py │ │ │ └── transactions.py │ │ ├── trades.py │ │ └── transactions.py │ ├── exceptions.py │ ├── oandapyV20.py │ └── types/ │ ├── __init__.py │ └── types.py ├── readthedocs.yml ├── requirements.txt ├── setup.py └── tests/ ├── __init__.py ├── account.txt ├── test_accounts.py ├── test_contrib_factories.py ├── test_contrib_generic.py ├── test_contrib_orders.py ├── test_decorators.py ├── test_definitions.py ├── test_forexlabs.py ├── test_instruments.py ├── test_oandapyv20.py ├── test_orders.py ├── test_positions.py ├── test_pricing.py ├── test_trades.py ├── test_transactions.py ├── test_types.py ├── token.txt └── unittestsetup.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ ================================================ FILE: .landscape.yml ================================================ inherits: [flake8] ================================================ FILE: .travis.yml ================================================ language: python python: - '3.6' - '3.7' - '3.8' - '3.9' install: - pip install --upgrade pip setuptools - pip install requests-mock - pip install coveralls - pip install nose nose-parameterized - pip install wheel - pip install twine script: - coverage run --source=oandapyV20 setup.py test after_success: - coveralls deploy: provider: pypi user: hootnot password: secure: Jr+/eYSrysSQcSaC8gW5TJak3AycoMlPe3QfOE/CV7n9RYn5JsXudUhWTFiC4c78CAGu14F3Iu6TQPiC0CZrWc04Gi9H1xnWniAXRsr8xKiSfyL1zpHRoBmP6zbdOGc6zk8Jjit1YUNSdp2W/YA7Ii1BwwLQ/h+er8IqZxoGNs3kh51G/2dYOU085VELbz9S1um27e+oJuAOurrfZiPGdjnnUVALfQcuRrEWRS3Cc+1LDiKbHWD1dBlVvf71okyBAd2gnT+bBjCpKiBmjHzEnRO2mdjPBA+2lOI8EOr0E3Ueb/9q3ID3H9UPnb2ZL9VhqxutLVB0fEbHpiAPcG7MPwKN8EWTDIPDGUP9/DEUDX0qV0eybP8yl/5+2l4np0a+cqSkHJfeLZwxixaOs02qP6SMScxfsf+N6BhlIKCpNnESkpsdESlkgG0lxkoJVOR8Pf0otTd0lIjLl/Iy4I7SoRwFNHiUg1naQHIVvbFdsYeM0KZQUQtLEUN/pcjZkRJsHkZHZNo/KSB27xECbPUcrzBorMc8EL1s9H51q8HhhDufnAel3Y0IW3aMR4cN3gX0H+udEEbWgeVf7p7xZESJgmntYoHWICpzK2uNvxJZ2+gkJQKPL4fERSskjo+7UUf6mwi32HNk0oAOyGDGxtgbCEyZ0GazZLzFYAIsiR1Uj88= on: tags: true distributions: sdist bdist_wheel repo: hootnot/oanda-api-v20 ================================================ FILE: CHANGELOG.rst ================================================ Changelog ========= [Unreleased] ------------ v0.7.2 (2021-08-26) ------------------- Documentation Changes ~~~~~~~~~~~~~~~~~~~~~ - [endpoints] fix -> trades.TradeDetails v0.7.1 (2021-08-15) ------------------- Bug Fixes ~~~~~~~~~ - [types] make Units handle up to 2 decimals (was 1) v0.7.0 (2021-05-25) ------------------- Bug Fixes ~~~~~~~~~ - [test] test_orders: fix fail python 3.9 - [types] make Units handle up to 1 decimal Documentation Changes ~~~~~~~~~~~~~~~~~~~~~ - using the with context Administration and Chores ~~~~~~~~~~~~~~~~~~~~~~~~~ - rename CHANGES.txt -> CHANGELOG.rst - [versions] support Python 3.5 - 3.9 Version 0.6.3 ------------- 2018/04/12 update of definitions according v20-openapi release 3.0.22 Version 0.6.2 ------------- 2018/04/07 granularity_to_time now also handles weekly 'W' as parameter, see PR #119. Tests added. Version 0.6.1 ------------- 2018/03/31 InstrumentsCandlesFactory fix, incorrect handling of parameters in the rare case one would not specify the 'from' parameter, see PR #117 Version 0.6.0 ------------- 2018/03/07 support added for the (legacy) forexlabs endpoints, #110. See developer.oanda.com for details. Version 0.5.0 ------------- 2018/02/18 update various definitions as OANDA added these to their docs Version 0.4.5 ------------- 2017/09/25 force 'includeFirst' in InstrumentsCandlesFactory, #100 Version 0.4.4 ------------- 2017/09/23 fix bug in InstrumentsCandlesFactory skipping the last request, #97 Version 0.4.3 ------------- 2017/08/21 fix missing gtdTime in LimitOrderRequest, #94 Version 0.4.2 ------------- 2017/07/24 fix possible 'date is in the future' error when retrieving candledata using InstrumentsCandlesFactory Version 0.4.1 ------------- 2017/07/11: bugfix, see pr #85 Version 0.4.0 ------------- 2017/07/04: contrib.factories added, pr #81: InstrumentsCandlesFactory to generate InstrumentsCandles requests Version 0.3.0 ------------- 2017/06/28: Recently released extra instruments endpoints added, pr #77: /v3/instruments/{instrument}/orderBook /v3/instruments/{instrument}/positionBook Version 0.2.4 ------------- 2017/05/15: documentation fix regarding incorrect output references of AccountList and AccountDetails, #pr 73 Version 0.2.3 ------------- 2017/04/17: fix: trades.TradesList unhandled params, issue #69 2017/03/09: jupyter notebooks added 2017/02/08: Python 3.6 added 2017/01/30: datetime name conflict solved, issue #62 2016/12/05: documentation: dynamically generated types directory tree types: DateTime subsecond part fix 2016/12/05: fixes OrderSpecifier 2016/12/01: extend types with DateTime 2016/11/17: bug streaming prices: list values need to be joined, solved #50 Version 0.2.2 ------------- 2016/11/17: extend types with AccountID definitions increase coverage contrib.request classes timeInForce parameter add and/or verify against allowed values Version 0.2.1 ------------- 2016/11/15: * documentation updates * missing requirement: six added Version 0.2.0 ------------- 2016/11/15: * first release with coverage of all endpoints except the 'forexlabs' * definitions covered as in the development documeintation * types representing data types as in the development documeintation * contrib.requests: classes to construct data for requestbodies ================================================ FILE: LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2016 Feite Brekeveld Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: MANIFEST.in ================================================ include requirements.txt include LICENSE.md ================================================ FILE: README.rst ================================================ OANDA REST-V20 API wrapper ========================== .. _Top: As of march 2018 OANDA no longer supports the v1 REST-API. The only pending V20 endpoint was the *forexlabs endpoint*. Instead of launching *forexlabs* as a *V20-endpoint*, OANDA choose to support this endpoint from the v1 REST interface, see: http://developer.oanda.com/rest-live-v20/forexlabs-ep/. .. image:: https://travis-ci.org/hootnot/oanda-api-v20.svg?branch=master :target: https://travis-ci.org/hootnot/oanda-api-v20 :alt: Build .. image:: https://readthedocs.org/projects/oanda-api-v20/badge/?version=latest :target: http://oanda-api-v20.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://landscape.io/github/hootnot/oanda-api-v20/master/landscape.svg?style=flat :target: https://landscape.io/github/hootnot/oanda-api-v20/master :alt: Code Health .. image:: https://coveralls.io/repos/github/hootnot/oanda-api-v20/badge.svg?branch=master :target: https://coveralls.io/github/hootnot/oanda-api-v20?branch=master :alt: Coverage .. image:: https://badge.fury.io/py/oandapyV20.svg :target: https://badge.fury.io/py/oandapyV20 :alt: Pypi .. image:: https://img.shields.io/pypi/pyversions/oandapyV20.svg :target: https://pypi.org/project/oandapyV20 :alt: Python versions .. image:: https://api.codacy.com/project/badge/Grade/5946514e3a7c407291f76e630ce3553b :target: https://www.codacy.com/app/hootnot/oandaapiv20utm_source=github.com&utm_medium=referral&utm_content=hootnot/oanda-api-v20&utm_campaign=Badge_Grade :alt: Codacy Interactive ----------- .. image:: https://jupyter.readthedocs.io/en/latest/_static/_images/jupyter.svg :target: ./jupyter :alt: Jupyter Using the Jupyter `notebook`_ it is easy to play with the *oandapyV20* library. .. _notebook: ./jupyter/index.ipynb TOC --- + `Install`_ + `Design`_ + `Client`_ - `contrib.requests`_ - `contrib.factories`_ - `API-endpoint access`_ - `Placing a MarketOrder with TakeProfitOrder and StopLossOrder`_ - `Processing series of requests`_ - `Streaming endpoints`_ Install ------- .. code-block:: bash $ pip install oandapyV20 or the latest development version from github: .. code-block:: bash $ pip install git+https://github.com/hootnot/oanda-api-v20.git If you want to run the tests, clone the repository: .. code-block:: bash $ git clone https://github.com/hootnot/oanda-api-v20 $ cd oanda-api-v20 # install necessary packages for testing $ grep "\- pip install" .travis.yml | > while read LNE > do `echo $LNE| cut -c2-` ; done $ python setup.py test $ python setup.py install Examples are provided in the https://github.com/hootnot/oandapyV20-examples repository. Design ------ In the V20-library endpoints are represented as APIRequest objects derived from the APIRequest base class. Each endpoint group (accounts, trades, etc.) is represented by it's own (abstract) class covering the functionality of all endpoints for that group. Each endpoint within that group is covered by a class derived from the abstract class. Top_ Client ~~~~~~ The V20-library has a client class (API) which processes APIRequest objects. Top_ contrib.requests ~~~~~~~~~~~~~~~~ The contrib.request 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). .. code-block:: python mktOrder = MarketOrderRequest(instrument="EUR_USD", units=10000, takeProfitOnFill=TakeProfitDetails(price=1.10).data, stopLossOnFill=StopLossDetails(price=1.07).data ).data instead of: .. code-block:: python mktOrder = {'order': { 'timeInForce': 'FOK', 'instrument': 'EUR_USD', 'positionFill': 'DEFAULT', 'units': '10000', 'type': 'MARKET', 'takeProfitOnFill': { 'timeInForce': 'GTC', 'price': '1.10000'} } 'stopLossOnFill': { 'timeInForce': 'GTC', 'price': '1.07000'} } } Top_ contrib.factories ~~~~~~~~~~~~~~~~~ The contrib.factories module offers classes providing an easy way generate requests. Downloading historical data is limited to 5000 records per request. This means that you have to make consecutive requests with change of parameters if you want more than 5000 records. The *InstrumentsCandlesFactory* solves this by generating the requests for you, example: .. code-block:: python import sys import json from oandapyV20.contrib.factories import InstrumentsCandlesFactory from oandapyV20 import API access_token = "..." client = API(access_token=access_token) _from = sys.argv[1] _to = sys.argv[2] gran = sys.argv[3] instr = sys.argv[4] params = { "granularity": gran, "from": _from, "to": _to } def cnv(r, h): for candle in r.get('candles'): ctime = candle.get('time')[0:19] try: rec = "{time},{complete},{o},{h},{l},{c},{v}".format( time=ctime, complete=candle['complete'], o=candle['mid']['o'], h=candle['mid']['h'], l=candle['mid']['l'], c=candle['mid']['c'], v=candle['volume'], ) except Exception as e: print(e, r) else: h.write(rec+"\n") with open("/tmp/{}.{}.out".format(instr, gran), "w") as O: for r in InstrumentsCandlesFactory(instrument=instr, params=params): print("REQUEST: {} {} {}".format(r, r.__class__.__name__, r.params)) rv = client.request(r) cnv(r.response, O) When running this: .. code-block:: shell $ python oandahist.py 2017-01-01T00:00:00Z 2017-06-30T00:00:00Z H4 EUR_USD REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-25T08:00:00Z', 'from': '2017-01-01T00:00:00Z', 'granularity': 'H4'} REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-16T20:00:00Z', 'from': '2017-03-25T12:00:00Z', 'granularity': 'H4'} REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-30T00:00:00Z', 'from': '2017-06-17T00:00:00Z', 'granularity': 'H4'} The output shows it processed three *InstrumentsCandles* requests. The data can be found in */tmp/EUR_USD.H4.out*: .. code-block:: shell $ tail /tmp/EUR_USD.H4.out ... 2017-06-28T01:00:0,True,1.13397,1.13557,1.13372,1.13468,1534 2017-06-28T05:00:0,True,1.13465,1.13882,1.13454,1.13603,8486 2017-06-28T09:00:0,True,1.13606,1.13802,1.12918,1.13315,12815 2017-06-28T13:00:0,True,1.13317,1.13909,1.13283,1.13781,13255 2017-06-28T17:00:0,True,1.13783,1.13852,1.13736,1.13771,2104 2017-06-28T21:00:0,True,1.13789,1.13894,1.13747,1.13874,1454 Top_ Examples -------- API-endpoint access ~~~~~~~~~~~~~~~~~~~ .. code-block:: python import json from oandapyV20 import API # the client import oandapyV20.endpoints.trades as trades access_token = "..." accountID = "..." client = API(access_token=access_token) # request trades list r = trades.TradesList(accountID) rv = client.request(r) print("RESPONSE:\n{}".format(json.dumps(rv, indent=2))) Top_ Placing a *MarketOrder* with *TakeProfitOrder* and *StopLossOrder* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python import json from oandapyV20.contrib.requests import MarketOrderRequest from oandapyV20.contrib.requests import TakeProfitDetails, StopLossDetails import oandapyV20.endpoints.orders as orders import oandapyV20 from exampleauth import exampleAuth accountID, access_token = exampleAuth() api = oandapyV20.API(access_token=access_token) # EUR_USD (today 1.0750) EUR_USD_STOP_LOSS = 1.07 EUR_USD_TAKE_PROFIT = 1.10 mktOrder = MarketOrderRequest( instrument="EUR_USD", units=10000, takeProfitOnFill=TakeProfitDetails(price=EUR_USD_TAKE_PROFIT).data, stopLossOnFill=StopLossDetails(price=EUR_USD_STOP_LOSS).data) # create the OrderCreate request r = orders.OrderCreate(accountID, data=mktOrder.data) try: # create the OrderCreate request rv = api.request(r) except oandapyV20.exceptions.V20Error as err: print(r.status_code, err) else: print(json.dumps(rv, indent=2)) Top_ Processing series of requests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Processing series of requests is also possible now by storing different requests in an array or from some 'request-factory' class. Below an array example: .. code-block:: python import json from oandapyV20 import API # the client from oandapyV20.exceptions import V20Error import oandapyV20.endpoints.accounts as accounts import oandapyV20.endpoints.trades as trades import oandapyV20.endpoints.pricing as pricing access_token = "..." accountID = "..." client = API(access_token=access_token) # list of requests lor = [] # request trades list lor.append(trades.TradesList(accountID)) # request accounts list lor.append(accounts.AccountList()) # request pricing info params={"instruments": "DE30_EUR,EUR_GBP"} lor.append(pricing.PricingInfo(accountID, params=params)) for r in lor: try: rv = client.request(r) # put request and response in 1 JSON structure print("{}".format(json.dumps({"request": "{}".format(r), "response": rv}, indent=2))) except V20Error as e: print("OOPS: {:d} {:s}".format(e.code, e.msg)) Output `````` .. code-block:: json { "request": "v3/accounts/101-004-1435156-001/trades", "response": { "lastTransactionID": "1109", "trades": [ { "unrealizedPL": "23.0000", "financing": "-0.5556", "state": "OPEN", "price": "10159.4", "realizedPL": "0.0000", "currentUnits": "-10", "openTime": "2016-07-22T16:47:04.315211198Z", "initialUnits": "-10", "instrument": "DE30_EUR", "id": "1105" }, { "unrealizedPL": "23.0000", "financing": "-0.5556", "state": "OPEN", "price": "10159.4", "realizedPL": "0.0000", "currentUnits": "-10", "openTime": "2016-07-22T16:47:04.141436468Z", "initialUnits": "-10", "instrument": "DE30_EUR", "id": "1103" } ] } } { "request": "v3/accounts", "response": { "accounts": [ { "tags": [], "id": "101-004-1435156-002" }, { "tags": [], "id": "101-004-1435156-001" } ] } } { "request": "v3/accounts/101-004-1435156-001/pricing", "response": { "prices": [ { "status": "tradeable", "quoteHomeConversionFactors": { "negativeUnits": "1.00000000", "positiveUnits": "1.00000000" }, "asks": [ { "price": "10295.1", "liquidity": 25 }, { "price": "10295.3", "liquidity": 75 }, { "price": "10295.5", "liquidity": 150 } ], "unitsAvailable": { "default": { "short": "60", "long": "100" }, "reduceOnly": { "short": "0", "long": "20" }, "openOnly": { "short": "60", "long": "0" }, "reduceFirst": { "short": "60", "long": "100" } }, "closeoutBid": "10293.5", "bids": [ { "price": "10293.9", "liquidity": 25 }, { "price": "10293.7", "liquidity": 75 }, { "price": "10293.5", "liquidity": 150 } ], "instrument": "DE30_EUR", "time": "2016-09-29T17:07:19.598030528Z", "closeoutAsk": "10295.5" }, { "status": "tradeable", "quoteHomeConversionFactors": { "negativeUnits": "1.15679152", "positiveUnits": "1.15659083" }, "asks": [ { "price": "0.86461", "liquidity": 1000000 }, { "price": "0.86462", "liquidity": 2000000 }, { "price": "0.86463", "liquidity": 5000000 }, { "price": "0.86465", "liquidity": 10000000 } ], "unitsAvailable": { "default": { "short": "624261", "long": "624045" }, "reduceOnly": { "short": "0", "long": "0" }, "openOnly": { "short": "624261", "long": "624045" }, "reduceFirst": { "short": "624261", "long": "624045" } }, "closeoutBid": "0.86442", "bids": [ { "price": "0.86446", "liquidity": 1000000 }, { "price": "0.86445", "liquidity": 2000000 }, { "price": "0.86444", "liquidity": 5000000 }, { "price": "0.86442", "liquidity": 10000000 } ], "instrument": "EUR_GBP", "time": "2016-09-29T17:07:19.994271769Z", "closeoutAsk": "0.86465", "type": "PRICE" } ] } } Top_ Streaming endpoints ~~~~~~~~~~~~~~~~~~~ Streaming quotes: use pricing.PricingStream. Streaming transactions: use transactions.TransactionsEvents. To fetch streaming data from a stream use the following pattern: .. code-block:: python import json from oandapyV20 import API from oandapyV20.exceptions import V20Error from oandapyV20.endpoints.pricing import PricingStream accountID = "..." access_token="..." api = API(access_token=access_token, environment="practice") instruments = "DE30_EUR,EUR_USD,EUR_JPY" s = PricingStream(accountID=accountID, params={"instruments":instruments}) try: n = 0 for R in api.request(s): print(json.dumps(R, indent=2)) n += 1 if n > 10: s.terminate("maxrecs received: {}".format(MAXREC)) except V20Error as e: print("Error: {}".format(e)) Check the 'examples' directory for more detailed examples. Output `````` .. code-block:: json { "status": "tradeable", "asks": [ { "price": "10547.0", "liquidity": 25 }, { "price": "10547.2", "liquidity": 75 }, { "price": "10547.4", "liquidity": 150 } ], "closeoutBid": "10546.6", "bids": [ { "price": "10547.0", "liquidity": 25 }, { "price": "10546.8", "liquidity": 75 }, { "price": "10546.6", "liquidity": 150 } ], "instrument": "DE30_EUR", "time": "2016-10-17T12:25:28.158741026Z", "closeoutAsk": "10547.4", "type": "PRICE", } { "type": "HEARTBEAT", "time": "2016-10-17T12:25:37.447397298Z" } { "status": "tradeable", "asks": [ { "price": "114.490", "liquidity": 1000000 }, { "price": "114.491", "liquidity": 2000000 }, { "price": "114.492", "liquidity": 5000000 }, { "price": "114.494", "liquidity": 10000000 } ], "closeoutBid": "114.469", "bids": [ { "price": "114.473", "liquidity": 1000000 }, { "price": "114.472", "liquidity": 2000000 }, { "price": "114.471", "liquidity": 5000000 }, { "price": "114.469", "liquidity": 10000000 } ], "instrument": "EUR_JPY", "time": "2016-10-17T12:25:40.837289374Z", "closeoutAsk": "114.494", "type": "PRICE", } { "type": "HEARTBEAT", "time": "2016-10-17T12:25:42.447922336Z" } { "status": "tradeable", "asks": [ { "price": "1.09966", "liquidity": 10000000 }, { "price": "1.09968", "liquidity": 10000000 } ], "closeoutBid": "1.09949", "bids": [ { "price": "1.09953", "liquidity": 10000000 }, { "price": "1.09951", "liquidity": 10000000 } ], "instrument": "EUR_USD", "time": "2016-10-17T12:25:43.689619691Z", "closeoutAsk": "1.09970", "type": "PRICE" } { "status": "tradeable", "asks": [ { "price": "114.486", "liquidity": 1000000 }, { "price": "114.487", "liquidity": 2000000 }, { "price": "114.488", "liquidity": 5000000 }, { "price": "114.490", "liquidity": 10000000 } ], "closeoutBid": "114.466", "bids": [ { "price": "114.470", "liquidity": 1000000 }, { "price": "114.469", "liquidity": 2000000 }, { "price": "114.468", "liquidity": 5000000 }, { "price": "114.466", "liquidity": 10000000 } ], "instrument": "EUR_JPY", "time": "2016-10-17T12:25:43.635964725Z", "closeoutAsk": "114.490", "type": "PRICE" } { "status": "tradeable", "asks": [ { "price": "10547.3", "liquidity": 25 }, { "price": "10547.5", "liquidity": 75 }, { "price": "10547.7", "liquidity": 150 } ], "closeoutBid": "10546.9", "bids": [ { "price": "10547.3", "liquidity": 25 }, { "price": "10547.1", "liquidity": 75 }, { "price": "10546.9", "liquidity": 150 } ], "instrument": "DE30_EUR", "time": "2016-10-17T12:25:44.900162113Z", "closeoutAsk": "10547.7", "type": "PRICE" } { "status": "tradeable", "asks": [ { "price": "10547.0", "liquidity": 25 }, { "price": "10547.2", "liquidity": 75 }, { "price": "10547.4", "liquidity": 150 } ], "closeoutBid": "10546.6", "bids": [ { "price": "10547.0", "liquidity": 25 }, { "price": "10546.8", "liquidity": 75 }, { "price": "10546.6", "liquidity": 150 } ], "instrument": "DE30_EUR", "time": "2016-10-17T12:25:44.963539084Z", "closeoutAsk": "10547.4", "type": "PRICE" } { "status": "tradeable", "asks": [ { "price": "114.491", "liquidity": 1000000 }, { "price": "114.492", "liquidity": 2000000 }, { "price": "114.493", "liquidity": 5000000 }, { "price": "114.495", "liquidity": 10000000 } ], "closeoutBid": "114.471", "bids": [ { "price": "114.475", "liquidity": 1000000 }, { "price": "114.474", "liquidity": 2000000 }, { "price": "114.473", "liquidity": 5000000 }, { "price": "114.471", "liquidity": 10000000 } ], "instrument": "EUR_JPY", "time": "2016-10-17T12:25:45.586100087Z", "closeoutAsk": "114.495", "type": "PRICE" } Top_ About this software ------------------- The *oanda-api-v20* software is a personal project. I have no prior or existing relationship with OANDA. If you have any questions regarding this software, please take a look at the documentation first: * oandapyV20 : http://oanda-api-v20.readthedocs.io/en/latest/?badge=latest * OANDA developer docs : http://developer.oanda.com * examples : https://github.com/hootnot/oandapyV20-examples * Github: https://github.com/hootnot/oanda-api-v20 check the open and closed issues If you still have questions/issues you can open an *issue* on Gitub: https://github.com/hootnot/oanda-api-v20 ================================================ FILE: docs/Makefile ================================================ # Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: types types: ../oandapyV20/types/types.py @python dirstruct.py types oandapyV20.types .PHONY: html html: types $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OANDAREST-V20API.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OANDAREST-V20API.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/OANDAREST-V20API" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OANDAREST-V20API" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." ================================================ FILE: docs/conf.py ================================================ # -*- coding: utf-8 -*- # # OANDA REST-V20 API documentation build configuration file, created by # sphinx-quickstart on Sun Jul 10 12:57:37 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath('oandapyV20')) import oandapyV20 # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = getattr(sys.modules["oandapyV20"], "__title__") copyright = getattr(sys.modules["oandapyV20"], "__copyright__") author = getattr(sys.modules["oandapyV20"], "__author__") # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = getattr(sys.modules["oandapyV20"], "__version__") # The full version, including alpha/beta/rc tags. release = getattr(sys.modules["oandapyV20"], "__version__") # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = u'OANDA REST-V20 API v0.1.0' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'OANDAREST-V20APIdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'OANDAREST-V20API.tex', u'OANDA REST-V20 API Documentation', u'Feite Brekeveld', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'oanda-api-v20', u'OANDA REST-V20 API Documentation', [author], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'OANDAREST-V20API', u'OANDA REST-V20 API Documentation', author, 'OANDAREST-V20API', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False # Napoleon settings napoleon_google_docstring = True napoleon_numpy_docstring = True # napoleon_include_private_with_doc = False # napoleon_include_special_with_doc = True napoleon_use_admonition_for_examples = False napoleon_use_admonition_for_notes = False napoleon_use_admonition_for_references = False napoleon_use_ivar = False napoleon_use_param = True napoleon_use_rtype = True on_rtd = os.environ.get('READTHEDOCS', None) == 'True' # custom directive (stackoverflow.com: 7250659) from os.path import basename try: from StringIO import StringIO except ImportError: from io import StringIO from docutils import nodes, statemachine from docutils.parsers.rst import Directive class ExecDirective(Directive): """Execute the specified python code and insert the output into the document""" has_content = True def run(self): oldStdout, sys.stdout = sys.stdout, StringIO() tab_width = self.options.get('tab-width', self.state.document.settings.tab_width) source = self.state_machine.input_lines.source(self.lineno - self.state_machine.input_offset - 1) try: exec('\n'.join(self.content)) text = sys.stdout.getvalue() lines = statemachine.string2lines(text, tab_width, convert_whitespace=True) self.state_machine.insert_input(lines, source) return [] except Exception: return [nodes.error(None, nodes.paragraph(text = "Unable to execute python code at %s:%d:" % (basename(source), self.lineno)), nodes.paragraph(text = str(sys.exc_info()[1])))] finally: sys.stdout = oldStdout def setup(app): app.add_directive('exec', ExecDirective) if not on_rtd: # only import and set the theme if we're building docs locally import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] ================================================ FILE: docs/contrib/factories/instrumentscandlesfactory.rst ================================================ InstrumentsCandlesFactory ------------------------- .. automodule:: oandapyV20.contrib.factories :members: ================================================ FILE: docs/contrib/factories.rst ================================================ Factories --------- The :mod:`oandapyV20.contrib.factories` module contains several classes / methods that can be used optionally to generate requests. .. toctree:: :maxdepth: 4 :glob: factories/* ================================================ FILE: docs/contrib/generic/generic.rst ================================================ granularity_to_time ------------------- .. automodule:: oandapyV20.contrib.generic :members: ================================================ FILE: docs/contrib/generic.rst ================================================ Generic --------- The :mod:`oandapyV20.contrib.generic` module contains several classes / methods that serve a generic purpose. .. toctree:: :maxdepth: 4 :glob: generic/* ================================================ FILE: docs/contrib/orders/limitorderrequest.rst ================================================ LimitOrderRequest ------------------ .. autoclass:: oandapyV20.contrib.requests.LimitOrderRequest :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/orders/marketorderrequest.rst ================================================ MarketOrderRequest ------------------ .. autoclass:: oandapyV20.contrib.requests.MarketOrderRequest :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/orders/mitorderrequest.rst ================================================ MITOrderRequest --------------- .. autoclass:: oandapyV20.contrib.requests.MITOrderRequest :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/orders/positionscloserequest.rst ================================================ PositionCloseRequest -------------------- .. autoclass:: oandapyV20.contrib.requests.PositionCloseRequest :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/orders/stoplossorderrequest.rst ================================================ StopLossOrderRequest -------------------- .. autoclass:: oandapyV20.contrib.requests.StopLossOrderRequest :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/orders/stoporderrequest.rst ================================================ StopOrderRequest ---------------- .. autoclass:: oandapyV20.contrib.requests.StopOrderRequest :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/orders/takeprofitorderrequest.rst ================================================ TakeProfitOrderRequest ---------------------- .. autoclass:: oandapyV20.contrib.requests.TakeProfitOrderRequest :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/orders/tradecloserequest.rst ================================================ TradeCloseRequest ----------------- .. autoclass:: oandapyV20.contrib.requests.TradeCloseRequest :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/orders/trailingstoplossorderrequest.rst ================================================ TrailingStopLossOrderRequest ---------------------------- .. autoclass:: oandapyV20.contrib.requests.TrailingStopLossOrderRequest :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/orders.rst ================================================ Order Classes ------------- The :mod:`oandapyV20.contrib.requests` module contains several classes that can be used optionally when creating Order Requests. When creating an order to create a position, it is possible to create dependant orders that will be triggered when the position gets filled. This goes typically for *Take Profit* and *Stop Loss*. These order specifications and additional data that goes with these order specifications can be created by the contrib.requests.*Order* classes and the contrib.requests.*Details classes. .. toctree:: :maxdepth: 4 :glob: orders/* ================================================ FILE: docs/contrib/support/clientextensions.rst ================================================ Client Extensions ~~~~~~~~~~~~~~~~~ Client extensions can be used optionally on Order Requests. It allows a client to set a custom ID, Tag and/or Comment. .. autoclass:: oandapyV20.contrib.requests.ClientExtensions :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/support/stoplossdetails.rst ================================================ StopLossDetails ~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.contrib.requests.StopLossDetails :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/support/takeprofitdetails.rst ================================================ TakeProfitDetails ~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.contrib.requests.TakeProfitDetails :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/support/trailingstoplossdetails.rst ================================================ TrailingStopLossDetails ~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.contrib.requests.TrailingStopLossDetails :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/contrib/support.rst ================================================ support classes --------------- The :mod:`oandapyV20.contrib.requests` module contains several classes that can be used optionally when creating Order Requests. When creating an order to create a position, it is possible to create dependant orders that will be triggered when the position gets filled. This goes typically for *Take Profit* and *Stop Loss*. These order specifications and additional data that goes with these order specifications can be created by the contrib.requests.*Order* classes and the contrib.requests.*Details classes. .. toctree:: :maxdepth: 4 :glob: support/* ================================================ FILE: docs/dirstruct.py ================================================ # -*- coding: utf8 -*- """dirstruct. generate document directory structure. """ import sys import os import inspect from importlib import import_module def get_classes(modName): """return a list of all classes in a module.""" classNames = [] for name, obj in inspect.getmembers(sys.modules[modName]): if inspect.isclass(obj): classNames.append(name) return classNames if __name__ == "__main__": destDir = sys.argv[1] modToDoc = sys.argv[2] import_module(modToDoc) if not os.path.exists(destDir): os.makedirs(destDir) for cls in get_classes(modToDoc): with open(os.path.join(destDir, "{}.rst".format(cls)), "w") as F: # :show-inheritance:\n F.write("""{cls}\n""" """{ul}\n""" """\n""" """.. autoclass:: {mod}.{cls}\n""" """ :members:\n""" """ :undoc-members:\n""" """ :inherited-members:\n""" """ :special-members: __init__\n""".format( cls=cls, ul=len(cls)*"~", mod=modToDoc)) ================================================ FILE: docs/endpoints/accounts/accountchanges.rst ================================================ AccountChanges ~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.accounts.AccountChanges :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/accounts/accountconfiguration.rst ================================================ AccountConfiguration ~~~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.accounts.AccountConfiguration :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/accounts/accountdetails.rst ================================================ AccountDetails ~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.accounts.AccountDetails :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/accounts/accountinstruments.rst ================================================ AccountInstruments ~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.accounts.AccountInstruments :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/accounts/accountlist.rst ================================================ AccountList ~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.accounts.AccountList :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/accounts/accountsummary.rst ================================================ AccountSummary ~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.accounts.AccountSummary :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/accounts.rst ================================================ oandapyV20.endpoints.accounts ----------------------------- .. toctree:: :maxdepth: 4 :glob: accounts/* ================================================ FILE: docs/endpoints/forexlabs/autochartist.rst ================================================ Autochartist ~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.forexlabs.Autochartist :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/forexlabs/calendar.rst ================================================ Calendar ~~~~~~~~ .. autoclass:: oandapyV20.endpoints.forexlabs.Calendar :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/forexlabs/commitmentsoftraders.rst ================================================ CommitmentsOfTraders ~~~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.forexlabs.CommitmentsOfTraders :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/forexlabs/historicalpositionratios.rst ================================================ HistoricalPositionRatios ~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.forexlabs.HistoricalPositionRatios :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/forexlabs/orderbookdata.rst ================================================ OrderbookData ~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.forexlabs.OrderbookData :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/forexlabs/spreads.rst ================================================ Spreads ~~~~~~~ .. autoclass:: oandapyV20.endpoints.forexlabs.Spreads :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/forexlabs.rst ================================================ oandapyV20.endpoints.forexlabs ------------------------------ .. toctree:: :maxdepth: 4 :glob: forexlabs/* ================================================ FILE: docs/endpoints/instruments/instrumentlist.rst ================================================ InstrumentsCandles ~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.instruments.InstrumentsCandles :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/instruments/instrumentorderbook.rst ================================================ InstrumentsOrderBook ~~~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.instruments.InstrumentsOrderBook :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/instruments/instrumentpositionbook.rst ================================================ InstrumentsPositionBook ~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.instruments.InstrumentsPositionBook :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/instruments.rst ================================================ oandapyV20.endpoints.instruments -------------------------------- .. toctree:: :maxdepth: 4 :glob: instruments/* ================================================ FILE: docs/endpoints/orders/ordercancel.rst ================================================ OrderCancel ~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.orders.OrderCancel :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/orders/orderclientextensions.rst ================================================ OrderClientExtensions ~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.orders.OrderClientExtensions :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/orders/ordercreate.rst ================================================ OrderCreate ~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.orders.OrderCreate :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/orders/orderdetails.rst ================================================ OrderDetails ~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.orders.OrderDetails :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/orders/orderlist.rst ================================================ OrderList ~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.orders.OrderList :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/orders/orderreplace.rst ================================================ OrderReplace ~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.orders.OrderReplace :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/orders/orderspending.rst ================================================ OrdersPending ~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.orders.OrdersPending :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/orders.rst ================================================ oandapyV20.endpoints.orders --------------------------- .. toctree:: :maxdepth: 4 :glob: orders/* ================================================ FILE: docs/endpoints/positions/openpositions.rst ================================================ OpenPositions ~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.positions.OpenPositions :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/positions/positionclose.rst ================================================ PositionClose ~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.positions.PositionClose :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/positions/positiondetails.rst ================================================ PositionDetails ~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.positions.PositionDetails :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/positions/positionlist.rst ================================================ PositionList ~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.positions.PositionList :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/positions.rst ================================================ oandapyV20.endpoints.positions ------------------------------ .. toctree:: :maxdepth: 4 :glob: positions/* ================================================ FILE: docs/endpoints/pricing/pricinginfo.rst ================================================ PricingInfo ~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.pricing.PricingInfo :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/pricing/pricingstream.rst ================================================ PricingStream ~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.pricing.PricingStream :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/pricing.rst ================================================ oandapyV20.endpoints.pricing ---------------------------- .. toctree:: :maxdepth: 4 :glob: pricing/* ================================================ FILE: docs/endpoints/trades/opentrades.rst ================================================ OpenTrades ~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.trades.OpenTrades :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/trades/tradeCRCDO.rst ================================================ TradeCRCDO ~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.trades.TradeCRCDO :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/trades/tradeclientextensions.rst ================================================ TradeClientExtensions ~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.trades.TradeClientExtensions :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/trades/tradeclose.rst ================================================ TradeClose ~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.trades.TradeClose :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/trades/tradedetails.rst ================================================ TradeDetails ~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.trades.TradeDetails :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/trades/tradeslist.rst ================================================ TradesList ~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.trades.TradesList :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/trades.rst ================================================ oandapyV20.endpoints.trades --------------------------- .. toctree:: :maxdepth: 4 :glob: trades/* ================================================ FILE: docs/endpoints/transactions/transactiondetails.rst ================================================ TransactionDetails ~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.transactions.TransactionDetails :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/transactions/transactionidrange.rst ================================================ TransactionIDRange ~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.transactions.TransactionIDRange :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/transactions/transactionlist.rst ================================================ TransactionList ~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.transactions.TransactionList :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/transactions/transactionssinceid.rst ================================================ TransactionsSinceID ~~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.transactions.TransactionsSinceID :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/transactions/transactionsstream.rst ================================================ TransactionsStream ~~~~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.endpoints.transactions.TransactionsStream :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/endpoints/transactions.rst ================================================ oandapyV20.endpoints.transactions --------------------------------- .. toctree:: :maxdepth: 4 :glob: transactions/* ================================================ FILE: docs/examples.rst ================================================ Examples -------- Examples can be found in the examples repositiory on github: examplesrepo_. .. _examplesrepo: https://github.com/hootnot/oandapyV20-examples Example for trades-endpoints ```````````````````````````` Take the script below and name it 'trades.py'. From the shell: :: hootnot@dev:~/test$ python trades.py list hootnot@dev:~/test$ python trades.py open hootnot@dev:~/test$ python trades.py details [ ...] hootnot@dev:~/test$ python trades.py close [ ...] hootnot@dev:~/test$ python trades.py clext [ ...] hootnot@dev:~/test$ python trades.py crc_do [ ...] .. code-block:: python # use of the Trades{..} classes import json import requests from oandapyV20 import API import oandapyV20.endpoints.trades as trades import sys access_token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy" accountID = "zzz-zzzz-zzzzz" api = API(access_token=access_token) if chc == 'list': r = trades.TradesList(accountID) rv = api.request(r) print("RESP:\n{} ".format(json.dumps(rv, indent=2))) if chc == 'open': r = trades.OpenTrades(accountID) rv = api.request(r) print("RESP:\n{} ".format(json.dumps(rv, indent=2))) tradeIDs = [o["id"] for o in rv["trades"]] print("TRADE IDS: {}".format(tradeIDs)) if chc == 'details': for O in sys.argv[2:]: r = trades.TradeDetails(accountID, tradeID=O) rv = api.request(r) print("RESP:\n{} ".format(json.dumps(rv, indent=2))) if chc == 'close': X = iter(sys.argv[2:]) for O in X: cfg = { "units": X.next() } r = trades.TradeClose(accountID, tradeID=O, data=cfg) rv = api.request(r) print("RESP:\n{} ".format(json.dumps(rv, indent=2))) if chc == 'cltext': for O in sys.argv[2:]: # tradeIDs cfg = { "clientExtensions": { "id": "myID{}".format(O), "comment": "myComment", } } r = trades.TradeClientExtensions(accountID, tradeID=O, data=cfg) rv = api.request(r) print("RESP:\n{} ".format(json.dumps(rv, indent=2))) if chc == 'crc_do': X = iter(sys.argv[2:]) for O in X: cfg = { "takeProfit": { "timeInForce": "GTC", "price": X.next(), }, "stopLoss": { "timeInForce": "GTC", "price": X.next() } } r = trades.TradeCRCDO(accountID, tradeID=O, data=cfg) rv = api.request(r) print("RESP:\n{} ".format(json.dumps(rv, indent=2))) ================================================ FILE: docs/index.rst ================================================ .. OANDA REST-V20 API documentation master file, created by sphinx-quickstart on Sun Jul 10 12:57:37 2016. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. The oandapyV20 REST-V20 API wrapper documentation ================================================= Contents: .. toctree:: :maxdepth: 2 :caption: oandapyV20 REST-V20 API wrapper installation oanda-api-v20 oandapyV20.endpoints oandapyV20.definitions oandapyV20.types oandapyV20.contrib examples .. modules oandapyV20 Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ================================================ FILE: docs/installation.rst ================================================ Introduction ============ The :mod:`oandapyV20` package offers an API to the OANDA V20 REST service. To use the REST-API-service you will need a *token* and an *account*. This applies for both *live* and *practice* accounts. For details check oanda.com_. .. _oanda.com: https://oanda.com Install ------- Install the pypi package with pip:: $ pip install oandapyV20 Or alternatively install the latest development version from github:: $ pip install git+https://github.com/hootnot/oanda-api-v20.git You may consider using *virtualenv* to create isolated Python environments. Python 3.4 has *pyvenv* providing the same kind of functionality. Download from Github -------------------- If you want to run the tests, download the source from github:: $ git clone https://github.com/hootnot/oanda-api-v20.git $ cd oanda-api-v20 $ python setup.py test $ python setup.py install ================================================ FILE: docs/oanda-api-v20.rst ================================================ Interface OANDA's REST-V20 ========================== The client ---------- The :mod:`oandapyV20` package contains a client class, :class:`oandapyV20.API`, to communicate with the REST-V20 interface. It processes requests that can be created from the endpoint classes. For it's communication it relies on: :py:mod:`requests` (requests_). .. _requests: http://docs.python-requests.org/en/master/ The client keeps no state of a requests. The response of a request is assigned to the request instance. The response is also returned as a return value by the client. .. autoclass:: oandapyV20.API :inherited-members: :show-inheritance: :special-members: __init__ Exceptions ---------- .. autoclass:: oandapyV20.V20Error :inherited-members: :show-inheritance: :special-members: __init__ Logging ------- The :mod:`oandapyV20` package has `logging` integrated. Logging can be simply applied by enabling a `logger`. The example below will log INFO-level logging to the file *v20.log*. For details check the :py:mod:`logger` module in the standard Python documentation. .. code-block:: python # code snippet from oandapyV20 import API import oandapyV20.endpoints.orders as orders from oandapyV20.exceptions import V20Error from exampleauth import exampleAuth import logging logging.basicConfig( filename="v20.log", level=logging.INFO, format='%(asctime)s [%(levelname)s] %(name)s : %(message)s', ) accountID, token = exampleAuth() ... Resulting loglines: .. code-block:: text 2016-10-22 17:50:37,988 [INFO] oandapyV20.oandapyV20 : setting up API-client for environment practice 2016-10-22 17:50:37,990 [INFO] oandapyV20.oandapyV20 : performing request https://api-fxpractice.oanda.com/v3/accounts/101-004-1435156-001/orders 2016-10-22 17:50:37,998 [INFO] requests.packages.urllib3.connectionpool : Starting new HTTPS connection (1): api-fxpractice.oanda.com 2016-10-22 17:50:38,866 [INFO] oandapyV20.oandapyV20 : performing request https://api-fxpractice.oanda.com/v3/accounts/101-004-1435156-001/orders 2016-10-22 17:50:39,066 [ERROR] oandapyV20.oandapyV20 : request https://api-fxpractice.oanda.com/v3/accounts/101-004-1435156-001/orders failed [400,{"errorMessage":"Invalid value specified for 'order.instrument'"}] ================================================ FILE: docs/oandapyV20.contrib.rst ================================================ oandapyV20.contrib ================== .. toctree:: :maxdepth: 4 :glob: contrib/* ================================================ FILE: docs/oandapyV20.definitions.accounts.rst ================================================ oandapyV20.definitions.accounts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: oandapyV20.definitions.accounts :members: :undoc-members: :show-inheritance: :special-members: __getitem__ ================================================ FILE: docs/oandapyV20.definitions.instruments.rst ================================================ oandapyV20.definitions.instruments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: oandapyV20.definitions.instruments :members: :undoc-members: :show-inheritance: :special-members: __getitem__ ================================================ FILE: docs/oandapyV20.definitions.orders.rst ================================================ oandapyV20.definitions.orders ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: oandapyV20.definitions.orders :members: :undoc-members: :show-inheritance: :special-members: __getitem__ ================================================ FILE: docs/oandapyV20.definitions.pricing.rst ================================================ oandapyV20.definitions.pricing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: oandapyV20.definitions.pricing :members: :undoc-members: :show-inheritance: :special-members: __getitem__ ================================================ FILE: docs/oandapyV20.definitions.rst ================================================ oandapyV20.definitions ====================== The :mod:`oandapyV20.definitions` module holds all the definitions as in the definitions section of the REST-V20 specs of OANDA, see developer.oanda.com_. .. _developer.oanda.com: http://developer.oanda.com .. toctree:: :maxdepth: 3 oandapyV20.definitions.accounts oandapyV20.definitions.instruments oandapyV20.definitions.orders oandapyV20.definitions.pricing oandapyV20.definitions.trades oandapyV20.definitions.transactions ================================================ FILE: docs/oandapyV20.definitions.trades.rst ================================================ oandapyV20.definitions.trades ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: oandapyV20.definitions.trades :members: :undoc-members: :show-inheritance: :special-members: __getitem__ ================================================ FILE: docs/oandapyV20.definitions.transactions.rst ================================================ oandapyV20.definitions.transactions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: oandapyV20.definitions.transactions :members: :undoc-members: :show-inheritance: :special-members: __getitem__ ================================================ FILE: docs/oandapyV20.endpoints.rst ================================================ oandapyV20.endpoints ==================== .. toctree:: :maxdepth: 4 :glob: endpoints/* ================================================ FILE: docs/oandapyV20.types.rst ================================================ oandapyV20.types ================ The :mod:`oandapyV20.types` module contains the types representing the types that are used in the API-specs of OANDA, check developer.oanda.com_. These types offer a convenient interface between Python types and the types used in the REST-API. .. _developer.oanda.com: http://developer.oanda.com Take for instance the `PriceValue` type. It is the string representation of a float. .. code-block:: python from oandapyV20.types import PriceValue pv1 = PriceValue(122.345) pv2 = PriceValue("122.345") pv1.value "122.345" pv1.value == pv2.value True Regardless the value we instantiate it with, a float or a string, the PriceValue instance will allways be a string value. The types also validate the values passed. Invalid values will raise an exception. .. toctree:: :maxdepth: 4 :glob: types/* ================================================ FILE: docs/types/AccountID.rst ================================================ AccountID ~~~~~~~~~ .. autoclass:: oandapyV20.types.AccountID :members: :undoc-members: :inherited-members: :special-members: __init__ ================================================ FILE: docs/types/AccountUnits.rst ================================================ AccountUnits ~~~~~~~~~~~~ .. autoclass:: oandapyV20.types.AccountUnits :members: :undoc-members: :inherited-members: :special-members: __init__ ================================================ FILE: docs/types/ClientComment.rst ================================================ ClientComment ~~~~~~~~~~~~~ .. autoclass:: oandapyV20.types.ClientComment :members: :undoc-members: :inherited-members: :special-members: __init__ ================================================ FILE: docs/types/ClientID.rst ================================================ ClientID ~~~~~~~~ .. autoclass:: oandapyV20.types.ClientID :members: :undoc-members: :inherited-members: :special-members: __init__ ================================================ FILE: docs/types/ClientTag.rst ================================================ ClientTag ~~~~~~~~~ .. autoclass:: oandapyV20.types.ClientTag :members: :undoc-members: :inherited-members: :special-members: __init__ ================================================ FILE: docs/types/DateTime.rst ================================================ DateTime ~~~~~~~~ .. autoclass:: oandapyV20.types.DateTime :members: :undoc-members: :inherited-members: :special-members: __init__ ================================================ FILE: docs/types/OrderID.rst ================================================ OrderID ~~~~~~~ .. autoclass:: oandapyV20.types.OrderID :members: :undoc-members: :inherited-members: :special-members: __init__ ================================================ FILE: docs/types/OrderIdentifier.rst ================================================ OrderIdentifier ~~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.types.OrderIdentifier :members: :undoc-members: :inherited-members: :special-members: __init__ ================================================ FILE: docs/types/OrderSpecifier.rst ================================================ OrderSpecifier ~~~~~~~~~~~~~~ .. autoclass:: oandapyV20.types.OrderSpecifier :members: :undoc-members: :inherited-members: :special-members: __init__ ================================================ FILE: docs/types/PriceValue.rst ================================================ PriceValue ~~~~~~~~~~ .. autoclass:: oandapyV20.types.PriceValue :members: :undoc-members: :inherited-members: :special-members: __init__ ================================================ FILE: docs/types/TradeID.rst ================================================ TradeID ~~~~~~~ .. autoclass:: oandapyV20.types.TradeID :members: :undoc-members: :inherited-members: :special-members: __init__ ================================================ FILE: docs/types/Units.rst ================================================ Units ~~~~~ .. autoclass:: oandapyV20.types.Units :members: :undoc-members: :inherited-members: :special-members: __init__ ================================================ FILE: examples/README.rst ================================================ Examples ======== As of Nov 28th 2016 examples are in a separate repo: oandapyV20-examples_ .. _oandapyV20-examples: https://github.com/hootnot/oandapyV20-examples ================================================ FILE: jupyter/account.txt ================================================ xxx-yyy-zzzzzzz-aaa ================================================ FILE: jupyter/accounts.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "[index](./index.ipynb) | [accounts](./accounts.ipynb) | [orders](./orders.ipynb) | [trades](./trades.ipynb) | [positions](./positions.ipynb) | [historical](./historical.ipynb) | [streams](./streams.ipynb) | [errors](./exceptions.ipynb)\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "## Account endpoints\n", "\n", "### Example: fetch account information\n", "\n", "A simple example to retrieve the accounts belonging to a *token*:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"accounts\": [\n", " {\n", " \"tags\": [],\n", " \"id\": \"101-004-1435156-002\"\n", " },\n", " {\n", " \"tags\": [],\n", " \"id\": \"101-004-1435156-001\"\n", " }\n", " ]\n", "}\n" ] } ], "source": [ "import json\n", "import oandapyV20\n", "import oandapyV20.endpoints.accounts as accounts\n", "from exampleauth import exampleauth\n", "\n", "accountID, access_token = exampleauth.exampleAuth()\n", "client = oandapyV20.API(access_token=access_token)\n", "\n", "r = accounts.AccountList()\n", "response = client.request(r)\n", "print(json.dumps(response, indent=2))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "### Request details\n", "\n", "Lets get some details from the request itself after the client performed the request:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "API-path: v3/accounts\n", "METHOD: GET\n", "Response status: 200\n", "The account id's: ['101-004-1435156-002', '101-004-1435156-001']\n" ] } ], "source": [ "print(\"API-path: \" , r)\n", "print(\"METHOD: \", r.method)\n", "print(\"Response status: \", r.status_code)\n", "print(\"The account id's: \", [acc.get('id') for acc in r.response.get('accounts')])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: jupyter/exampleauth/__init__.py ================================================ ================================================ FILE: jupyter/exampleauth/exampleauth.py ================================================ """simple auth method for examples.""" def exampleAuth(): accountID, token = None, None with open("account.txt") as I: accountID = I.read().strip() with open("token.txt") as I: token = I.read().strip() return accountID, token ================================================ FILE: jupyter/exceptions.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "[index](./index.ipynb) | [accounts](./accounts.ipynb) | [orders](./orders.ipynb) | [trades](./trades.ipynb) | [positions](./positions.ipynb) | [historical](./historical.ipynb) | [streams](./streams.ipynb) | [errors](./exceptions.ipynb)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "## What about errors ?\n", "\n", "Errors can occur. Wrong parameters or connection errors for example. Lets take the example from [Orders](./orders.ipynb) and make the instrument one that does not exist. The orderspecfication itself is still valid. Wrap it up in a *try/except* to catch the error:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "V20Error occurred: {\"errorMessage\":\"Invalid value specified for 'order.instrument'\"}\n" ] } ], "source": [ "import json\n", "import oandapyV20\n", "from oandapyV20.exceptions import V20Error\n", "import oandapyV20.endpoints.orders as orders\n", "from oandapyV20.contrib.requests import (\n", " MarketOrderRequest,\n", " TakeProfitDetails,\n", " StopLossDetails)\n", "from exampleauth import exampleauth\n", "\n", "accountID, access_token = exampleauth.exampleAuth()\n", "client = oandapyV20.API(access_token=access_token)\n", "\n", "mktOrder = MarketOrderRequest(instrument=\"EUR_UDS\", # EUR_UDS ... the faulty instrument\n", " units=10000,\n", " takeProfitOnFill=TakeProfitDetails(price=1.10).data,\n", " stopLossOnFill=StopLossDetails(price=1.05).data\n", " ).data\n", "r = orders.OrderCreate(accountID=accountID, data=mktOrder)\n", "try:\n", " rv = client.request(r)\n", "except V20Error as err:\n", " print(\"V20Error occurred: {}\".format(err))\n", "else:\n", " print(\"Response: {}\\n{}\".format(r.status_code, json.dumps(rv, indent=2)))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "As we can see the REST-API returned an error response saying the instrument is invalid." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: jupyter/historical.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "[accounts](./accounts.ipynb) | [orders](./orders.ipynb) | [trades](./trades.ipynb) | [positions](./positions.ipynb) | [historical](./historical.ipynb) | [streams](./streams.ipynb) | [errors](./exceptions.ipynb)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "# Historical data\n", "\n", "OANDA provides access to historical data. The *oandapyV20* has a class to access this data: *oandapyV20.endpoints.instruments.InstrumentsCandles*.\n", "\n", "Lets give it a try and download some data for:\n", " + instrument: EUR_USD\n", " + granularity: H1\n", " + from: 2017-01-01T00:00:00" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Request: v3/instruments/EUR_USD/candles #candles received: 9\n", "{\n", " \"instrument\": \"EUR_USD\", \n", " \"candles\": [\n", " {\n", " \"volume\": 481, \n", " \"mid\": {\n", " \"h\": \"1.04712\", \n", " \"c\": \"1.04662\", \n", " \"l\": \"1.04572\", \n", " \"o\": \"1.04577\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T00:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 664, \n", " \"mid\": {\n", " \"h\": \"1.04808\", \n", " \"c\": \"1.04758\", \n", " \"l\": \"1.04646\", \n", " \"o\": \"1.04665\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T01:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 392, \n", " \"mid\": {\n", " \"h\": \"1.04780\", \n", " \"c\": \"1.04721\", \n", " \"l\": \"1.04709\", \n", " \"o\": \"1.04761\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T02:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 394, \n", " \"mid\": {\n", " \"h\": \"1.04848\", \n", " \"c\": \"1.04848\", \n", " \"l\": \"1.04715\", \n", " \"o\": \"1.04718\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T03:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 285, \n", " \"mid\": {\n", " \"h\": \"1.04898\", \n", " \"c\": \"1.04884\", \n", " \"l\": \"1.04820\", \n", " \"o\": \"1.04852\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T04:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 250, \n", " \"mid\": {\n", " \"h\": \"1.04902\", \n", " \"c\": \"1.04824\", \n", " \"l\": \"1.04816\", \n", " \"o\": \"1.04886\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T05:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 368, \n", " \"mid\": {\n", " \"h\": \"1.04892\", \n", " \"c\": \"1.04882\", \n", " \"l\": \"1.04813\", \n", " \"o\": \"1.04821\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T06:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 1639, \n", " \"mid\": {\n", " \"h\": \"1.04888\", \n", " \"c\": \"1.04602\", \n", " \"l\": \"1.04536\", \n", " \"o\": \"1.04885\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T07:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 2830, \n", " \"mid\": {\n", " \"h\": \"1.04658\", \n", " \"c\": \"1.04353\", \n", " \"l\": \"1.04207\", \n", " \"o\": \"1.04606\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T08:00:00.000000000Z\"\n", " }\n", " ], \n", " \"granularity\": \"H1\"\n", "}\n" ] } ], "source": [ "import json\n", "import oandapyV20\n", "import oandapyV20.endpoints.instruments as instruments\n", "from exampleauth import exampleauth\n", "\n", "accountID, access_token = exampleauth.exampleAuth()\n", "client = oandapyV20.API(access_token=access_token)\n", "\n", "instrument = \"EUR_USD\"\n", "params = {\n", " \"from\": \"2017-01-01T00:00:00Z\",\n", " \"granularity\": \"H1\",\n", " \"count\": 10,\n", "}\n", "r = instruments.InstrumentsCandles(instrument=instrument, params=params)\n", "response = client.request(r)\n", "print(\"Request: {} #candles received: {}\".format(r, len(r.response.get('candles'))))\n", "print(json.dumps(response, indent=2))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "So, that is 9 records?\n", "... that can be fixed by including the parameter *includeFirst*, see the OANDA documentation for details." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Request: v3/instruments/EUR_USD/candles #candles received: 10\n", "{\n", " \"instrument\": \"EUR_USD\", \n", " \"candles\": [\n", " {\n", " \"volume\": 974, \n", " \"mid\": {\n", " \"h\": \"1.04711\", \n", " \"c\": \"1.04575\", \n", " \"l\": \"1.04567\", \n", " \"o\": \"1.04684\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-02T23:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 481, \n", " \"mid\": {\n", " \"h\": \"1.04712\", \n", " \"c\": \"1.04662\", \n", " \"l\": \"1.04572\", \n", " \"o\": \"1.04577\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T00:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 664, \n", " \"mid\": {\n", " \"h\": \"1.04808\", \n", " \"c\": \"1.04758\", \n", " \"l\": \"1.04646\", \n", " \"o\": \"1.04665\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T01:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 392, \n", " \"mid\": {\n", " \"h\": \"1.04780\", \n", " \"c\": \"1.04721\", \n", " \"l\": \"1.04709\", \n", " \"o\": \"1.04761\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T02:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 394, \n", " \"mid\": {\n", " \"h\": \"1.04848\", \n", " \"c\": \"1.04848\", \n", " \"l\": \"1.04715\", \n", " \"o\": \"1.04718\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T03:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 285, \n", " \"mid\": {\n", " \"h\": \"1.04898\", \n", " \"c\": \"1.04884\", \n", " \"l\": \"1.04820\", \n", " \"o\": \"1.04852\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T04:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 250, \n", " \"mid\": {\n", " \"h\": \"1.04902\", \n", " \"c\": \"1.04824\", \n", " \"l\": \"1.04816\", \n", " \"o\": \"1.04886\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T05:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 368, \n", " \"mid\": {\n", " \"h\": \"1.04892\", \n", " \"c\": \"1.04882\", \n", " \"l\": \"1.04813\", \n", " \"o\": \"1.04821\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T06:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 1639, \n", " \"mid\": {\n", " \"h\": \"1.04888\", \n", " \"c\": \"1.04602\", \n", " \"l\": \"1.04536\", \n", " \"o\": \"1.04885\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T07:00:00.000000000Z\"\n", " }, \n", " {\n", " \"volume\": 2830, \n", " \"mid\": {\n", " \"h\": \"1.04658\", \n", " \"c\": \"1.04353\", \n", " \"l\": \"1.04207\", \n", " \"o\": \"1.04606\"\n", " }, \n", " \"complete\": true, \n", " \"time\": \"2017-01-03T08:00:00.000000000Z\"\n", " }\n", " ], \n", " \"granularity\": \"H1\"\n", "}\n" ] } ], "source": [ "instrument = \"EUR_USD\"\n", "params = {\n", " \"from\": \"2017-01-01T00:00:00Z\",\n", " \"granularity\": \"H1\",\n", " \"includeFirst\": True,\n", " \"count\": 10,\n", "}\n", "r = instruments.InstrumentsCandles(instrument=instrument, params=params)\n", "response = client.request(r)\n", "print(\"Request: {} #candles received: {}\".format(r, len(r.response.get('candles'))))\n", "print(json.dumps(response, indent=2))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "# Bulk history\n", "\n", "## InstrumentsCandles class\n", "\n", "It is likely that you want to retrieve more than 10 records. The OANDA docs say that the default number of records\n", "is 500, in case you do not specify. You can specify the number of records to retrieve by using *count*, with a maximum of 5000. The *InstrumentsCandles* class enables you to retrieve the records.\n", "\n", "## InstrumentsCandlesFactory\n", "\n", "Now if you would like to retrieve a lot of history, you have to make consecutive requests. To make this an easy process the *oandapyV20* library comes with a so called *factory* named *InstrumentsCandlesFactory*.\n", "\n", "Using this class you can retrieve all history of an instrument from a certain date. The *InstrumentsCandlesFactory* acts as a generator generating *InstrumentCandles* requests until all data is retrieved. The number of requests can be influenced by specifying *count*. Setting *count* to 5000 would generate a tenth of the requests vs. the default of 500.\n", "\n", "Back to our example: lets make sure we request a lot of data, so we set the *granularity* to *M5* and leave the date at 2017-01-01T00:00:00. The will retrieve all records from that date up to today, because we did not specify the *to* parameter. \n" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-02T17:40:00Z', 'from': '2017-01-01T00:00:00Z', 'granularity': 'M5'}, received: 0\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-04T11:25:00Z', 'from': '2017-01-02T17:45:00Z', 'granularity': 'M5'}, received: 436\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-06T05:10:00Z', 'from': '2017-01-04T11:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-07T22:55:00Z', 'from': '2017-01-06T05:15:00Z', 'granularity': 'M5'}, received: 200\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-09T16:40:00Z', 'from': '2017-01-07T23:00:00Z', 'granularity': 'M5'}, received: 222\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-11T10:25:00Z', 'from': '2017-01-09T16:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-13T04:10:00Z', 'from': '2017-01-11T10:30:00Z', 'granularity': 'M5'}, received: 498\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-14T21:55:00Z', 'from': '2017-01-13T04:15:00Z', 'granularity': 'M5'}, received: 212\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-16T15:40:00Z', 'from': '2017-01-14T22:00:00Z', 'granularity': 'M5'}, received: 211\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-18T09:25:00Z', 'from': '2017-01-16T15:45:00Z', 'granularity': 'M5'}, received: 497\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-20T03:10:00Z', 'from': '2017-01-18T09:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-21T20:55:00Z', 'from': '2017-01-20T03:15:00Z', 'granularity': 'M5'}, received: 224\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-23T14:40:00Z', 'from': '2017-01-21T21:00:00Z', 'granularity': 'M5'}, received: 193\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-25T08:25:00Z', 'from': '2017-01-23T14:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-27T02:10:00Z', 'from': '2017-01-25T08:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-28T19:55:00Z', 'from': '2017-01-27T02:15:00Z', 'granularity': 'M5'}, received: 236\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-01-30T13:40:00Z', 'from': '2017-01-28T20:00:00Z', 'granularity': 'M5'}, received: 187\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-01T07:25:00Z', 'from': '2017-01-30T13:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-03T01:10:00Z', 'from': '2017-02-01T07:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-04T18:55:00Z', 'from': '2017-02-03T01:15:00Z', 'granularity': 'M5'}, received: 248\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-06T12:40:00Z', 'from': '2017-02-04T19:00:00Z', 'granularity': 'M5'}, received: 175\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-08T06:25:00Z', 'from': '2017-02-06T12:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-10T00:10:00Z', 'from': '2017-02-08T06:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-11T17:55:00Z', 'from': '2017-02-10T00:15:00Z', 'granularity': 'M5'}, received: 260\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-13T11:40:00Z', 'from': '2017-02-11T18:00:00Z', 'granularity': 'M5'}, received: 163\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-15T05:25:00Z', 'from': '2017-02-13T11:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-16T23:10:00Z', 'from': '2017-02-15T05:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-18T16:55:00Z', 'from': '2017-02-16T23:15:00Z', 'granularity': 'M5'}, received: 272\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-20T10:40:00Z', 'from': '2017-02-18T17:00:00Z', 'granularity': 'M5'}, received: 151\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-22T04:25:00Z', 'from': '2017-02-20T10:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-23T22:10:00Z', 'from': '2017-02-22T04:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-25T15:55:00Z', 'from': '2017-02-23T22:15:00Z', 'granularity': 'M5'}, received: 284\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-02-27T09:40:00Z', 'from': '2017-02-25T16:00:00Z', 'granularity': 'M5'}, received: 139\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-01T03:25:00Z', 'from': '2017-02-27T09:45:00Z', 'granularity': 'M5'}, received: 498\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-02T21:10:00Z', 'from': '2017-03-01T03:30:00Z', 'granularity': 'M5'}, received: 498\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-04T14:55:00Z', 'from': '2017-03-02T21:15:00Z', 'granularity': 'M5'}, received: 296\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-06T08:40:00Z', 'from': '2017-03-04T15:00:00Z', 'granularity': 'M5'}, received: 127\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-08T02:25:00Z', 'from': '2017-03-06T08:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-09T20:10:00Z', 'from': '2017-03-08T02:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-11T13:55:00Z', 'from': '2017-03-09T20:15:00Z', 'granularity': 'M5'}, received: 308\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-13T07:40:00Z', 'from': '2017-03-11T14:00:00Z', 'granularity': 'M5'}, received: 126\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-15T01:25:00Z', 'from': '2017-03-13T07:45:00Z', 'granularity': 'M5'}, received: 497\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-16T19:10:00Z', 'from': '2017-03-15T01:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-18T12:55:00Z', 'from': '2017-03-16T19:15:00Z', 'granularity': 'M5'}, received: 308\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-20T06:40:00Z', 'from': '2017-03-18T13:00:00Z', 'granularity': 'M5'}, received: 115\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-22T00:25:00Z', 'from': '2017-03-20T06:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-23T18:10:00Z', 'from': '2017-03-22T00:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-25T11:55:00Z', 'from': '2017-03-23T18:15:00Z', 'granularity': 'M5'}, received: 319\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-27T05:40:00Z', 'from': '2017-03-25T12:00:00Z', 'granularity': 'M5'}, received: 103\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-28T23:25:00Z', 'from': '2017-03-27T05:45:00Z', 'granularity': 'M5'}, received: 497\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-03-30T17:10:00Z', 'from': '2017-03-28T23:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-01T10:55:00Z', 'from': '2017-03-30T17:15:00Z', 'granularity': 'M5'}, received: 331\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-03T04:40:00Z', 'from': '2017-04-01T11:00:00Z', 'granularity': 'M5'}, received: 91\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-04T22:25:00Z', 'from': '2017-04-03T04:45:00Z', 'granularity': 'M5'}, received: 497\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-06T16:10:00Z', 'from': '2017-04-04T22:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-08T09:55:00Z', 'from': '2017-04-06T16:15:00Z', 'granularity': 'M5'}, received: 343\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-10T03:40:00Z', 'from': '2017-04-08T10:00:00Z', 'granularity': 'M5'}, received: 79\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-11T21:25:00Z', 'from': '2017-04-10T03:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-13T15:10:00Z', 'from': '2017-04-11T21:30:00Z', 'granularity': 'M5'}, received: 497\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-15T08:55:00Z', 'from': '2017-04-13T15:15:00Z', 'granularity': 'M5'}, received: 352\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-17T02:40:00Z', 'from': '2017-04-15T09:00:00Z', 'granularity': 'M5'}, received: 67\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-18T20:25:00Z', 'from': '2017-04-17T02:45:00Z', 'granularity': 'M5'}, received: 496\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-20T14:10:00Z', 'from': '2017-04-18T20:30:00Z', 'granularity': 'M5'}, received: 497\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-22T07:55:00Z', 'from': '2017-04-20T14:15:00Z', 'granularity': 'M5'}, received: 366\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-24T01:40:00Z', 'from': '2017-04-22T08:00:00Z', 'granularity': 'M5'}, received: 55\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-25T19:25:00Z', 'from': '2017-04-24T01:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-27T13:10:00Z', 'from': '2017-04-25T19:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-04-29T06:55:00Z', 'from': '2017-04-27T13:15:00Z', 'granularity': 'M5'}, received: 379\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-01T00:40:00Z', 'from': '2017-04-29T07:00:00Z', 'granularity': 'M5'}, received: 43\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-02T18:25:00Z', 'from': '2017-05-01T00:45:00Z', 'granularity': 'M5'}, received: 497\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-04T12:10:00Z', 'from': '2017-05-02T18:30:00Z', 'granularity': 'M5'}, received: 496\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-06T05:55:00Z', 'from': '2017-05-04T12:15:00Z', 'granularity': 'M5'}, received: 392\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-07T23:40:00Z', 'from': '2017-05-06T06:00:00Z', 'granularity': 'M5'}, received: 31\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-09T17:25:00Z', 'from': '2017-05-07T23:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-11T11:10:00Z', 'from': '2017-05-09T17:30:00Z', 'granularity': 'M5'}, received: 498\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-13T04:55:00Z', 'from': '2017-05-11T11:15:00Z', 'granularity': 'M5'}, received: 402\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-14T22:40:00Z', 'from': '2017-05-13T05:00:00Z', 'granularity': 'M5'}, received: 19\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-16T16:25:00Z', 'from': '2017-05-14T22:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-18T10:10:00Z', 'from': '2017-05-16T16:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-20T03:55:00Z', 'from': '2017-05-18T10:15:00Z', 'granularity': 'M5'}, received: 416\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-21T21:40:00Z', 'from': '2017-05-20T04:00:00Z', 'granularity': 'M5'}, received: 7\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-23T15:25:00Z', 'from': '2017-05-21T21:45:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-25T09:10:00Z', 'from': '2017-05-23T15:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-27T02:55:00Z', 'from': '2017-05-25T09:15:00Z', 'granularity': 'M5'}, received: 428\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-28T20:40:00Z', 'from': '2017-05-27T03:00:00Z', 'granularity': 'M5'}, received: 0\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-05-30T14:25:00Z', 'from': '2017-05-28T20:45:00Z', 'granularity': 'M5'}, received: 491\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-01T08:10:00Z', 'from': '2017-05-30T14:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-03T01:55:00Z', 'from': '2017-06-01T08:15:00Z', 'granularity': 'M5'}, received: 440\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-04T19:40:00Z', 'from': '2017-06-03T02:00:00Z', 'granularity': 'M5'}, received: 0\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-06T13:25:00Z', 'from': '2017-06-04T19:45:00Z', 'granularity': 'M5'}, received: 483\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-08T07:10:00Z', 'from': '2017-06-06T13:30:00Z', 'granularity': 'M5'}, received: 498\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-10T00:55:00Z', 'from': '2017-06-08T07:15:00Z', 'granularity': 'M5'}, received: 452\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-11T18:40:00Z', 'from': '2017-06-10T01:00:00Z', 'granularity': 'M5'}, received: 0\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-13T12:25:00Z', 'from': '2017-06-11T18:45:00Z', 'granularity': 'M5'}, received: 471\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-15T06:10:00Z', 'from': '2017-06-13T12:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-16T23:55:00Z', 'from': '2017-06-15T06:15:00Z', 'granularity': 'M5'}, received: 464\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-18T17:40:00Z', 'from': '2017-06-17T00:00:00Z', 'granularity': 'M5'}, received: 0\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-20T11:25:00Z', 'from': '2017-06-18T17:45:00Z', 'granularity': 'M5'}, received: 458\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-22T05:10:00Z', 'from': '2017-06-20T11:30:00Z', 'granularity': 'M5'}, received: 495\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-23T22:55:00Z', 'from': '2017-06-22T05:15:00Z', 'granularity': 'M5'}, received: 474\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-25T16:40:00Z', 'from': '2017-06-23T23:00:00Z', 'granularity': 'M5'}, received: 0\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-27T10:25:00Z', 'from': '2017-06-25T16:45:00Z', 'granularity': 'M5'}, received: 444\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-29T04:10:00Z', 'from': '2017-06-27T10:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-06-30T21:55:00Z', 'from': '2017-06-29T04:15:00Z', 'granularity': 'M5'}, received: 488\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-02T15:40:00Z', 'from': '2017-06-30T22:00:00Z', 'granularity': 'M5'}, received: 0\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-04T09:25:00Z', 'from': '2017-07-02T15:45:00Z', 'granularity': 'M5'}, received: 433\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-06T03:10:00Z', 'from': '2017-07-04T09:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-07T20:55:00Z', 'from': '2017-07-06T03:15:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-09T14:40:00Z', 'from': '2017-07-07T21:00:00Z', 'granularity': 'M5'}, received: 0\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-11T08:25:00Z', 'from': '2017-07-09T14:45:00Z', 'granularity': 'M5'}, received: 422\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-13T02:10:00Z', 'from': '2017-07-11T08:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-14T19:55:00Z', 'from': '2017-07-13T02:15:00Z', 'granularity': 'M5'}, received: 498\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-16T13:40:00Z', 'from': '2017-07-14T20:00:00Z', 'granularity': 'M5'}, received: 11\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-18T07:25:00Z', 'from': '2017-07-16T13:45:00Z', 'granularity': 'M5'}, received: 412\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-20T01:10:00Z', 'from': '2017-07-18T07:30:00Z', 'granularity': 'M5'}, received: 498\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-21T18:55:00Z', 'from': '2017-07-20T01:15:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-23T12:40:00Z', 'from': '2017-07-21T19:00:00Z', 'granularity': 'M5'}, received: 23\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-25T06:25:00Z', 'from': '2017-07-23T12:45:00Z', 'granularity': 'M5'}, received: 400\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-27T00:10:00Z', 'from': '2017-07-25T06:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-28T17:55:00Z', 'from': '2017-07-27T00:15:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-07-30T11:40:00Z', 'from': '2017-07-28T18:00:00Z', 'granularity': 'M5'}, received: 35\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-01T05:25:00Z', 'from': '2017-07-30T11:45:00Z', 'granularity': 'M5'}, received: 388\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-02T23:10:00Z', 'from': '2017-08-01T05:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-04T16:55:00Z', 'from': '2017-08-02T23:15:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-06T10:40:00Z', 'from': '2017-08-04T17:00:00Z', 'granularity': 'M5'}, received: 47\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-08T04:25:00Z', 'from': '2017-08-06T10:45:00Z', 'granularity': 'M5'}, received: 376\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-09T22:10:00Z', 'from': '2017-08-08T04:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-11T15:55:00Z', 'from': '2017-08-09T22:15:00Z', 'granularity': 'M5'}, received: 498\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-13T09:40:00Z', 'from': '2017-08-11T16:00:00Z', 'granularity': 'M5'}, received: 59\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-15T03:25:00Z', 'from': '2017-08-13T09:45:00Z', 'granularity': 'M5'}, received: 364\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-16T21:10:00Z', 'from': '2017-08-15T03:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-18T14:55:00Z', 'from': '2017-08-16T21:15:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-20T08:40:00Z', 'from': '2017-08-18T15:00:00Z', 'granularity': 'M5'}, received: 71\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-22T02:25:00Z', 'from': '2017-08-20T08:45:00Z', 'granularity': 'M5'}, received: 352\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-23T20:10:00Z', 'from': '2017-08-22T02:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-25T13:55:00Z', 'from': '2017-08-23T20:15:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-27T07:40:00Z', 'from': '2017-08-25T14:00:00Z', 'granularity': 'M5'}, received: 83\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-29T01:25:00Z', 'from': '2017-08-27T07:45:00Z', 'granularity': 'M5'}, received: 340\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-08-30T19:10:00Z', 'from': '2017-08-29T01:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-01T12:55:00Z', 'from': '2017-08-30T19:15:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-03T06:40:00Z', 'from': '2017-09-01T13:00:00Z', 'granularity': 'M5'}, received: 95\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-05T00:25:00Z', 'from': '2017-09-03T06:45:00Z', 'granularity': 'M5'}, received: 325\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-06T18:10:00Z', 'from': '2017-09-05T00:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-08T11:55:00Z', 'from': '2017-09-06T18:15:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-10T05:40:00Z', 'from': '2017-09-08T12:00:00Z', 'granularity': 'M5'}, received: 107\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-11T23:25:00Z', 'from': '2017-09-10T05:45:00Z', 'granularity': 'M5'}, received: 315\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-13T17:10:00Z', 'from': '2017-09-11T23:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-15T10:55:00Z', 'from': '2017-09-13T17:15:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-17T04:40:00Z', 'from': '2017-09-15T11:00:00Z', 'granularity': 'M5'}, received: 119\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-18T22:25:00Z', 'from': '2017-09-17T04:45:00Z', 'granularity': 'M5'}, received: 303\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-20T16:10:00Z', 'from': '2017-09-18T22:30:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-22T09:55:00Z', 'from': '2017-09-20T16:15:00Z', 'granularity': 'M5'}, received: 499\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-24T03:40:00Z', 'from': '2017-09-22T10:00:00Z', 'granularity': 'M5'}, received: 131\n", "REQUEST: v3/instruments/EUR_USD/candles InstrumentsCandles {'to': '2017-09-24T17:54:30Z', 'from': '2017-09-24T03:45:00Z', 'granularity': 'M5'}, received: 0\n", "Check the datafile: /tmp/EUR_USD.M5.out under /tmp!, it contains 54090 records\n" ] } ], "source": [ "import json\n", "import oandapyV20\n", "import oandapyV20.endpoints.instruments as instruments\n", "from oandapyV20.contrib.factories import InstrumentsCandlesFactory\n", "from exampleauth import exampleauth\n", "\n", "accountID, access_token = exampleauth.exampleAuth()\n", "client = oandapyV20.API(access_token=access_token)\n", "\n", "instrument = \"EUR_USD\"\n", "params = {\n", " \"from\": \"2017-01-01T00:00:00Z\",\n", " \"granularity\": \"M5\",\n", "}\n", "\n", "def cnv(r, h):\n", " # get all candles from the response and write them as a record to the filehandle h\n", " for candle in r.get('candles'):\n", " ctime = candle.get('time')[0:19]\n", " try:\n", " rec = \"{time},{complete},{o},{h},{l},{c},{v}\".format(\n", " time=ctime,\n", " complete=candle['complete'],\n", " o=candle['mid']['o'],\n", " h=candle['mid']['h'],\n", " l=candle['mid']['l'],\n", " c=candle['mid']['c'],\n", " v=candle['volume'],\n", " )\n", " except Exception as e:\n", " print(e, r)\n", " else:\n", " h.write(rec+\"\\n\")\n", "\n", "datafile = \"/tmp/{}.{}.out\".format(instrument, params['granularity'])\n", "with open(datafile, \"w\") as O:\n", " n = 0\n", " for r in InstrumentsCandlesFactory(instrument=instrument, params=params):\n", " rv = client.request(r)\n", " cnt = len(r.response.get('candles'))\n", " print(\"REQUEST: {} {} {}, received: {}\".format(r, r.__class__.__name__, r.params, cnt))\n", " n += cnt\n", " cnv(r.response, O)\n", " print(\"Check the datafile: {} under /tmp!, it contains {} records\".format(datafile, n))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "## ... that was easy ...\n", "\n", "All request were made on the default of max. 500 records per request. With a granularity of *M5* this means that we have 288 records per day. The algorithm of the factory does not check on weekends or holidays. Therefore some request only return a part of the 500 requested records because there simply are no more records within the specified timespan.\n", "\n", "If you want to decrease the number of requests and increase the number of records returned for a request, just specify *count* as a number higher than 500 and up to max. 5000." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: jupyter/index.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "[accounts](./accounts.ipynb) | [orders](./orders.ipynb) | [trades](./trades.ipynb) | [positions](./positions.ipynb) | [historical](./historical.ipynb) | [streams](./streams.ipynb) | [errors](./exceptions.ipynb)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "# Tutorial oandapyV20\n", "\n", "## Installing *oandapyV20*\n", "\n", "Installing oandapyV20 from pypi:\n", "\n", "```shell\n", "$ pip install oandapyV20\n", "```\n", "\n", "## Tests\n", "\n", "If you want to run the tests, clone the repository:\n", "\n", "```bash\n", "\n", " $ git clone https://github.com/hootnot/oanda-api-v20\n", " $ cd oanda-api-v20\n", "\n", " # install necessary packages for testing\n", " $ grep \"\\- pip install\" .travis.yml |\n", " > while read LNE\n", " > do `echo $LNE| cut -c2-` ; done\n", "\n", " $ python setup.py test\n", " $ python setup.py install\n", "```\n", "\n", "## Jupyter\n", "\n", "To use this *Jupyter notebook* you need to install *Jupyter*:\n", "\n", "```shell\n", "$ pip install jupyter\n", "```\n", "\n", "See, [http://jupyter.org/install.html]() for all details regarding Jupyter.\n", "\n", "When installed you can start Jupyter from the cloned repo directory:\n", "\n", "```shell\n", "$ cd jupyter\n", "# provide your V20-accountID and token by editing account.txt and token.txt\n", "$ jupyter notebook\n", "# ... will launch your brouwser with the notebook tree structure.\n", "```\n", "\n", "\n", "## Examples\n", "\n", "Examples are provided in the https://github.com/hootnot/oandapyV20-examples repository.\n", "\n", "\n", "## Design\n", "\n", "The design of the oandapyV20 library differs from the library covering the REST-V1 interface:\n", "https://github.com/oanda/oandapy (oandapy), which is based on 'mixin' classes.\n", "\n", "### Client\n", "\n", "The V20-library has a client class (API) which processes all APIRequest objects. Status\n", "and response are properties of the request and are assigned by the client when performing \n", "the request. The response is also returned by the client. \n", "\n", "### Requests\n", "\n", "In the V20-library endpoints are represented as APIRequest objects derived from the\n", "APIRequest base class. Each endpoint group (accounts, trades, etc.) is represented\n", "by it's own (abstract) class covering the functionality of all endpoints for that group.\n", "Each endpoint within that group is covered by a class derived from the abstract class.\n", "\n", "## oandapyV20 step by step\n", "\n", "* [accounts](./accounts.ipynb)\n", "* [orders](./orders.ipynb)\n", "* [trades](./trades.ipynb)\n", "* [positions](./positions.ipynb)\n", "* [streams](./streams.ipynb)\n", "* [errors](./exceptions.ipynb)\n", "\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: jupyter/orders.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "[index](./index.ipynb) | [accounts](./accounts.ipynb) | [orders](./orders.ipynb) | [trades](./trades.ipynb) | [positions](./positions.ipynb) | [historical](./historical.ipynb) | [streams](./streams.ipynb) | [errors](./exceptions.ipynb)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "## Orders\n", "\n", "This notebook provides an example of\n", "\n", " + a MarketOrder\n", " + a simplyfied way for a MarketOrder by using contrib.requests.MarketOrderRequest\n", " + a LimitOrder with an expiry datetime by using *GTD* and contrib.requests.LimitOrderRequest\n", " + canceling a GTD order\n", "\n", "### create a marketorder request with a TakeProfit and a StopLoss order when it gets filled." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Request: v3/accounts/101-004-1435156-001/orders\n", "MarketOrder specs: {\n", " \"order\": {\n", " \"timeInForce\": \"FOK\",\n", " \"instrument\": \"EUR_USD\",\n", " \"stopLossOnFill\": {\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.07\"\n", " },\n", " \"positionFill\": \"DEFAULT\",\n", " \"units\": 10000,\n", " \"takeProfitOnFill\": {\n", " \"timeInForce\": \"GTC\",\n", " \"price\": 1.1\n", " },\n", " \"type\": \"MARKET\"\n", " }\n", "}\n" ] } ], "source": [ "import json\n", "import oandapyV20\n", "import oandapyV20.endpoints.orders as orders\n", "from exampleauth import exampleauth\n", "\n", "accountID, access_token = exampleauth.exampleAuth()\n", "client = oandapyV20.API(access_token=access_token)\n", "\n", "\n", "# create a market order to enter a LONG position 10000 EUR_USD, stopLoss @1.07 takeProfit @1.10 ( current: 1.055)\n", "# according to the docs at developer.oanda.com the requestbody looks like:\n", "\n", "mktOrder = {\n", " \"order\": {\n", " \"timeInForce\": \"FOK\", # Fill-or-kill\n", " \"instrument\": \"EUR_USD\",\n", " \"positionFill\": \"DEFAULT\",\n", " \"type\": \"MARKET\",\n", " \"units\": 10000, # as integer\n", " \"takeProfitOnFill\": {\n", " \"timeInForce\": \"GTC\", # Good-till-cancelled\n", " \"price\": 1.10 # as float\n", " },\n", " \"stopLossOnFill\": {\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.07\" # as string\n", " }\n", " }\n", "}\n", "r = orders.OrderCreate(accountID=accountID, data=mktOrder)\n", "\n", "print(\"Request: \", r)\n", "print(\"MarketOrder specs: \", json.dumps(mktOrder, indent=2))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "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.\n", "\n", "This kind of datastructures can become complex, are not easy to read or construct and are prone to errors.\n", "\n", "## Types and definitions\n", "\n", "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.\n", "\n", "## Contrib.requests\n", "\n", "The *oandapyV20.contrib.requests* package offers classes providing an easy way to construct the data for\n", "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*.\n", "\n", "Let's improve the previous example by making use of *oandapyV20.contrib.requests*:\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Request: v3/accounts/101-004-1435156-001/orders\n", "MarketOrder specs: {\n", " \"order\": {\n", " \"timeInForce\": \"FOK\",\n", " \"instrument\": \"EUR_USD\",\n", " \"positionFill\": \"DEFAULT\",\n", " \"type\": \"MARKET\",\n", " \"units\": \"10000\",\n", " \"takeProfitOnFill\": {\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.10000\"\n", " },\n", " \"stopLossOnFill\": {\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.07000\"\n", " }\n", " }\n", "}\n" ] } ], "source": [ "import json\n", "import oandapyV20\n", "import oandapyV20.endpoints.orders as orders\n", "from oandapyV20.contrib.requests import (\n", " MarketOrderRequest,\n", " TakeProfitDetails,\n", " StopLossDetails)\n", "from exampleauth import exampleauth\n", "\n", "accountID, access_token = exampleauth.exampleAuth()\n", "client = oandapyV20.API(access_token=access_token)\n", "\n", "# create a market order to enter a LONG position 10000 EUR_USD\n", "mktOrder = MarketOrderRequest(instrument=\"EUR_USD\",\n", " units=10000,\n", " takeProfitOnFill=TakeProfitDetails(price=1.10).data,\n", " stopLossOnFill=StopLossDetails(price=1.07).data\n", " ).data\n", "r = orders.OrderCreate(accountID=accountID, data=mktOrder)\n", "\n", "print(\"Request: \", r)\n", "print(\"MarketOrder specs: \", json.dumps(mktOrder, indent=2))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "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.\n", "\n", "Next step, place the order:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Response: 201\n", "{\n", " \"orderCancelTransaction\": {\n", " \"time\": \"2017-03-09T13:17:59.319422181Z\",\n", " \"userID\": 1435156,\n", " \"batchID\": \"7576\",\n", " \"orderID\": \"7576\",\n", " \"id\": \"7577\",\n", " \"type\": \"ORDER_CANCEL\",\n", " \"accountID\": \"101-004-1435156-001\",\n", " \"reason\": \"STOP_LOSS_ON_FILL_LOSS\"\n", " },\n", " \"lastTransactionID\": \"7577\",\n", " \"orderCreateTransaction\": {\n", " \"timeInForce\": \"FOK\",\n", " \"instrument\": \"EUR_USD\",\n", " \"batchID\": \"7576\",\n", " \"accountID\": \"101-004-1435156-001\",\n", " \"units\": \"10000\",\n", " \"takeProfitOnFill\": {\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.10000\"\n", " },\n", " \"time\": \"2017-03-09T13:17:59.319422181Z\",\n", " \"userID\": 1435156,\n", " \"positionFill\": \"DEFAULT\",\n", " \"id\": \"7576\",\n", " \"type\": \"MARKET_ORDER\",\n", " \"stopLossOnFill\": {\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.07000\"\n", " },\n", " \"reason\": \"CLIENT_ORDER\"\n", " },\n", " \"relatedTransactionIDs\": [\n", " \"7576\",\n", " \"7577\"\n", " ]\n", "}\n" ] } ], "source": [ "rv = client.request(r)\n", "print(\"Response: {}\\n{}\".format(r.status_code, json.dumps(rv, indent=2)))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "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/ .\n", "\n", "Lets change the stopLoss level below the current price and place the order once again." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Response: 201\n", "{\n", " \"orderFillTransaction\": {\n", " \"accountBalance\": \"102107.4442\",\n", " \"instrument\": \"EUR_USD\",\n", " \"batchID\": \"7578\",\n", " \"pl\": \"0.0000\",\n", " \"accountID\": \"101-004-1435156-001\",\n", " \"units\": \"10000\",\n", " \"tradeOpened\": {\n", " \"tradeID\": \"7579\",\n", " \"units\": \"10000\"\n", " },\n", " \"financing\": \"0.0000\",\n", " \"price\": \"1.05563\",\n", " \"userID\": 1435156,\n", " \"orderID\": \"7578\",\n", " \"time\": \"2017-03-09T13:22:13.832587780Z\",\n", " \"id\": \"7579\",\n", " \"type\": \"ORDER_FILL\",\n", " \"reason\": \"MARKET_ORDER\"\n", " },\n", " \"lastTransactionID\": \"7581\",\n", " \"orderCreateTransaction\": {\n", " \"timeInForce\": \"FOK\",\n", " \"instrument\": \"EUR_USD\",\n", " \"batchID\": \"7578\",\n", " \"accountID\": \"101-004-1435156-001\",\n", " \"units\": \"10000\",\n", " \"takeProfitOnFill\": {\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.10000\"\n", " },\n", " \"time\": \"2017-03-09T13:22:13.832587780Z\",\n", " \"userID\": 1435156,\n", " \"positionFill\": \"DEFAULT\",\n", " \"id\": \"7578\",\n", " \"type\": \"MARKET_ORDER\",\n", " \"stopLossOnFill\": {\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.05000\"\n", " },\n", " \"reason\": \"CLIENT_ORDER\"\n", " },\n", " \"relatedTransactionIDs\": [\n", " \"7578\",\n", " \"7579\",\n", " \"7580\",\n", " \"7581\"\n", " ]\n", "}\n" ] } ], "source": [ "mktOrder = MarketOrderRequest(instrument=\"EUR_USD\",\n", " units=10000,\n", " takeProfitOnFill=TakeProfitDetails(price=1.10).data,\n", " stopLossOnFill=StopLossDetails(price=1.05).data\n", " ).data\n", "r = orders.OrderCreate(accountID=accountID, data=mktOrder)\n", "rv = client.request(r)\n", "\n", "print(\"Response: {}\\n{}\".format(r.status_code, json.dumps(rv, indent=2)))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "We now see an *orderFillTransaction* for 10000 units EUR_USD with *reason* **MARKET_ORDER**.\n", "\n", "Lets retrieve the orders. We should see the *stopLoss* and *takeProfit* orders as *pending*:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Response:\n", " {\n", " \"lastTransactionID\": \"7581\",\n", " \"orders\": [\n", " {\n", " \"createTime\": \"2017-03-09T13:22:13.832587780Z\",\n", " \"triggerCondition\": \"TRIGGER_DEFAULT\",\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.05000\",\n", " \"tradeID\": \"7579\",\n", " \"id\": \"7581\",\n", " \"state\": \"PENDING\",\n", " \"type\": \"STOP_LOSS\"\n", " },\n", " {\n", " \"createTime\": \"2017-03-09T13:22:13.832587780Z\",\n", " \"triggerCondition\": \"TRIGGER_DEFAULT\",\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.10000\",\n", " \"tradeID\": \"7579\",\n", " \"id\": \"7580\",\n", " \"state\": \"PENDING\",\n", " \"type\": \"TAKE_PROFIT\"\n", " },\n", " {\n", " \"createTime\": \"2017-03-09T11:45:48.928448770Z\",\n", " \"triggerCondition\": \"TRIGGER_DEFAULT\",\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.05000\",\n", " \"tradeID\": \"7572\",\n", " \"id\": \"7574\",\n", " \"state\": \"PENDING\",\n", " \"type\": \"STOP_LOSS\"\n", " },\n", " {\n", " \"createTime\": \"2017-03-07T09:18:51.563637768Z\",\n", " \"triggerCondition\": \"TRIGGER_DEFAULT\",\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.05000\",\n", " \"tradeID\": \"7562\",\n", " \"id\": \"7564\",\n", " \"state\": \"PENDING\",\n", " \"type\": \"STOP_LOSS\"\n", " },\n", " {\n", " \"createTime\": \"2017-03-07T09:08:04.219010730Z\",\n", " \"triggerCondition\": \"TRIGGER_DEFAULT\",\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.05000\",\n", " \"tradeID\": \"7558\",\n", " \"id\": \"7560\",\n", " \"state\": \"PENDING\",\n", " \"type\": \"STOP_LOSS\"\n", " }\n", " ]\n", "}\n" ] } ], "source": [ "r = orders.OrdersPending(accountID=accountID)\n", "rv = client.request(r)\n", "print(\"Response:\\n\", json.dumps(rv, indent=2))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "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().\n", "\n", "Now lets cancel all pending TAKE_PROFIT orders:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Request: v3/accounts/101-004-1435156-001/orders/7580/cancel ... response: {\n", " \"orderCancelTransaction\": {\n", " \"time\": \"2017-03-09T13:26:07.480994423Z\",\n", " \"userID\": 1435156,\n", " \"batchID\": \"7582\",\n", " \"orderID\": \"7580\",\n", " \"id\": \"7582\",\n", " \"type\": \"ORDER_CANCEL\",\n", " \"accountID\": \"101-004-1435156-001\",\n", " \"reason\": \"CLIENT_REQUEST\"\n", " },\n", " \"lastTransactionID\": \"7582\",\n", " \"relatedTransactionIDs\": [\n", " \"7582\"\n", " ]\n", "}\n" ] } ], "source": [ "r = orders.OrdersPending(accountID=accountID)\n", "rv = client.request(r)\n", "idsToCancel = [order.get('id') for order in rv['orders'] if order.get('type') == \"TAKE_PROFIT\"]\n", "for orderID in idsToCancel:\n", " r = orders.OrderCancel(accountID=accountID, orderID=orderID)\n", " rv = client.request(r)\n", " print(\"Request: {} ... response: {}\".format(r, json.dumps(rv, indent=2)))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "### create a LimitOrder with a *GTD* \"good-til-date\"\n", "\n", "Create a LimitOrder and let it expire: *2018-07-02T00:00:00* using *GTD*. Make sure it is in the future\n", "when you run this example!" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"order\": {\n", " \"price\": \"1.08000\",\n", " \"timeInForce\": \"GTD\",\n", " \"positionFill\": \"DEFAULT\",\n", " \"type\": \"LIMIT\",\n", " \"instrument\": \"EUR_USD\",\n", " \"gtdTime\": \"2018-07-02T00:00:00\",\n", " \"units\": \"10000\"\n", " }\n", "}\n", "{\n", " \"relatedTransactionIDs\": [\n", " \"8923\"\n", " ],\n", " \"lastTransactionID\": \"8923\",\n", " \"orderCreateTransaction\": {\n", " \"price\": \"1.08000\",\n", " \"triggerCondition\": \"DEFAULT\",\n", " \"positionFill\": \"DEFAULT\",\n", " \"type\": \"LIMIT_ORDER\",\n", " \"requestID\": \"42440345970496965\",\n", " \"partialFill\": \"DEFAULT\",\n", " \"gtdTime\": \"2018-07-02T04:00:00.000000000Z\",\n", " \"batchID\": \"8923\",\n", " \"id\": \"8923\",\n", " \"userID\": 1435156,\n", " \"accountID\": \"101-004-1435156-001\",\n", " \"timeInForce\": \"GTD\",\n", " \"reason\": \"CLIENT_ORDER\",\n", " \"instrument\": \"EUR_USD\",\n", " \"time\": \"2018-06-10T12:06:30.259079220Z\",\n", " \"units\": \"10000\"\n", " }\n", "}\n" ] } ], "source": [ "from oandapyV20.contrib.requests import LimitOrderRequest\n", "\n", "# make sure GTD_TIME is in the future \n", "# also make sure the price condition is not met\n", "# and specify GTD_TIME as UTC or local\n", "# GTD_TIME=\"2018-07-02T00:00:00Z\" # UTC\n", "GTD_TIME=\"2018-07-02T00:00:00\"\n", "ordr = LimitOrderRequest(instrument=\"EUR_USD\",\n", " units=10000,\n", " timeInForce=\"GTD\",\n", " gtdTime=GTD_TIME,\n", " price=1.08)\n", "print(json.dumps(ordr.data, indent=4))\n", "r = orders.OrderCreate(accountID=accountID, data=ordr.data)\n", "rv = client.request(r)\n", "print(json.dumps(rv, indent=2))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "### Request the pending orders" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"orders\": [\n", " {\n", " \"price\": \"1.08000\",\n", " \"triggerCondition\": \"DEFAULT\",\n", " \"state\": \"PENDING\",\n", " \"positionFill\": \"DEFAULT\",\n", " \"partialFill\": \"DEFAULT_FILL\",\n", " \"gtdTime\": \"2018-07-02T04:00:00.000000000Z\",\n", " \"id\": \"8923\",\n", " \"timeInForce\": \"GTD\",\n", " \"type\": \"LIMIT\",\n", " \"instrument\": \"EUR_USD\",\n", " \"createTime\": \"2018-06-10T12:06:30.259079220Z\",\n", " \"units\": \"10000\"\n", " }\n", " ],\n", " \"lastTransactionID\": \"8923\"\n", "}\n" ] } ], "source": [ "r = orders.OrdersPending(accountID=accountID)\n", "rv = client.request(r)\n", "print(json.dumps(rv, indent=2))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "### Cancel the GTD order\n", "\n", "Fetch the *orderID* from the *pending orders* and cancel the order." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"relatedTransactionIDs\": [\n", " \"8924\"\n", " ],\n", " \"orderCancelTransaction\": {\n", " \"accountID\": \"101-004-1435156-001\",\n", " \"time\": \"2018-06-10T12:07:35.453416669Z\",\n", " \"orderID\": \"8923\",\n", " \"reason\": \"CLIENT_REQUEST\",\n", " \"requestID\": \"42440346243149289\",\n", " \"type\": \"ORDER_CANCEL\",\n", " \"batchID\": \"8924\",\n", " \"id\": \"8924\",\n", " \"userID\": 1435156\n", " },\n", " \"lastTransactionID\": \"8924\"\n", "}\n" ] } ], "source": [ "r = orders.OrderCancel(accountID=accountID, orderID=8923)\n", "rv = client.request(r)\n", "print(json.dumps(rv, indent=2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Request pendig orders once again ... the 8923 should be gone" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"orders\": [],\n", " \"lastTransactionID\": \"8924\"\n", "}\n" ] } ], "source": [ "r = orders.OrdersPending(accountID=accountID)\n", "rv = client.request(r)\n", "print(json.dumps(rv, indent=2))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: jupyter/positions.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "[index](./index.ipynb) | [accounts](./accounts.ipynb) | [orders](./orders.ipynb) | [trades](./trades.ipynb) | [positions](./positions.ipynb) | [historical](./historical.ipynb) | [streams](./streams.ipynb) | [errors](./exceptions.ipynb)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "## Open Positions\n", "\n", "Lets get the open positions. It should show the total units LONG and SHORT per instrument and the *tradeID's* associated with the position." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Response: {\n", " \"positions\": [\n", " {\n", " \"pl\": \"-752.7628\",\n", " \"resettablePL\": \"-752.7628\",\n", " \"unrealizedPL\": \"-128.4516\",\n", " \"short\": {\n", " \"units\": \"0\",\n", " \"pl\": \"-101.7859\",\n", " \"resettablePL\": \"-101.7859\",\n", " \"unrealizedPL\": \"0.0000\"\n", " },\n", " \"instrument\": \"EUR_USD\",\n", " \"long\": {\n", " \"units\": \"50000\",\n", " \"pl\": \"-650.9769\",\n", " \"resettablePL\": \"-650.9769\",\n", " \"averagePrice\": \"1.05836\",\n", " \"unrealizedPL\": \"-128.4516\",\n", " \"tradeIDs\": [\n", " \"4224\",\n", " \"7558\",\n", " \"7562\",\n", " \"7572\",\n", " \"7579\"\n", " ]\n", " }\n", " }\n", " ],\n", " \"lastTransactionID\": \"7582\"\n", "}\n" ] } ], "source": [ "import json\n", "import oandapyV20\n", "import oandapyV20.endpoints.positions as positions\n", "from exampleauth import exampleauth\n", "\n", "accountID, access_token = exampleauth.exampleAuth()\n", "client = oandapyV20.API(access_token=access_token)\n", "\n", "r = positions.OpenPositions(accountID=accountID)\n", "rv = client.request(r)\n", "print(\"Response: \", json.dumps(rv, indent=2))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: jupyter/streams.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "[index](./index.ipynb) | [accounts](./accounts.ipynb) | [orders](./orders.ipynb) | [trades](./trades.ipynb) | [positions](./positions.ipynb) | [historical](./historical.ipynb) | [streams](./streams.ipynb) | [errors](./exceptions.ipynb)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "## Streaming data (prices & events)\n", "\n", "The REST-V20 API offers *streaming prices* and *streaming events*. Both can be simply accessed as the next example will show." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'asks': [{'liquidity': 10000000.0, 'price': '1.05534'}, {'liquidity': 10000000.0, 'price': '1.05536'}], 'tradeable': True, 'instrument': 'EUR_USD', 'type': 'PRICE', 'closeoutAsk': '1.05538', 'closeoutBid': '1.05518', 'time': '2017-03-09T13:37:46.048197280Z', 'status': 'tradeable', 'bids': [{'liquidity': 10000000.0, 'price': '1.05522'}, {'liquidity': 10000000.0, 'price': '1.05520'}]}\n", "{'asks': [{'liquidity': 1000000.0, 'price': '120.991'}, {'liquidity': 2000000.0, 'price': '120.992'}, {'liquidity': 5000000.0, 'price': '120.993'}, {'liquidity': 10000000.0, 'price': '120.995'}], 'tradeable': True, 'instrument': 'EUR_JPY', 'type': 'PRICE', 'closeoutAsk': '120.995', 'closeoutBid': '120.965', 'time': '2017-03-09T13:37:44.958026355Z', 'status': 'tradeable', 'bids': [{'liquidity': 1000000.0, 'price': '120.969'}, {'liquidity': 2000000.0, 'price': '120.968'}, {'liquidity': 5000000.0, 'price': '120.967'}, {'liquidity': 10000000.0, 'price': '120.965'}]}\n", "{'asks': [{'liquidity': 1000000.0, 'price': '120.995'}, {'liquidity': 2000000.0, 'price': '120.996'}, {'liquidity': 5000000.0, 'price': '120.997'}, {'liquidity': 10000000.0, 'price': '120.999'}], 'tradeable': True, 'instrument': 'EUR_JPY', 'type': 'PRICE', 'closeoutAsk': '120.999', 'closeoutBid': '120.971', 'time': '2017-03-09T13:37:47.550550833Z', 'status': 'tradeable', 'bids': [{'liquidity': 1000000.0, 'price': '120.975'}, {'liquidity': 2000000.0, 'price': '120.974'}, {'liquidity': 5000000.0, 'price': '120.973'}, {'liquidity': 10000000.0, 'price': '120.971'}]}\n", "{'asks': [{'liquidity': 10000000.0, 'price': '1.05527'}, {'liquidity': 10000000.0, 'price': '1.05529'}], 'tradeable': True, 'instrument': 'EUR_USD', 'type': 'PRICE', 'closeoutAsk': '1.05531', 'closeoutBid': '1.05510', 'time': '2017-03-09T13:37:49.349544372Z', 'status': 'tradeable', 'bids': [{'liquidity': 10000000.0, 'price': '1.05514'}, {'liquidity': 10000000.0, 'price': '1.05512'}]}\n", "Stream processing ended because we made it stop after 3 ticks\n" ] } ], "source": [ "import json\n", "import oandapyV20\n", "import oandapyV20.endpoints.pricing as pricing\n", "from oandapyV20.exceptions import StreamTerminated\n", "from exampleauth import exampleauth\n", "\n", "accountID, access_token = exampleauth.exampleAuth()\n", "client = oandapyV20.API(access_token=access_token)\n", "\n", "instruments = [\"EUR_USD\", \"EUR_JPY\"]\n", "r = pricing.PricingStream(accountID=accountID, params={\"instruments\": \",\".join(instruments)})\n", "\n", "n = 0\n", "stopAfter = 3 # let's terminate after receiving 3 ticks\n", "try:\n", " # the stream requests returns a generator so we can do ...\n", " for tick in client.request(r):\n", " print(tick)\n", " if n >= stopAfter:\n", " r.terminate()\n", " n += 1\n", " \n", "except StreamTerminated as err:\n", " print(\"Stream processing ended because we made it stop after {} ticks\".format(n))" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'lastTransactionID': '7582', 'time': '2017-03-09T15:08:57.512620407Z', 'type': 'HEARTBEAT'}\n", "{'lastTransactionID': '7582', 'time': '2017-03-09T15:09:02.594588344Z', 'type': 'HEARTBEAT'}\n", "{'lastTransactionID': '7582', 'time': '2017-03-09T15:09:07.657436396Z', 'type': 'HEARTBEAT'}\n", "{'lastTransactionID': '7582', 'time': '2017-03-09T15:09:12.721645564Z', 'type': 'HEARTBEAT'}\n", "Stream processing ended because we made it stop after 3 ticks\n" ] } ], "source": [ "import json\n", "import oandapyV20\n", "import oandapyV20.endpoints.transactions as trans\n", "from oandapyV20.exceptions import StreamTerminated\n", "from exampleauth import exampleauth\n", "\n", "accountID, access_token = exampleauth.exampleAuth()\n", "client = oandapyV20.API(access_token=access_token)\n", "\n", "instruments = [\"EUR_USD\", \"EUR_JPY\"]\n", "r = trans.TransactionsStream(accountID=accountID)\n", "\n", "n = 0\n", "stopAfter = 3 # let's terminate after receiving 3 ticks\n", "try:\n", " # the stream requests returns a generator so we can do ...\n", " for T in client.request(r):\n", " print(T)\n", " if n >= stopAfter:\n", " r.terminate()\n", " n += 1\n", " \n", "except StreamTerminated as err:\n", " print(\"Stream processing ended because we made it stop after {} ticks\".format(n))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: jupyter/token.txt ================================================ aaaabbbbccccddddeeeeffffaaaabbbb-ccccbbbbaaaaeeeebbbbaaaaffffbbbb ================================================ FILE: jupyter/trades.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "[index](./index.ipynb) | [accounts](./accounts.ipynb) | [orders](./orders.ipynb) | [trades](./trades.ipynb) | [positions](./positions.ipynb) | [historical](./historical.ipynb) | [streams](./streams.ipynb) | [errors](./exceptions.ipynb)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "## Open Trades\n", "\n", "The previously executed Market-Order resulted in a *trade: LONG 10000 EUR_USD*. This results also in a *Position*.\n", "The position will be 10000 units also unless there were other trades opened in the same instrument. In that case the last 10000 units will be added to it. \n", "\n", "Example: get the open trades. The trade that was opened has the id 7562. It should be in the list of trades that we get by performing an OpenTrades request. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Response: {\n", " \"lastTransactionID\": \"7582\",\n", " \"trades\": [\n", " {\n", " \"state\": \"OPEN\",\n", " \"financing\": \"0.0000\",\n", " \"stopLossOrder\": {\n", " \"state\": \"PENDING\",\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.05000\",\n", " \"triggerCondition\": \"TRIGGER_DEFAULT\",\n", " \"createTime\": \"2017-03-09T13:22:13.832587780Z\",\n", " \"id\": \"7581\",\n", " \"type\": \"STOP_LOSS\",\n", " \"tradeID\": \"7579\"\n", " },\n", " \"realizedPL\": \"0.0000\",\n", " \"price\": \"1.05563\",\n", " \"unrealizedPL\": \"-0.8526\",\n", " \"instrument\": \"EUR_USD\",\n", " \"initialUnits\": \"10000\",\n", " \"id\": \"7579\",\n", " \"openTime\": \"2017-03-09T13:22:13.832587780Z\",\n", " \"currentUnits\": \"10000\"\n", " },\n", " {\n", " \"state\": \"OPEN\",\n", " \"financing\": \"0.0000\",\n", " \"stopLossOrder\": {\n", " \"state\": \"PENDING\",\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.05000\",\n", " \"triggerCondition\": \"TRIGGER_DEFAULT\",\n", " \"createTime\": \"2017-03-09T11:45:48.928448770Z\",\n", " \"id\": \"7574\",\n", " \"type\": \"STOP_LOSS\",\n", " \"tradeID\": \"7572\"\n", " },\n", " \"realizedPL\": \"0.0000\",\n", " \"price\": \"1.05580\",\n", " \"unrealizedPL\": \"-2.4632\",\n", " \"instrument\": \"EUR_USD\",\n", " \"initialUnits\": \"10000\",\n", " \"id\": \"7572\",\n", " \"openTime\": \"2017-03-09T11:45:48.928448770Z\",\n", " \"currentUnits\": \"10000\"\n", " },\n", " {\n", " \"state\": \"OPEN\",\n", " \"financing\": \"-0.2693\",\n", " \"stopLossOrder\": {\n", " \"state\": \"PENDING\",\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.05000\",\n", " \"triggerCondition\": \"TRIGGER_DEFAULT\",\n", " \"createTime\": \"2017-03-07T09:18:51.563637768Z\",\n", " \"id\": \"7564\",\n", " \"type\": \"STOP_LOSS\",\n", " \"tradeID\": \"7562\"\n", " },\n", " \"realizedPL\": \"0.0000\",\n", " \"price\": \"1.05837\",\n", " \"unrealizedPL\": \"-26.8109\",\n", " \"instrument\": \"EUR_USD\",\n", " \"initialUnits\": \"10000\",\n", " \"id\": \"7562\",\n", " \"openTime\": \"2017-03-07T09:18:51.563637768Z\",\n", " \"currentUnits\": \"10000\"\n", " },\n", " {\n", " \"state\": \"OPEN\",\n", " \"financing\": \"-0.2706\",\n", " \"stopLossOrder\": {\n", " \"state\": \"PENDING\",\n", " \"timeInForce\": \"GTC\",\n", " \"price\": \"1.05000\",\n", " \"triggerCondition\": \"TRIGGER_DEFAULT\",\n", " \"createTime\": \"2017-03-07T09:08:04.219010730Z\",\n", " \"id\": \"7560\",\n", " \"type\": \"STOP_LOSS\",\n", " \"tradeID\": \"7558\"\n", " },\n", " \"realizedPL\": \"0.0000\",\n", " \"price\": \"1.05820\",\n", " \"unrealizedPL\": \"-25.2004\",\n", " \"instrument\": \"EUR_USD\",\n", " \"initialUnits\": \"10000\",\n", " \"id\": \"7558\",\n", " \"openTime\": \"2017-03-07T09:08:04.219010730Z\",\n", " \"currentUnits\": \"10000\"\n", " },\n", " {\n", " \"state\": \"OPEN\",\n", " \"financing\": \"-4.7102\",\n", " \"realizedPL\": \"0.0000\",\n", " \"price\": \"1.06381\",\n", " \"unrealizedPL\": \"-78.3485\",\n", " \"instrument\": \"EUR_USD\",\n", " \"initialUnits\": \"10000\",\n", " \"id\": \"4224\",\n", " \"openTime\": \"2017-02-10T21:50:02.670154087Z\",\n", " \"currentUnits\": \"10000\"\n", " }\n", " ]\n", "}\n" ] } ], "source": [ "import json\n", "import oandapyV20\n", "import oandapyV20.endpoints.trades as trades\n", "from exampleauth import exampleauth\n", "\n", "accountID, access_token = exampleauth.exampleAuth()\n", "client = oandapyV20.API(access_token=access_token)\n", "\n", "r = trades.OpenTrades(accountID=accountID)\n", "rv = client.request(r)\n", "print(\"Response: \", json.dumps(rv, indent=2))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: oandapyV20/__init__.py ================================================ import logging from .oandapyV20 import API from .exceptions import V20Error __title__ = "OANDA REST V20 API Wrapper" __version__ = "0.7.2" __author__ = "Feite Brekeveld" __license__ = "MIT" __copyright__ = "Copyright 2016 - 2018 Feite Brekeveld" # Version synonym VERSION = __version__ # Set default logging handler to avoid "No handler found" warnings. try: from logging import NullHandler except ImportError: class NullHandler(logging.Handler): def emit(self, record): pass logging.getLogger(__name__).addHandler(NullHandler()) __all__ = ( 'API', 'V20Error' ) ================================================ FILE: oandapyV20/contrib/__init__.py ================================================ ================================================ FILE: oandapyV20/contrib/factories/__init__.py ================================================ from .history import InstrumentsCandlesFactory __all__ = ( 'InstrumentsCandlesFactory', ) ================================================ FILE: oandapyV20/contrib/factories/history.py ================================================ # -*- coding: utf-8 -*- from datetime import datetime import calendar import logging import oandapyV20.endpoints.instruments as instruments from oandapyV20.contrib.generic import granularity_to_time, secs2time logger = logging.getLogger(__name__) MAX_BATCH = 5000 DEFAULT_BATCH = 500 def InstrumentsCandlesFactory(instrument, params=None): """InstrumentsCandlesFactory - generate InstrumentCandles requests. InstrumentsCandlesFactory is used to retrieve historical data by automatically generating consecutive requests when the OANDA limit of *count* records is exceeded. This is known by calculating the number of candles between *from* and *to*. If *to* is not specified *to* will be equal to *now*. The *count* parameter is only used to control the number of records to retrieve in a single request. The *includeFirst* parameter is forced to make sure that results do no have a 1-record gap between consecutive requests. Parameters ---------- instrument : string (required) the instrument to create the order for params: params (optional) the parameters to specify the historical range, see the REST-V20 docs regarding 'instrument' at developer.oanda.com If no params are specified, just a single InstrumentsCandles request will be generated acting the same as if you had just created it directly. Example ------- The *oandapyV20.API* client processes requests as objects. So, downloading large historical batches simply comes down to: >>> import json >>> from oandapyV20 import API >>> from oandapyV20.contrib.factories import InstrumentsCandlesFactory >>> >>> client = API(access_token=...) >>> instrument, granularity = "EUR_USD", "M15" >>> _from = "2017-01-01T00:00:00Z" >>> params = { ... "from": _from, ... "granularity": granularity, ... "count": 2500, ... } >>> with open("/tmp/{}.{}".format(instrument, granularity), "w") as OUT: >>> # The factory returns a generator generating consecutive >>> # requests to retrieve full history from date 'from' till 'to' >>> for r in InstrumentsCandlesFactory(instrument=instrument, ... params=params) >>> client.request(r) >>> OUT.write(json.dumps(r.response.get('candles'), indent=2)) .. note:: Normally you can't combine *from*, *to* and *count*. When *count* specified, it is used to calculate the gap between *to* and *from*. The *params* passed to the generated request itself does contain the *count* parameter. """ RFC3339 = "%Y-%m-%dT%H:%M:%SZ" # if not specified use the default of 'S5' as OANDA does gs = granularity_to_time(params.get('granularity', 'S5')) _from = None _epoch_from = None if 'from' in params: _from = datetime.strptime(params.get('from'), RFC3339) _epoch_from = int(calendar.timegm(_from.timetuple())) _to = datetime.utcnow() if 'to' in params: _tmp = datetime.strptime(params.get('to'), RFC3339) # if specified datetime > now, we use 'now' instead if _tmp > _to: logger.info("datetime %s is in the future, will be set to 'now'", params.get('to')) else: _to = _tmp _epoch_to = int(calendar.timegm(_to.timetuple())) _count = params.get('count', DEFAULT_BATCH) # OANDA will respond with a V20Error if count > MAX_BATCH if 'to' in params and 'from' not in params: raise ValueError("'to' specified without 'from'") if not params or 'from' not in params: yield instruments.InstrumentsCandles(instrument=instrument, params=params) else: delta = _epoch_to - _epoch_from nbars = delta / gs cpparams = params.copy() for k in ['count', 'from', 'to']: if k in cpparams: del cpparams[k] # force includeFirst cpparams.update({"includeFirst": True}) # generate InstrumentsCandles requests for all 'bars', each request # requesting max. count records for _ in range(_count, int(((nbars//_count)+1))*_count+1, _count): to = _epoch_from + _count * gs if to > _epoch_to: to = _epoch_to yparams = cpparams.copy() yparams.update({"from": secs2time(_epoch_from).strftime(RFC3339)}) yparams.update({"to": secs2time(to).strftime(RFC3339)}) yield instruments.InstrumentsCandles(instrument=instrument, params=yparams) _epoch_from = to ================================================ FILE: oandapyV20/contrib/generic.py ================================================ import re import time from datetime import datetime def secs2time(e): """secs2time - convert epoch to datetime. >>> d = secs2time(1497499200) >>> d datetime.datetime(2017, 6, 15, 4, 0) >>> d.strftime("%Y%m%d-%H:%M:%S") '20170615-04:00:00' """ w = time.gmtime(e) return datetime(*list(w)[0:6]) def granularity_to_time(s): """convert a named granularity into seconds. get value in seconds for named granularities: M1, M5 ... H1 etc. >>> print(granularity_to_time("M5")) 300 """ mfact = { 'S': 1, 'M': 60, 'H': 3600, 'D': 86400, 'W': 604800, } try: f, n = re.match("(?P[SMHDW])(?:(?P\d+)|)", s).groups() n = n if n else 1 return mfact[f] * int(n) except Exception as e: raise ValueError(e) ================================================ FILE: oandapyV20/contrib/requests/__init__.py ================================================ from .marketorder import MarketOrderRequest from .limitorder import LimitOrderRequest from .mitorder import MITOrderRequest from .takeprofitorder import TakeProfitOrderRequest from .stoplossorder import StopLossOrderRequest from .trailingstoplossorder import TrailingStopLossOrderRequest from .stoporder import StopOrderRequest from .positionclose import PositionCloseRequest from .tradeclose import TradeCloseRequest from .onfill import ( TakeProfitDetails, StopLossDetails, TrailingStopLossDetails ) from .extensions import ClientExtensions __all__ = ( 'MarketOrderRequest', 'LimitOrderRequest', 'MITOrderRequest', 'TakeProfitOrderRequest', 'StopLossOrderRequest', 'TrailingStopLossOrderRequest', 'StopOrderRequest', 'PositionCloseRequest', 'TradeCloseRequest', 'TakeProfitDetails', 'StopLossDetails', 'TrailingStopLossDetails', 'ClientExtensions', ) ================================================ FILE: oandapyV20/contrib/requests/baserequest.py ================================================ # -*- coding: utf-8 -*- """baserequest.""" import json import six from abc import ABCMeta, abstractmethod @six.add_metaclass(ABCMeta) class BaseRequest(object): """baseclass for request classes.""" @abstractmethod def __init__(self): self._data = dict() def __repr__(self): return json.dumps(self.__dict__) @property def data(self): """data - return the JSON body. The data property returns a dict representing the JSON-body needed for the API-request. All values that are not set will be left out """ d = dict() for k, v in self._data.items(): # skip unset properties if v is None: continue d.update({k: v}) return d def toJSON(self): return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) ================================================ FILE: oandapyV20/contrib/requests/extensions.py ================================================ # -*- coding: utf-8 -*- from .baserequest import BaseRequest from oandapyV20.types import ClientID, ClientTag, ClientComment class ClientExtensions(BaseRequest): """Representation of the ClientExtensions.""" def __init__(self, clientID=None, clientTag=None, clientComment=None): """Instantiate ClientExtensions. Parameters ---------- clientID : clientID (required) the clientID clientTag : clientTag (required) the clientTag clientComment : clientComment (required) the clientComment Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.orders as orders >>> from oandapyV20.contrib.requests import ( ... MarketOrderRequest, TakeProfitDetails, ClientExtensions) >>> >>> accountID = "..." >>> client = API(access_token=...) >>> # at time of writing EUR_USD = 1.0740 >>> # let us take profit at 1.10, GoodTillCancel (default) >>> # add clientExtensions to it also >>> takeProfitOnFillOrder = TakeProfitDetails( ... price=1.10, ... clientExtensions=ClientExtensions(clientTag="mytag").data) >>> print(takeProfitOnFillOrder.data) { 'timeInForce': 'GTC', 'price": '1.10000', 'clientExtensions': {'tag': 'mytag'} } >>> ordr = MarketOrderRequest( ... instrument="EUR_USD", ... units=10000, ... takeProfitOnFill=takeProfitOnFillOrder.data ... ) >>> # or as shortcut ... >>> # takeProfitOnFill=TakeProfitDetails(price=1.10).data >>> print(json.dumps(ordr.data, indent=4)) >>> r = orders.OrderCreate(accountID, data=ordr.data) >>> rv = client.request(r) >>> ... """ super(ClientExtensions, self).__init__() if not (clientID or clientTag or clientComment): raise ValueError("clientID, clientTag, clientComment required") if clientID: self._data.update({"id": ClientID(clientID).value}) if clientTag: self._data.update({"tag": ClientTag(clientTag).value}) if clientComment: self._data.update({"comment": ClientComment(clientComment).value}) ================================================ FILE: oandapyV20/contrib/requests/limitorder.py ================================================ # -*- coding: utf-8 -*- from .baserequest import BaseRequest from oandapyV20.types import Units, PriceValue from oandapyV20.definitions.orders import ( OrderType, TimeInForce, OrderPositionFill) class LimitOrderRequest(BaseRequest): """create a LimitOrderRequest. LimitOrderRequest is used to build the body for a LimitOrder. The body can be used to pass to the OrderCreate endpoint. """ def __init__(self, instrument, units, price, positionFill=OrderPositionFill.DEFAULT, clientExtensions=None, takeProfitOnFill=None, timeInForce=TimeInForce.GTC, gtdTime=None, stopLossOnFill=None, trailingStopLossOnFill=None, tradeClientExtensions=None): """ Instantiate a LimitOrderRequest. Parameters ---------- instrument : string (required) the instrument to create the order for units: integer (required) the number of units. If positive the order results in a LONG order. If negative the order results in a SHORT order price: float (required) the price indicating the limit. Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.orders as orders >>> from oandapyV20.contrib.requests import LimitOrderRequest >>> >>> accountID = "..." >>> client = API(access_token=...) >>> ordr = LimitOrderRequest(instrument="EUR_USD", ... units=10000, price=1.08) >>> print(json.dumps(ordr.data, indent=4)) { "order": { "timeInForce": "GTC", "instrument": "EUR_USD", "units": "10000", "price": "1.08000", "type": "LIMIT", "positionFill": "DEFAULT" } } >>> r = orders.orderCreate(accountID, data=ordr.data) >>> rv = client.request(r) >>> """ super(LimitOrderRequest, self).__init__() # by default for a LIMIT order self._data.update({"type": OrderType.LIMIT}) self._data.update({"timeInForce": timeInForce}) # required self._data.update({"instrument": instrument}) self._data.update({"units": Units(units).value}) self._data.update({"price": PriceValue(price).value}) # optional, but required if timeInForce.GTD self._data.update({"gtdTime": gtdTime}) if timeInForce == TimeInForce.GTD and not gtdTime: raise ValueError("gtdTime missing") # optional self._data.update({"positionFill": positionFill}) self._data.update({"clientExtensions": clientExtensions}) self._data.update({"takeProfitOnFill": takeProfitOnFill}) self._data.update({"stopLossOnFill": stopLossOnFill}) self._data.update({"trailingStopLossOnFill": trailingStopLossOnFill}) self._data.update({"tradeClientExtensions": tradeClientExtensions}) @property def data(self): """data property. return the JSON order body """ return dict({"order": super(LimitOrderRequest, self).data}) ================================================ FILE: oandapyV20/contrib/requests/marketorder.py ================================================ # -*- coding: utf-8 -*- from .baserequest import BaseRequest from oandapyV20.types import Units, PriceValue from oandapyV20.definitions.orders import ( OrderType, TimeInForce, OrderPositionFill) class MarketOrderRequest(BaseRequest): """create a MarketOrderRequest. MarketOrderRequest is used to build the body for a MarketOrder. The body can be used to pass to the OrderCreate endpoint. """ def __init__(self, instrument, units, priceBound=None, positionFill=OrderPositionFill.DEFAULT, clientExtensions=None, takeProfitOnFill=None, timeInForce=TimeInForce.FOK, stopLossOnFill=None, trailingStopLossOnFill=None, tradeClientExtensions=None): """ Instantiate a MarketOrderRequest. Parameters ---------- instrument : string (required) the instrument to create the order for units: integer (required) the number of units. If positive the order results in a LONG order. If negative the order results in a SHORT order Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.orders as orders >>> from oandapyV20.contrib.requests import MarketOrderRequest >>> >>> accountID = "..." >>> client = API(access_token=...) >>> mo = MarketOrderRequest(instrument="EUR_USD", units=10000) >>> print(json.dumps(mo.data, indent=4)) { "order": { "type": "MARKET", "positionFill": "DEFAULT", "instrument": "EUR_USD", "timeInForce": "FOK", "units": "10000" } } >>> # now we have the order specification, create the order request >>> r = orders.OrderCreate(accountID, data=mo.data) >>> # perform the request >>> rv = client.request(r) >>> print(rv) >>> print(json.dumps(rv, indent=4)) { "orderFillTransaction": { "reason": "MARKET_ORDER", "pl": "0.0000", "accountBalance": "97864.8813", "units": "10000", "instrument": "EUR_USD", "accountID": "101-004-1435156-001", "time": "2016-11-11T19:59:43.253587917Z", "type": "ORDER_FILL", "id": "2504", "financing": "0.0000", "tradeOpened": { "tradeID": "2504", "units": "10000" }, "orderID": "2503", "userID": 1435156, "batchID": "2503", "price": "1.08463" }, "lastTransactionID": "2504", "relatedTransactionIDs": [ "2503", "2504" ], "orderCreateTransaction": { "type": "MARKET_ORDER", "reason": "CLIENT_ORDER", "id": "2503", "timeInForce": "FOK", "units": "10000", "time": "2016-11-11T19:59:43.253587917Z", "positionFill": "DEFAULT", "accountID": "101-004-1435156-001", "instrument": "EUR_USD", "batchID": "2503", "userID": 1435156 } } >>> """ super(MarketOrderRequest, self).__init__() # allowed: FOK/IOC if timeInForce not in [TimeInForce.FOK, TimeInForce.IOC]: raise ValueError("timeInForce: {}".format(timeInForce)) # by default for a MARKET order self._data.update({"type": OrderType.MARKET}) self._data.update({"timeInForce": timeInForce}) # required self._data.update({"instrument": instrument}) self._data.update({"units": Units(units).value}) # optional if priceBound: self._data.update({"priceBound": PriceValue(priceBound).value}) if not hasattr(OrderPositionFill, positionFill): raise ValueError("positionFill {}".format(positionFill)) self._data.update({"positionFill": positionFill}) self._data.update({"clientExtensions": clientExtensions}) self._data.update({"takeProfitOnFill": takeProfitOnFill}) self._data.update({"stopLossOnFill": stopLossOnFill}) self._data.update({"trailingStopLossOnFill": trailingStopLossOnFill}) self._data.update({"tradeClientExtensions": tradeClientExtensions}) @property def data(self): """data property. return the JSON body. """ return dict({"order": super(MarketOrderRequest, self).data}) ================================================ FILE: oandapyV20/contrib/requests/mitorder.py ================================================ # -*- coding: utf-8 -*- from .baserequest import BaseRequest from oandapyV20.types import Units, PriceValue from oandapyV20.definitions.orders import ( OrderType, OrderPositionFill, TimeInForce) class MITOrderRequest(BaseRequest): """create a MarketIfTouched OrderRequest. MITOrderRequest is used to build the body for a MITOrder. The body can be used to pass to the OrderCreate endpoint. """ def __init__(self, instrument, units, price, priceBound=None, positionFill=OrderPositionFill.DEFAULT, timeInForce=TimeInForce.GTC, gtdTime=None, clientExtensions=None, takeProfitOnFill=None, stopLossOnFill=None, trailingStopLossOnFill=None, tradeClientExtensions=None): """ Instantiate an MITOrderRequest. Parameters ---------- instrument : string (required) the instrument to create the order for units: integer (required) the number of units. If positive the order results in a LONG order. If negative the order results in a SHORT order price: float (required) the price indicating the limit. Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.orders as orders >>> from oandapyV20.contrib.requests import MITOrderRequest >>> >>> accountID = "..." >>> client = API(access_token=...) >>> ordr = MITOrderRequest(instrument="EUR_USD", ... units=10000, price=1.08) >>> print(json.dumps(ordr.data, indent=4)) { "order": { "timeInForce": "GTC", "instrument": "EUR_USD", "units": "10000", "price": "1.08000", "type": "MARKET_IF_TOUCHED", "positionFill": "DEFAULT" } } >>> r = orders.OrderCreate(accountID, data=ordr.data) >>> rv = client.request(r) >>> ... """ super(MITOrderRequest, self).__init__() # allowed: GTC/GFD/GTD if timeInForce not in [TimeInForce.GTC, TimeInForce.GTD, TimeInForce.GFD]: raise ValueError("timeInForce: {}".format(timeInForce)) # by default for a MARKET_IF_TOUCHED order self._data.update({"type": OrderType.MARKET_IF_TOUCHED}) # required self._data.update({"timeInForce": timeInForce}) self._data.update({"instrument": instrument}) self._data.update({"units": Units(units).value}) self._data.update({"price": PriceValue(price).value}) # optional, but required if self._data.update({"gtdTime": gtdTime}) if timeInForce == TimeInForce.GTD and not gtdTime: raise ValueError("gtdTime missing") # optional self._data.update({"priceBound": priceBound}) self._data.update({"positionFill": positionFill}) self._data.update({"clientExtensions": clientExtensions}) self._data.update({"takeProfitOnFill": takeProfitOnFill}) self._data.update({"stopLossOnFill": stopLossOnFill}) self._data.update({"trailingStopLossOnFill": trailingStopLossOnFill}) self._data.update({"tradeClientExtensions": tradeClientExtensions}) @property def data(self): """data property. return the JSON order body """ return dict({"order": super(MITOrderRequest, self).data}) ================================================ FILE: oandapyV20/contrib/requests/onfill.py ================================================ # -*- coding: utf-8 -*- import six from abc import ABCMeta, abstractmethod from .baserequest import BaseRequest from oandapyV20.types import PriceValue import oandapyV20.definitions.orders as OD @six.add_metaclass(ABCMeta) class OnFill(BaseRequest): """baseclass for onFill requests.""" @abstractmethod def __init__(self, timeInForce=OD.TimeInForce.GTC, gtdTime=None, clientExtensions=None): super(OnFill, self).__init__() if timeInForce not in [OD.TimeInForce.GTC, OD.TimeInForce.GTD, OD.TimeInForce.GFD]: raise ValueError("timeInForce: {} invalid".format(timeInForce)) self._data.update({"timeInForce": timeInForce}) # optional, but required if if timeInForce == OD.TimeInForce.GTD and not gtdTime: raise ValueError("gtdTime: value required when timeInForce is GTD") self._data.update({"gtdTime": gtdTime}) self._data.update({"clientExtensions": clientExtensions}) class TakeProfitDetails(OnFill): """Representation of the specification for a TakeProfitOrder. It is typically used to specify 'take profit details' for the 'takeProfitOnFill' parameter of an OrderRequest. This way one can create the Take Profit Order as a dependency when an order gets filled. The other way to create a TakeProfitOrder is to create it afterwards on an existing trade. In that case you use TakeProfitOrderRequest on the trade. """ def __init__(self, price, timeInForce=OD.TimeInForce.GTC, gtdTime=None, clientExtensions=None): """Instantiate TakeProfitDetails. Parameters ---------- price : float or string (required) the price to trigger take profit order timeInForce : TimeInForce (required), default TimeInForce.GTC the time in force gtdTime : DateTime (optional) gtdTime is required in case timeInForce == TimeInForce.GTD Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.orders as orders >>> from oandapyV20.contrib.requests import ( >>> MarketOrderRequest, TakeProfitDetails) >>> >>> accountID = "..." >>> client = API(access_token=...) >>> # at time of writing EUR_USD = 1.0740 >>> # let us take profit at 1.10, GoodTillCancel (default) >>> takeProfitOnFillOrder = TakeProfitDetails(price=1.10) >>> print(takeProfitOnFillOrder.data) { "timeInForce": "GTC", "price": "1.10000" } >>> ordr = MarketOrderRequest( >>> instrument="EUR_USD", >>> units=10000, >>> takeProfitOnFill=takeProfitOnFillOrder.data >>> ) >>> # or as shortcut ... >>> # takeProfitOnFill=TakeProfitDetails(price=1.10).data >>> print(json.dumps(ordr.data, indent=4)) { "order": { "timeInForce": "FOK", "instrument": "EUR_USD", "units": "10000", "positionFill": "DEFAULT", "type": "MARKET", "takeProfitOnFill": { "timeInForce": "GTC", "price": "1.10000" } } } >>> r = orders.OrderCreate(accountID, data=ordr.data) >>> rv = client.request(r) >>> ... """ super(TakeProfitDetails, self).__init__( timeInForce=timeInForce, gtdTime=gtdTime, clientExtensions=clientExtensions) self._data.update({"price": PriceValue(price).value}) class StopLossDetails(OnFill): """Representation of the specification for a StopLossOrder. It is typically used to specify 'stop loss details' for the 'stopLossOnFill' parameter of an OrderRequest. This way one can create the Stop Loss Order as a dependency when an order gets filled. The other way to create a StopLossOrder is to create it afterwards on an existing trade. In that case you use StopLossOrderRequest on the trade. """ def __init__(self, price, timeInForce=OD.TimeInForce.GTC, gtdTime=None, clientExtensions=None): """Instantiate StopLossDetails. Parameters ---------- price : float or string (required) the price to trigger take profit order timeInForce : TimeInForce (required), default TimeInForce.GTC the time in force gtdTime : DateTime (optional) gtdTime is required in case timeInForce == TimeInForce.GTD clientExtensions : ClientExtensions (optional) Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.orders as orders >>> from oandapyV20.contrib.requests import ( >>> MarketOrderRequest, StopLossDetails) >>> >>> accountID = "..." >>> client = API(access_token=...) >>> # at time of writing EUR_USD = 1.0740 >>> # let us take profit at 1.10, GoodTillCancel (default) >>> stopLossOnFill = StopLossDetails(price=1.06) >>> print(stopLossOnFill) { "timeInForce": "GTC", "price": "1.10000" } >>> ordr = MarketOrderRequest( >>> instrument="EUR_USD", >>> units=10000, >>> stopLossOnFill=stopLossOnFill.data >>> ) >>> # or as shortcut ... >>> # stopLossOnFill=StopLossDetails(price=1.06).data >>> print(json.dumps(ordr.data, indent=4)) >>> r = orders.OrderCreate(accountID, data=ordr.data) >>> rv = client.request(r) >>> ... """ super(StopLossDetails, self).__init__( timeInForce=timeInForce, gtdTime=gtdTime, clientExtensions=clientExtensions) self._data.update({"price": PriceValue(price).value}) class TrailingStopLossDetails(OnFill): """Representation of the specification for a TrailingStopLossOrder. It is typically used to specify 'trailing stop loss details' for the 'trailingStopLossOnFill' parameter of an OrderRequest. This way one can create the Trailing Stop Loss Order as a dependency when an order gets filled. The other way to create a TrailingStopLossOrder is to create it afterwards on an existing trade. In that case you use TrailingStopLossOrderRequest on the trade. """ def __init__(self, distance, timeInForce=OD.TimeInForce.GTC, gtdTime=None, clientExtensions=None): """Instantiate TrailingStopLossDetails. Parameters ---------- distance : float or string (required) the price to trigger trailing stop loss order timeInForce : TimeInForce (required), default TimeInForce.GTC the time in force gtdTime : DateTime (optional) gtdTime is required in case timeInForce == TimeInForce.GTD clientExtensions : ClientExtensions (optional) Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.orders as orders >>> from oandapyV20.contrib.requests import ( >>> MarketOrderRequest, TrailingStopLossDetails) >>> >>> accountID = "..." >>> client = API(access_token=...) >>> # at time of writing EUR_USD = 1.0740 >>> # add a trailing stoploss, at 50 pips GoodTillCancel (default) >>> sld = 1.0740 - 1.0690 >>> trailingStopLossOnFill = TrailingStopLossDetails(distance=sld) >>> print(trailingStopLossOnFill) { "timeInForce": "GTC", "distance": "0.00500" } >>> ordr = MarketOrderRequest( >>> instrument="EUR_USD", >>> units=10000, >>> trailingStopLossOnFill=trailingStopLossOnFill.data >>> ) >>> print(json.dumps(ordr.data, indent=4)) >>> r = orders.OrderCreate(accountID, data=ordr.data) >>> rv = client.request(r) >>> ... """ super(TrailingStopLossDetails, self).__init__( timeInForce=timeInForce, gtdTime=gtdTime, clientExtensions=clientExtensions) self._data.update({"distance": PriceValue(distance).value}) ================================================ FILE: oandapyV20/contrib/requests/positionclose.py ================================================ # -*- coding: utf-8 -*- """positionclose.""" from .baserequest import BaseRequest from oandapyV20.types import Units class PositionCloseRequest(BaseRequest): """create a PositionCloseRequest. PositionCloseRequest is used to build the body to close a position. The body can be used to pass to the PositionClose endpoint. """ def __init__(self, longUnits=None, longClientExtensions=None, shortUnits=None, shortClientExtensions=None): """ Instantiate a PositionCloseRequest. Parameters ---------- longUnits : integer (optional) the number of long units to close longClientExtensions : dict (optional) dict representing longClientExtensions shortUnits : integer (optional) the number of short units to close shortClientExtensions : dict (optional) dict representing shortClientExtensions One of the parameters or both must be supplied. Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.positions as positions >>> from oandapyV20.contrib.requests import PositionCloseRequest >>> >>> accountID = "..." >>> client = API(access_token=...) >>> ordr = PositionCloseRequest(longUnits=10000) >>> print(json.dumps(ordr.data, indent=4)) { "longUnits": "10000" } >>> # now we have the order specification, create the order request >>> r = position.PositionClose(accountID, >>> instrument="EUR_USD", data=ordr.data) >>> # perform the request >>> rv = client.request(r) >>> print(rv) >>> ... """ super(PositionCloseRequest, self).__init__() if not (longUnits or shortUnits): raise ValueError("longUnits and/or shortUnits parameter required") if longUnits: self._data.update({"longUnits": Units(longUnits).value}) if longClientExtensions: self._data.update({"longClientExtensions": longClientExtensions}) if shortUnits: self._data.update({"shortUnits": Units(shortUnits).value}) if shortClientExtensions: self._data.update({"shortClientExtensions": shortClientExtensions}) ================================================ FILE: oandapyV20/contrib/requests/stoplossorder.py ================================================ # -*- coding: utf-8 -*- from .baserequest import BaseRequest from oandapyV20.types import TradeID, PriceValue from oandapyV20.definitions.orders import TimeInForce, OrderType class StopLossOrderRequest(BaseRequest): """create a StopLossOrderRequest. StopLossOrderRequest is used to build the body for a StopLossOrder. The body can be used to pass to the OrderCreate endpoint. """ def __init__(self, tradeID, price, clientTradeID=None, timeInForce=TimeInForce.GTC, gtdTime=None, clientExtensions=None): """ Instantiate a StopLossOrderRequest. Parameters ---------- tradeID : string (required) the tradeID of an existing trade price : float (required) the treshold price indicating the price to close the order Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.orders as orders >>> from oandapyV20.contrib.requests import StopLossOrderRequest >>> >>> accountID = "..." >>> client = API(access_token=...) >>> ordr = StopLossOrderRequest(tradeID="1234", price=1.07) >>> print(json.dumps(ordr.data, indent=4)) { "order": { "type": "STOP_LOSS", "tradeID": "1234", "price": "1.07000", "timeInForce": "GTC", } } >>> # now we have the order specification, create the order request >>> r = orders.OrderCreate(accountID, data=ordr.data) >>> # perform the request >>> rv = client.request(r) >>> print(json.dumps(rv, indent=4)) >>> ... """ super(StopLossOrderRequest, self).__init__() # allowed: GTC/GFD/GTD if timeInForce not in [TimeInForce.GTC, TimeInForce.GTD, TimeInForce.GFD]: raise ValueError("timeInForce: {}".format(timeInForce)) # by default for a STOP_LOSS order self._data.update({"type": OrderType.STOP_LOSS}) # required self._data.update({"tradeID": TradeID(tradeID).value}) self._data.update({"price": PriceValue(price).value}) # optional self._data.update({"clientExtensions": clientExtensions}) self._data.update({"timeInForce": timeInForce}) self._data.update({"gtdTime": gtdTime}) if timeInForce == TimeInForce.GTD and not gtdTime: raise ValueError("gtdTime missing") @property def data(self): """data property. return the JSON body. """ return dict({"order": super(StopLossOrderRequest, self).data}) ================================================ FILE: oandapyV20/contrib/requests/stoporder.py ================================================ # -*- coding: utf-8 -*- from .baserequest import BaseRequest from oandapyV20.types import Units, PriceValue from oandapyV20.definitions.orders import ( OrderType, OrderPositionFill, TimeInForce) class StopOrderRequest(BaseRequest): """create a StopOrderRequest. StopOrderRequest is used to build the body for an StopOrder. The body can be used to pass to the OrderCreate endpoint. """ def __init__(self, instrument, units, price, priceBound=None, positionFill=OrderPositionFill.DEFAULT, timeInForce=TimeInForce.GTC, gtdTime=None, clientExtensions=None, takeProfitOnFill=None, stopLossOnFill=None, trailingStopLossOnFill=None, tradeClientExtensions=None): """ Instantiate a StopOrderRequest. Parameters ---------- instrument : string (required) the instrument to create the order for units : integer (required) the number of units. If positive the order results in a LONG order. If negative the order results in a SHORT order price : float (required) the treshold price indicating the price to activate the order Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.orders as orders >>> from oandapyV20.contrib.requests import StopOrderRequest >>> >>> accountID = "..." >>> client = API(access_token=...) >>> ordr = StopOrderRequest(instrument="EUR_USD", ... units=10000, price=1.07) >>> print(json.dumps(ordr.data, indent=4)) { "order": { "type": "STOP", "price": "1.07000", "positionFill": "DEFAULT", "instrument": "EUR_USD", "timeInForce": "GTC", "units": "10000" } } >>> # now we have the order specification, create the order request >>> r = orders.OrderCreate(accountID, data=ordr.data) >>> # perform the request >>> rv = client.request(r) >>> print(json.dumps(rv, indent=4)) >>> ... """ super(StopOrderRequest, self).__init__() # by default for a STOP order self._data.update({"type": OrderType.STOP}) # required self._data.update({"instrument": instrument}) self._data.update({"units": Units(units).value}) self._data.update({"price": PriceValue(price).value}) # optional, but required if timeInForce.GTD self._data.update({"gtdTime": gtdTime}) if timeInForce == TimeInForce.GTD and not gtdTime: raise ValueError("gtdTime missing") # optional self._data.update({"timeInForce": timeInForce}) self._data.update({"priceBound": priceBound}) self._data.update({"positionFill": positionFill}) self._data.update({"clientExtensions": clientExtensions}) self._data.update({"takeProfitOnFill": takeProfitOnFill}) self._data.update({"stopLossOnFill": stopLossOnFill}) self._data.update({"trailingStopLossOnFill": trailingStopLossOnFill}) self._data.update({"tradeClientExtensions": tradeClientExtensions}) @property def data(self): """data property. return the JSON body. """ return dict({"order": super(StopOrderRequest, self).data}) ================================================ FILE: oandapyV20/contrib/requests/takeprofitorder.py ================================================ # -*- coding: utf-8 -*- from .baserequest import BaseRequest from oandapyV20.types import TradeID, PriceValue from oandapyV20.definitions.orders import TimeInForce, OrderType class TakeProfitOrderRequest(BaseRequest): """create a TakeProfit OrderRequest. TakeProfitOrderRequest is used to build the body for a TakeProfitOrder. The body can be used to pass to the OrderCreate endpoint. """ def __init__(self, tradeID, price, clientTradeID=None, timeInForce=TimeInForce.GTC, gtdTime=None, clientExtensions=None): """ Instantiate a TakeProfitOrderRequest. Parameters ---------- tradeID : string (required) the tradeID of an existing trade price: float (required) the price indicating the target price to close the order. Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.orders as orders >>> from oandapyV20.contrib.requests import TakeProfitOrderRequest >>> >>> accountID = "..." >>> client = API(access_token=...) >>> ordr = TakeProfitOrderRequest(tradeID="1234", >>> price=1.22) >>> print(json.dumps(ordr.data, indent=4)) { "order": { "timeInForce": "GTC", "price": "1.22000", "type": "TAKE_PROFIT", "tradeID": "1234" } } >>> r = orders.OrderCreate(accountID, data=ordr.data) >>> rv = client.request(r) >>> ... """ super(TakeProfitOrderRequest, self).__init__() # allowed: GTC/GFD/GTD if timeInForce not in [TimeInForce.GTC, TimeInForce.GTD, TimeInForce.GFD]: raise ValueError("timeInForce: {}".format(timeInForce)) # by default for a TAKE_PROFIT order self._data.update({"type": OrderType.TAKE_PROFIT}) self._data.update({"timeInForce": timeInForce}) # required self._data.update({"tradeID": TradeID(tradeID).value}) self._data.update({"price": PriceValue(price).value}) # optional, but required if self._data.update({"gtdTime": gtdTime}) if timeInForce == TimeInForce.GTD and not gtdTime: raise ValueError("gtdTime missing") # optional self._data.update({"clientExtensions": clientExtensions}) @property def data(self): """data property. return the JSON order body """ return dict({"order": super(TakeProfitOrderRequest, self).data}) ================================================ FILE: oandapyV20/contrib/requests/tradeclose.py ================================================ # -*- coding: utf-8 -*- """tradeclose.""" from .baserequest import BaseRequest class TradeCloseRequest(BaseRequest): """create a TradeCloseRequest. TradeCloseRequest is used to build the body to close a trade. The body can be used to pass to the TradeClose endpoint. """ def __init__(self, units="ALL"): """ Instantiate a TradeCloseRequest. Parameters ---------- units : integer (optional) the number of units to close. Default it is set to "ALL". Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.trades as trades >>> from oandapyV20.contrib.requests import TradeCloseRequest >>> >>> accountID = "..." >>> client = API(access_token=...) >>> ordr = TradeCloseRequest(units=10000) >>> print(json.dumps(ordr.data, indent=4)) { "units": "10000" } >>> # now we have the order specification, create the order request >>> r = trades.TradeClose(accountID, tradeID=1234, >>> data=ordr.data) >>> # perform the request >>> rv = client.request(r) >>> print(rv) >>> ... """ super(TradeCloseRequest, self).__init__() # by default for a TradeClose no parameters are required if units: self._data.update( {"units": "{:d}".format(int(units)) if units != "ALL" else "ALL"}) ================================================ FILE: oandapyV20/contrib/requests/trailingstoplossorder.py ================================================ # -*- coding: utf-8 -*- from .baserequest import BaseRequest from oandapyV20.types import TradeID, PriceValue from oandapyV20.definitions.orders import TimeInForce, OrderType class TrailingStopLossOrderRequest(BaseRequest): """create a TrailingStopLossOrderRequest. TrailingStopLossOrderRequest is used to build the body for a TrailingStopLossOrder. The body can be used to pass to the OrderCreate endpoint. """ def __init__(self, tradeID, distance, clientTradeID=None, timeInForce=TimeInForce.GTC, gtdTime=None, clientExtensions=None): """ Instantiate a TrailingStopLossOrderRequest. Parameters ---------- tradeID : string (required) the tradeID of an existing trade distance : float (required) the price distance Example ------- >>> import json >>> from oandapyV20 import API >>> import oandapyV20.endpoints.orders as orders >>> from oandapyV20.contrib.requests import TrailingStopLossOrderRequest >>> >>> accountID = "..." >>> client = API(access_token=...) >>> ordr = TrailingStopLossOrderRequest(tradeID="1234", distance=20) >>> print(json.dumps(ordr.data, indent=4)) { "order": { "type": "TRAILING_STOP_LOSS", "tradeID": "1234", "timeInForce": "GTC", "distance": "20.00000" } } >>> # now we have the order specification, create the order request >>> r = orders.OrderCreate(accountID, data=ordr.data) >>> # perform the request >>> rv = client.request(r) >>> print(json.dumps(rv, indent=4)) >>> ... """ super(TrailingStopLossOrderRequest, self).__init__() # allowed: GTC/GFD/GTD if timeInForce not in [TimeInForce.GTC, TimeInForce.GTD, TimeInForce.GFD]: raise ValueError("timeInForce: {}".format(timeInForce)) # by default for a TRAILING_STOP_LOSS order self._data.update({"type": OrderType.TRAILING_STOP_LOSS}) # required self._data.update({"tradeID": TradeID(tradeID).value}) self._data.update({"distance": PriceValue(distance).value}) # optional self._data.update({"clientExtensions": clientExtensions}) self._data.update({"timeInForce": timeInForce}) self._data.update({"gtdTime": gtdTime}) if timeInForce == TimeInForce.GTD and not gtdTime: raise ValueError("gtdTime missing") @property def data(self): """data property. return the JSON body. """ return dict({"order": super(TrailingStopLossOrderRequest, self).data}) ================================================ FILE: oandapyV20/definitions/__init__.py ================================================ # -*- coding: utf-8 -*- """dynamically add the classes for definition representations. Most of the endpoint groups have some definitions that apply. These are in the definitions package. It is conveniant to have access by a class representing a specific group of definitions instead of a dictionary. """ import sys from importlib import import_module import six dyndoc = """Definition representation of {cls} Definitions used in requests and responses. This class provides the ID and the description of the definitions. >>> import {PTH} as def{mod} >>> print def{mod}.{cls}.{firstItem} {orig} >>> c = def{mod}.{cls}() >>> print c[c.{firstItem}] {firstItemVal} >>> # or >>> print def{mod}.{cls}().definitions[c.{firstItem}] >>> # all keys >>> print def{mod}.{cls}().definitions.keys() >>> ... """ _doc = """ .. note:: attribute name *{}* is renamed to *{}*, value stil is *{}*. This means that a lookup stil applies. """ def make_definition_classes(mod): """Dynamically create the definition classes from module 'mod'.""" rootpath = "oandapyV20" PTH = "{}.definitions.{}".format(rootpath, mod) M = import_module(PTH) __ALL__ = [] # construct the __all__ variable for cls, cldef in M.definitions.items(): orig, fiV = next(six.iteritems(cldef)) fiK = orig.replace('-', '_') # create the docstring dynamically clsdoc = dyndoc.format(cls=cls, PTH=PTH, mod=mod, firstItem=fiK, orig=orig, firstItemVal=fiV) # Since we can't change the docstring afterwards (it's readonly) # figure this out before and not during ... for K, V in cldef.items(): attrName = K if "-" in K: attrName = K.replace('-', '_') adoc = _doc.format(K, attrName, K) clsdoc += adoc # the class dyncls = type(cls, (object,), {'__doc__': clsdoc}) definitions = dict() for K, V in cldef.items(): attrName = K if "-" in K: attrName = K.replace('-', '_') setattr(dyncls, attrName, K) # set as class attributes definitions.update({K: V}) # for mapping by __getitem__ def mkgi(): def __getitem__(self, definitionID): """return description for definitionID.""" return self._definitions[definitionID] return __getitem__ def mkinit(definitions): def __init__(self): self._definitions = definitions return __init__ def mkPropDefinitions(): def definitions(self): """readonly property holding definition dict.""" return self._definitions return property(definitions) setattr(dyncls, "__getitem__", mkgi()) setattr(dyncls, "__init__", mkinit(definitions)) setattr(dyncls, "definitions", mkPropDefinitions()) setattr(sys.modules["{}.definitions.{}".format(rootpath, mod)], cls, dyncls) __ALL__.append(cls) setattr(sys.modules["{}.definitions.{}".format(rootpath, mod)], "__all__", tuple(__ALL__)) definitionModules = [ 'accounts', 'instruments', 'orders', 'pricing', 'primitives', 'trades', 'transactions' ] # dynamically create all the definition classes from the modules for M in definitionModules: make_definition_classes(M) ================================================ FILE: oandapyV20/definitions/accounts.py ================================================ # -*- coding: utf-8 -*- """Account Definitions.""" definitions = { "GuaranteedStopLossOrderMode": { "DISABLED": "The account is not permitted to create guaranteed " "Stop Loss Orders.", "ALLOWED": "The account is able, but not required to have guaranteed " "Stop Loss Orders for open Trades.", "REQUIRED": "The account is required to have guaranteed " "Stop Loss Orders for open Trades.", }, "AccountFinancingMode": { "NO_FINANCING": "No financing is paid/charged for open Trades " "in the Account", "SECOND_BY_SECOND": "Second-by-second financing is paid/charged " "for open Trades in the Account, both daily " "and when the the Trade is closed", "DAILY": "A full day’s worth of financing is paid/charged for " "open Trades in the Account daily at 5pm New York time" }, "PositionAggregationMode": { "ABSOLUTE_SUM": "The Position value or margin for each side (long and" " short) of the Position are computed independently " "and added together.", "MAXIMAL_SIDE": "The Position value or margin for each side (long and" " short) of the Position are computed independently. " "The Position value or margin chosen is the maximal " "absolute value of the two.", "NET_SUM": "The units for each side (long and short) of the Position " "are netted together and the resulting value (long or " "short) is used to compute the Position value or margin." } } ================================================ FILE: oandapyV20/definitions/instruments.py ================================================ # -*- coding: utf-8 -*- """Instruments Definitions.""" definitions = { "PriceComponents": { "A": "Ask", "B": "Bid", "M": "Mid", }, "CandlestickGranularity": { "S5": "5 second candlesticks, minute alignment", "S10": "10 second candlesticks, minute alignment", "S15": "15 second candlesticks, minute alignment", "S30": "30 second candlesticks, minute alignment", "M1": "1 minute candlesticks, minute alignment", "M2": "2 minute candlesticks, hour alignment", "M4": "4 minute candlesticks, hour alignment", "M5": "5 minute candlesticks, hour alignment", "M10": "10 minute candlesticks, hour alignment", "M15": "15 minute candlesticks, hour alignment", "M30": "30 minute candlesticks, hour alignment", "H1": "1 hour candlesticks, hour alignment", "H2": "1 hour candlesticks, day alignment", "H3": "3 hour candlesticks, day alignment", "H4": "4 hour candlesticks, day alignment", "H6": "6 hour candlesticks, day alignment", "H8": "8 hour candlesticks, day alignment", "H12": "12 hour candlesticks, day alignment", "D": "1 day candlesticks, day alignment", "W": "1 week candlesticks, aligned to start of week", "M": "1 month candlesticks, aligned to first day of the month", }, "WeeklyAlignment": { "Monday": "Monday", "Tuesday": "Tuesday", "Wednesday": "Wednesday", "Thursday": "Thursday", "Friday": "Friday", "Saturday": "Saturday", "Sunday": "Sunday", }, } ================================================ FILE: oandapyV20/definitions/orders.py ================================================ # -*- coding: utf-8 -*- """Order related definitions.""" definitions = { "OrderType": { "MARKET": "A Market Order", "LIMIT": "A Limit Order", "STOP": "A Stop Order", "MARKET_IF_TOUCHED": "A Market-if-touched Order", "TAKE_PROFIT": "A Take Profit Order", "STOP_LOSS": "A Stop Loss Order", "TRAILING_STOP_LOSS": "A Trailing Stop Loss Order", "FIXED_PRICE": "A Fixed Price Order" }, "CancellableOrderType": { "LIMIT": "A Limit Order", "STOP": "A Stop Order", "MARKET_IF_TOUCHED": "A Market-if-touched Order", "TAKE_PROFIT": "A Take Profit Order", "STOP_LOSS": "A Stop Loss Order", "TRAILING_STOP_LOSS": "A Trailing Stop Loss Order", }, "OrderState": { "PENDING": "The Order is currently pending execution", "FILLED": "The Order has been filled", "TRIGGERED": "The Order has been triggered", "CANCELLED": "The Order has been cancelled", }, "OrderStateFilter": { "PENDING": "The orders that are currently pending execution", "FILLED": "The orders that have been filled", "TRIGGERED": "The orders that have been triggered", "CANCELLED": "The orders that have been cancelled", "ALL": "The orders that are in any of the possible states: " "PENDING, FILLED, TRIGGERED, CANCELLED", }, "TimeInForce": { "GTC": "The Order is “Good unTil Cancelled”", "GTD": "The Order is “Good unTil Date” and will be cancelled at " "the provided time", "GFD": "The Order is “Good for Day” and will be cancelled at " "5pm New York time", "FOK": "The Order must be immediately “Filled Or Killed”", "IOC": "The Order must be “Immediately partially filled Or Killed”", }, "OrderPositionFill": { "OPEN_ONLY": "When the Order is filled, only allow Positions to be " "opened or extended.", "REDUCE_FIRST": "When the Order is filled, always fully reduce an " "existing Position before opening a new Position.", "REDUCE_ONLY": "When the Order is filled, only reduce an existing " "Position.", "DEFAULT": "When the Order is filled, use REDUCE_FIRST behaviour " "for non-client hedging Accounts, and OPEN_ONLY behaviour " "for client hedging Accounts." }, "OrderTriggerCondition": { "DEFAULT": "Trigger an Order the “natural” way: compare its price to " "the ask for long Orders and bid for short Orders", "INVERSE": "Trigger an Order the opposite of the “natural” way: " "compare its price the bid for long Orders and ask for " "short Orders.", "BID": "Trigger an Order by comparing its price to the bid regardless" " of whether it is long or short.", "ASK": "Trigger an Order by comparing its price to the ask regardless" " of whether it is long or short.", "MID": "Trigger an Order by comparing its price to the midpoint " "regardless of whether it is long or short." } } ================================================ FILE: oandapyV20/definitions/pricing.py ================================================ # -*- coding: utf-8 -*- """Pricing related Definitions.""" definitions = { "PriceStatus": { "tradeable": "The Instrument’s price is tradeable.", "non-tradeable": "The Instrument’s price is not tradeable.", "invalid": "The Instrument of the price is invalid or there " "is no valid Price for the Instrument." }, } ================================================ FILE: oandapyV20/definitions/primitives.py ================================================ # -*- coding: utf-8 -*- """Primitives definitions.""" definitions = { "InstrumentType": { "CURRENCY": "Currency", "CFD": "Contract For Difference", "METAL": "Metal" }, "AcceptDatetimeFormat": { "UNIX": "Unix timeformat: DateTime fields will be specified " "or returned in the “12345678.000000123” format.", "RFC3339": "RFC3339 timeformat: DateTime will be specified " "or returned in “YYYY-MM-DDTHH:MM:SS.nnnnnnnnnZ” format." }, "Direction": { "LONG": "A long Order is used to to buy units of an Instrument. A " "Trade is long when it has bought units of an Instrument.", "SHORT": "A short Order is used to to sell units of an Instrument. A " "Trade is short when it has sold units of an Instrument" } } ================================================ FILE: oandapyV20/definitions/trades.py ================================================ # -*- coding: utf-8 -*- """Trades definitions.""" definitions = { "TradeState": { "OPEN": "The Trade is currently open", "CLOSED": "The Trade has been fully closed", "CLOSE_WHEN_TRADABLE": "The Trade will be closed as soon as the " "trade’s instrument becomes tradeable" }, "TradeStateFilter": { "OPEN": "The Trades that are currently open", "CLOSED": "The Trades that have been fully closed", "CLOSE_WHEN_TRADEABLE": "The Trades that will be closed as soon as " "the trades' instrument becomes tradeable", "ALL": "The Trades that are in any of the possible states listed " "above." }, "TradePL": { "POSITIVE": "An open Trade currently has a positive (profitable) " "unrealized P/L, or a closed Trade realized a positive " "amount of P/L.", "NEGATIVE": "An open Trade currently has a negative (losing) " "unrealized P/L, or a closed Trade realized a negative " "amount of P/L", "ZERO": "An open Trade currently has unrealized P/L of zero " "(neither profitable nor losing), or a closed Trade realized " "a P/L amount of zero." } } ================================================ FILE: oandapyV20/definitions/transactions.py ================================================ # -*- coding: utf-8 -*- """Transactions definitions.""" definitions = { "TransactionType": { "CREATE": "Account Create Transaction", "CLOSE": "Account Close Transaction", "REOPEN": "Account Reopen Transaction", "CLIENT_CONFIGURE": "Client Configuration Transaction", "CLIENT_CONFIGURE_REJECT": "Client Configuration Reject Transaction", "TRANSFER_FUNDS": "Transfer Funds Transaction", "TRANSFER_FUNDS_REJECT": "Transfer Funds Reject Transaction", "MARKET_ORDER": "Market Order Transaction", "MARKET_ORDER_REJECT": "Market Order Reject Transaction", "FIXED_PRICE_ORDER": "Fixed Price Order Transaction", "LIMIT_ORDER": "Limit Order Transaction", "LIMIT_ORDER_REJECT": "Limit Order Reject Transaction", "STOP_ORDER": "Stop Order Transaction", "STOP_ORDER_REJECT": "Stop Order Reject Transaction", "MARKET_IF_TOUCHED_ORDER": "Market if Touched Order Transaction", "MARKET_IF_TOUCHED_ORDER_REJECT": "Market if Touched Order " "Reject Transaction", "TAKE_PROFIT_ORDER": "Take Profit Order Transaction", "TAKE_PROFIT_ORDER_REJECT": "Take Profit Order Reject Transaction", "STOP_LOSS_ORDER": "Stop Loss Order Transaction", "STOP_LOSS_ORDER_REJECT": "Stop Loss Order Reject Transaction", "TRAILING_STOP_LOSS_ORDER": "Trailing Stop Loss Order Transaction", "TRAILING_STOP_LOSS_ORDER_REJECT": "Trailing Stop Loss Order " "Reject Transaction", "ORDER_FILL": "Order Fill Transaction", "ORDER_CANCEL": "Order Cancel Transaction", "ORDER_CANCEL_REJECT": "Order Cancel Reject Transaction", "ORDER_CLIENT_EXTENSIONS_MODIFY": "Order Client Extensions " "Modify Transaction", "ORDER_CLIENT_EXTENSIONS_MODIFY_REJECT": "Order Client Extensions " "Modify Reject Transaction", "TRADE_CLIENT_EXTENSIONS_MODIFY": "Trade Client Extensions " "Modify Transaction", "TRADE_CLIENT_EXTENSIONS_MODIFY_REJECT": "Trade Client Extensions " "Modify Reject Transaction", "MARGIN_CALL_ENTER": "Margin Call Enter Transaction", "MARGIN_CALL_EXTEND": "Margin Call Extend Transaction", "MARGIN_CALL_EXIT": "Margin Call Exit Transaction", "DELAYED_TRADE_CLOSURE": "Delayed Trade Closure Transaction", "DAILY_FINANCING": "Daily Financing Transaction", "RESET_RESETTABLE_PL": "Reset Resettable PL Transaction", }, "FundingReason": { "CLIENT_FUNDING": "The client has initiated a funds transfer", "ACCOUNT_TRANSFER": "Funds are being transfered between " "two Accounts.", "DIVISION_MIGRATION": "Funds are being transfered as part of a " "Division migration", "SITE_MIGRATION": "Funds are being transfered as part of a " "Site migration", "ADJUSTMENT": "Funds are being transfered as part of an " "Account adjustment" }, "MarketOrderReason": { "CLIENT_ORDER": "The Market Order was created at the request " "of a client", "TRADE_CLOSE": "The Market Order was created to close a Trade " "at the request of a client", "POSITION_CLOSEOUT": "The Market Order was created to close a " "Position at the request of a client", "MARGIN_CLOSEOUT": "The Market Order was created as part of a " "Margin Closeout", "DELAYED_TRADE_CLOSE": "The Market Order was created to close a " "trade marked for delayed closure", }, "FixedPriceOrderReason": { "PLATFORM_ACCOUNT_MIGRATION": "The Fixed Price Order was created as " "part of a platform account migration", }, "LimitOrderReason": { "CLIENT_ORDER": "The Limit Order was initiated at the request of a " "client", "REPLACEMENT": "The Limit Order was initiated as a replacement for " "an existing Order", }, "StopOrderReason": { "CLIENT_ORDER": "The Stop Order was initiated at the request of a " "client", "REPLACEMENT": "The Stop Order was initiated as a replacement for " "an existing Order", }, "MarketIfTouchedOrderReason": { "CLIENT_ORDER": "The Market-if-touched Order was initiated at the " "request of a client", "REPLACEMENT": "The Market-if-touched Order was initiated as a " "replacement for an existing Order", }, "TakeProfitOrderReason": { "CLIENT_ORDER": "The Take Profit Order was initiated at the request " "of a client", "REPLACEMENT": "The Take Profit Order was initiated as a replacement " "for an existing Order", "ON_FILL": "The Take Profit Order was initiated automatically when " "an Order was filled that opened a new Trade requiring " "a Take Profit Order.", }, "StopLossOrderReason": { "CLIENT_ORDER": "The Stop Loss Order was initiated at the request " "of a client", "REPLACEMENT": "The Stop Loss Order was initiated as a replacement " "for an existing Order", "ON_FILL": "The Stop Loss Order was initiated automatically when " "an Order was filled that opened a new Trade requiring " "a Stop Loss Order.", }, "TrailingStopLossOrderReason": { "CLIENT_ORDER": "The Trailing Stop Loss Order was initiated at the " "request of a client", "REPLACEMENT": "The Trailing Stop Loss Order was initiated as a " "replacement for an existing Order", "ON_FILL": "The Trailing Stop Loss Order was initiated automatically " "when an Order was filled that opened a new Trade " "requiring a Trailing Stop Loss Order.", }, "OrderFillReason": { "LIMIT_ORDER": "The Order filled was a Limit Order", "STOP_ORDER": "The Order filled was a Stop Order", "MARKET_IF_TOUCHED_ORDER": "The Order filled was a " "Market-if-touched Order", "TAKE_PROFIT_ORDER": "The Order filled was a Take Profit Order", "STOP_LOSS_ORDER": "The Order filled was a Stop Loss Order", "TRAILING_STOP_LOSS_ORDER": "The Order filled was a Trailing Stop " "Loss Order", "MARKET_ORDER": "The Order filled was a Market Order", "MARKET_ORDER_TRADE_CLOSE": "The Order filled was a Market Order " "used to explicitly close a Trade", "MARKET_ORDER_POSITION_CLOSEOUT": "The Order filled was a Market " "Order used to explicitly close " "a Position", "MARKET_ORDER_MARGIN_CLOSEOUT": "The Order filled was a Market Order " "used for a Margin Closeout", "MARKET_ORDER_DELAYED_TRADE_CLOSE": "The Order filled was a Market " "Order used for a delayed Trade " "close", }, "OrderCancelReason": { "INTERNAL_SERVER_ERROR": "The Order was cancelled because at the" "time of filling, an unexpected internal " "server error occurred.", "ACCOUNT_LOCKED": "The Order was cancelled because at the time of " "filling the account was locked.", "ACCOUNT_NEW_POSITIONS_LOCKED": "The order was to be filled, " "however the account is configured " "to not allow new positions to be " "created.", "ACCOUNT_ORDER_CREATION_LOCKED": "Filling the Order wasn’t possible " "because it required the creation " "of a dependent Order and the " "Account is locked for Order " "creation.", "ACCOUNT_ORDER_FILL_LOCKED": "Filling the Order was not possible " "because the Account is locked for " "filling Orders.", "CLIENT_REQUEST": "The Order was cancelled explicitly at the request " "of the client.", "MIGRATION": "The Order cancelled because it is being migrated to " "another account.", "MARKET_HALTED": "Filling the Order wasn’t possible because the " "Order’s instrument was halted.", "LINKED_TRADE_CLOSED": "The Order is linked to an open Trade that " "was closed.", "TIME_IN_FORCE_EXPIRED": "The time in force specified for this order " "has passed.", "INSUFFICIENT_MARGIN": "Filling the Order wasn’t possible because " "the Account had insufficient margin.", "FIFO_VIOLATION": "Filling the Order would have resulted in a FIFO " "violation.", "BOUNDS_VIOLATION": "Filling the Order would have violated the " "Order’s price bound.", "CLIENT_REQUEST_REPLACED": "The Order was cancelled for replacement " "at the request of the client.", "INSUFFICIENT_LIQUIDITY": "Filling the Order wasn’t possible " "because enough liquidity available.", "TAKE_PROFIT_ON_FILL_GTD_TIMESTAMP_IN_PAST": "Filling the Order would have resulted in the creation " "of a Take Profit Order with a GTD time in the past.", "TAKE_PROFIT_ON_FILL_LOSS": "Filling the Order would result in the creation of a " "Take Profit Order that would have been filled immediately, " "closing the new Trade at a loss.", "LOSING_TAKE_PROFIT": "Filling the Order would result in the creation of a " "Take Profit Loss Order that would close the new Trade " "at a loss when filled.", "STOP_LOSS_ON_FILL_GTD_TIMESTAMP_IN_PAST": "Filling the Order would have resulted in the creation of a " "Stop Loss Order with a GTD time in the past.", "STOP_LOSS_ON_FILL_LOSS": "Filling the Order would result in the creation of a " "Stop Loss Order that would have been filled immediately, " "closing the new Trade at a loss.", "STOP_LOSS_ON_FILL_PRICE_DISTANCE_MAXIMUM_EXCEEDED": "Filling the Order would result in the creation of a Stop Loss " "Order whose price would be zero or negative due to the " "specified distance.", "STOP_LOSS_ON_FILL_REQUIRED": "Filling the Order would not result in the creation of Stop " "Loss Order, however the Account’s configuration requires that " "all Trades have a Stop Loss Order attached to them", "STOP_LOSS_ON_FILL_GUARANTEED_REQUIRED": "Filling the Order would not result in the creation of a " "guaranteed Stop Loss Order, however the Account’s configuration " "requires that all Trades have a guaranteed Stop Loss Order " "attached to them", "STOP_LOSS_ON_FILL_GUARANTEED_NOT_ALLOWED": "Filling the Order would result in the creation of a guaranteed " "Stop Loss Order, however the Account’s configuration does not " "allow guaranteed Stop Loss Orders", "STOP_LOSS_ON_FILL_GUARANTEED_MINIMUM_DISTANCE_NOT_MET": "Filling the Order would result in the creation of a guaranteed " "Stop Loss Order with a distance smaller than the configured " "mininum distance.", "STOP_LOSS_ON_FILL_GUARANTEED_LEVEL_RESTRICTION_EXCEEDED": "Filling the Order would result in the creation of a guaranteed " "Stop Loss Order with trigger price and number of units that " "that violates the account’s guaranteed Stop Loss Order level " "restriction", "STOP_LOSS_ON_FILL_GUARANTEED_HEDGING_NOT_ALLOWED": "Filling the Order would result in the creation of a guaranteed " "Stop Loss Order for a hedged Trade, however the Account’s " "configuration does not allow guaranteed Stop Loss Orders for " "hedged Trades/Positions.", "STOP_LOSS_ON_FILL_TIME_IN_FORCE_INVALID": "Filling the Order would result in the creation of a Stop Loss " "Order whose TimeInForce value is invalid. A likely cause would " "be if the Account requires guaranteed stop loss orders and the " "TimeInForce value were not GTC.", "STOP_LOSS_ON_FILL_TRIGGER_CONDITION_INVALID": "Filling the Order would result in the creation of a Stop Loss " "Order whose TriggerCondition value is invalid. A likely cause " "would be if the stop loss order is guaranteed and the " "TimeInForce is not TRIGGER_DEFAULT or TRIGGER_BID for a long " "trade, or not TRIGGER_DEFAULT or TRIGGER_ASK for a short trade.", "TAKE_PROFIT_ON_FILL_PRICE_DISTANCE_MAXIMUM_EXCEEDED": "Filling the Order would result in the creation of a Take Profit " "Order whose price would be zero or negative due to the specified " "distance.", "TRAILING_STOP_LOSS_ON_FILL_GTD_TIMESTAMP_IN_PAST": "Filling the Order would have resulted in the creation of a" "Trailing Stop Loss Order with a GTD time in the past.", "CLIENT_TRADE_ID_ALREADY_EXISTS": "Filling the Order would result in the creation of a " "new Open Trade with a client Trade ID already in use.", "POSITION_CLOSEOUT_FAILED": "Closing out a position wasn’t " "fully possible.", "OPEN_TRADES_ALLOWED_EXCEEDED": "Filling the Order would cause the maximum open trades " "allowed for the Account to be exceeded.", "PENDING_ORDERS_ALLOWED_EXCEEDED": "Filling the Order would have resulted in exceeding the " "number of pending Orders allowed for the Account.", "TAKE_PROFIT_ON_FILL_CLIENT_ORDER_ID_ALREADY_EXISTS": "Filling the Order would have resulted in the creation of " "a Take Profit Order with a client Order ID that is already " "in use.", "STOP_LOSS_ON_FILL_CLIENT_ORDER_ID_ALREADY_EXISTS": "Filling the Order would have resulted in the creation of a " "Stop Loss Order with a client Order ID that is already in use.", "TRAILING_STOP_LOSS_ON_FILL_CLIENT_ORDER_ID_ALREADY_EXISTS": "Filling the Order would have resulted in the creation of a " "Trailing Stop Loss Order with a client Order ID that is " "already in use.", "POSITION_SIZE_EXCEEDED": "Filling the Order would have resulted in the " "Account’s maximum position size limit being exceeded " "for the Order’s instrument.", "HEDGING_GSLO_VIOLATION": "Filling the Order would result in the creation of a Trade, " "however there already exists an opposing (hedged) Trade that " "has a guaranteed Stop Loss Order attached to it. Guaranteed " "Stop Loss Orders cannot be combined with hedged positions", "ACCOUNT_POSITION_VALUE_LIMIT_EXCEEDED": "Filling the order would cause the maximum position value " "allowed for the account to be exceeded. The Order has been " "cancelled as a result." }, "MarketOrderMarginCloseoutReason": { "MARGIN_CHECK_VIOLATION": "Trade closures resulted from violating " "OANDA’s margin policy", "REGULATORY_MARGIN_CALL_VIOLATION": "Trade closures came from a margin closeout event resulting " "from regulatory conditions placed on the Account’s margin call" }, "TransactionRejectReason": { "INTERNAL_SERVER_ERROR": "An unexpected internal server error has " "occurred", "INSTRUMENT_PRICE_UNKNOWN": "The system was unable to determine the " "current price for the Order’s instrument", "ACCOUNT_NOT_ACTIVE": "The Account is not active", "ACCOUNT_LOCKED": "The Account is locked", "ACCOUNT_ORDER_CREATION_LOCKED": "The Account is locked for Order " "creation", "ACCOUNT_CONFIGURATION_LOCKED": "The Account is locked for " "configuration", "ACCOUNT_DEPOSIT_LOCKED": "The Account is locked for deposits", "ACCOUNT_WITHDRAWAL_LOCKED": "The Account is locked for withdrawals", "ACCOUNT_ORDER_CANCEL_LOCKED": "The Account is locked for Order " "cancellation", "INSTRUMENT_NOT_TRADEABLE": "The instrument specified is not " "tradeable by the Account", "PENDING_ORDERS_ALLOWED_EXCEEDED": "Creating the Order would result " "in the maximum number of allowed " "pending Orders being exceeded", "ORDER_ID_UNSPECIFIED": "Neither the Order ID nor client Order ID " "are specified", "ORDER_DOESNT_EXIST": "The Order specified does not exist", "ORDER_IDENTIFIER_INCONSISTENCY": "The Order ID and client Order ID " "specified do not identify the " "same Order", "TRADE_ID_UNSPECIFIED": "Neither the Trade ID nor client Trade ID " "are specified", "TRADE_DOESNT_EXIST": "The Trade specified does not exist", "TRADE_IDENTIFIER_INCONSISTENCY": "The Trade ID and client Trade ID " "specified do not identify the " "same Trade", "INSUFFICIENT_MARGIN": "The Account had insufficient margin to perform the action " "specified. One possible reason for this is due to the creation " "or modification of a guaranteed StopLoss Order.", "INSTRUMENT_MISSING": "Order instrument has not been specified", "INSTRUMENT_UNKNOWN": "The instrument specified is unknown", "UNITS_MISSING": "Order units have not been not specified", "UNITS_INVALID": "Order units specified are invalid", "UNITS_PRECISION_EXCEEDED": "The units specified contain more " "precision than is allowed for the " "Order’s instrument", "UNITS_LIMIT_EXCEEDED": "The units specified exceeds the maximum " "number of units allowed", "UNITS_MIMIMUM_NOT_MET": "The units specified is less than the " "minimum number of units required", "PRICE_MISSING": "The price has not been specified", "PRICE_INVALID": "The price specifed is invalid", "PRICE_PRECISION_EXCEEDED": "The price specified contains more " "precision than is allowed for the " "instrument", "PRICE_DISTANCE_MISSING": "The price distance has not been specified", "PRICE_DISTANCE_INVALID": "The price distance specifed is invalid", "PRICE_DISTANCE_PRECISION_EXCEEDED": "The price distance specified " "contains more precision than is " "allowed for the instrument", "PRICE_DISTANCE_MAXIMUM_EXCEEDED": "The price distance exceeds that " "maximum allowed amount", "PRICE_DISTANCE_MINIMUM_NOT_MET": "The price distance does not meet " "the minimum allowed amount", "TIME_IN_FORCE_MISSING": "The TimeInForce field has not been " "specified", "TIME_IN_FORCE_INVALID": "The TimeInForce specified is invalid", "TIME_IN_FORCE_GTD_TIMESTAMP_MISSING": "The TimeInForce is GTD but no " "GTD timestamp is provided", "TIME_IN_FORCE_GTD_TIMESTAMP_IN_PAST": "The TimeInForce is GTD but " "the GTD timestamp is in the " "past", "PRICE_BOUND_INVALID": "The price bound specified is invalid", "PRICE_BOUND_PRECISION_EXCEEDED": "The price bound specified contains " "more precision than is allowed for " "the Order’s instrument", "ORDERS_ON_FILL_DUPLICATE_CLIENT_ORDER_IDS": "Multiple Orders on fill share the same client Order ID", "TRADE_ON_FILL_CLIENT_EXTENSIONS_NOT_SUPPORTED": "The Order does not support Trade on fill client extensions " "because it cannot create a new Trade", "CLIENT_ORDER_ID_INVALID": "The client Order ID specified is invalid", "CLIENT_ORDER_ID_ALREADY_EXISTS": "The client Order ID specified is already assigned to another " "pending Order", "CLIENT_ORDER_TAG_INVALID": "The client Order tag specified is invalid", "CLIENT_ORDER_COMMENT_INVALID": "The client Order comment specified is invalid", "CLIENT_TRADE_ID_INVALID": "The client Trade ID specified is invalid", "CLIENT_TRADE_ID_ALREADY_EXISTS": "The client Trade ID specifed is already assigned to another " "open Trade", "CLIENT_TRADE_TAG_INVALID": "The client Trade tag specified is invalid", "CLIENT_TRADE_COMMENT_INVALID": "The client Trade comment is invalid", "ORDER_FILL_POSITION_ACTION_MISSING": "The OrderFillPositionAction field has not been specified", "ORDER_FILL_POSITION_ACTION_INVALID": "The OrderFillPositionAction specified is invalid", "TRIGGER_CONDITION_MISSING": "The TriggerCondition field has not been specified", "TRIGGER_CONDITION_INVALID": "The TriggerCondition specified is invalid", "ORDER_PARTIAL_FILL_OPTION_MISSING": "The OrderFillPositionAction field has not been specified", "ORDER_PARTIAL_FILL_OPTION_INVALID": "The OrderFillPositionAction specified is invalid.", "INVALID_REISSUE_IMMEDIATE_PARTIAL_FILL": "When attempting to reissue an order (currently only a " "MarketIfTouched) that was immediately partially filled, it is " "not possible to create a correct pending Order.", "TAKE_PROFIT_ORDER_ALREADY_EXISTS": "A Take Profit Order for the specified Trade already exists", "TAKE_PROFIT_ON_FILL_PRICE_MISSING": "The Take Profit on fill specified does not provide a price", "TAKE_PROFIT_ON_FILL_PRICE_INVALID": "The Take Profit on fill specified contains an invalid price", "TAKE_PROFIT_ON_FILL_PRICE_PRECISION_EXCEEDED": "The Take Profit on fill specified contains a price with more " "precision than is allowed by the Order’s instrument", "TAKE_PROFIT_ON_FILL_TIME_IN_FORCE_MISSING": "The Take Profit on fill specified does not provide a TimeInForce", "TAKE_PROFIT_ON_FILL_TIME_IN_FORCE_INVALID": "The Take Profit on fill specifies an invalid TimeInForce", "TAKE_PROFIT_ON_FILL_GTD_TIMESTAMP_MISSING": "The Take Profit on fill specifies a GTD TimeInForce but does " "not provide a GTD timestamp", "TAKE_PROFIT_ON_FILL_GTD_TIMESTAMP_IN_PAST": "The Take Profit on fill specifies a GTD timestamp that is in " "the past", "TAKE_PROFIT_ON_FILL_CLIENT_ORDER_ID_INVALID": "The Take Profit on fill client Order ID specified is invalid", "TAKE_PROFIT_ON_FILL_CLIENT_ORDER_TAG_INVALID": "The Take Profit on fill client Order tag specified is invalid", "TAKE_PROFIT_ON_FILL_CLIENT_ORDER_COMMENT_INVALID": "The Take Profit on fill client Order comment specified is " "invalid", "TAKE_PROFIT_ON_FILL_TRIGGER_CONDITION_MISSING": "The Take Profit on fill specified does not provide a " "TriggerCondition", "TAKE_PROFIT_ON_FILL_TRIGGER_CONDITION_INVALID": "The Take Profit on fill specifies an invalid TriggerCondition", "STOP_LOSS_ORDER_ALREADY_EXISTS": "A Stop Loss Order for the specified Trade already exists", "STOP_LOSS_ORDER_GUARANTEED_REQUIRED": "An attempt was made to to create a non-guaranteed stop loss " "order in an account that requires all stop loss orders to be " "guaranteed.", "STOP_LOSS_ORDER_GUARANTEED_PRICE_WITHIN_SPREAD": "An attempt to create a guaranteed stop loss order with a price " "that is within the current tradeable spread.", "STOP_LOSS_ORDER_GUARANTEED_NOT_ALLOWED": "An attempt was made to create a guaranteed Stop Loss Order, " "however the Account’s configuration does not allow guaranteed " "Stop Loss Orders.", "STOP_LOSS_ORDER_GUARANTEED_HALTED_CREATE_VIOLATION": "An attempt was made to create a guaranteed Stop Loss Order " "when the market was halted.", "STOP_LOSS_ORDER_GUARANTEED_HALTED_TIGHTEN_VIOLATION": "An attempt was made to re-create a guaranteed Stop Loss Order" "with a tighter fill price when the market was halted.", "STOP_LOSS_ORDER_GUARANTEED_HEDGING_NOT_ALLOWED": "An attempt was made to create a guaranteed Stop Loss Order on " "a hedged Trade (ie there is an existing open Trade in the " "opposing direction), however the Account’s configuration does " "not allow guaranteed Stop Loss Orders for hedged " "Trades/Positions.", "STOP_LOSS_ORDER_GUARANTEED_MINIMUM_DISTANCE_NOT_MET": "An attempt was made to create a guaranteed Stop Loss Order, " "however the distance between the current price and the trigger " "price does not meet the Account’s configured " "minimumGuaranteedStopLossDistance.", "STOP_LOSS_ORDER_NOT_CANCELABLE": "An attempt was made to cancel a Stop Loss Order, however the " "Account’s configuration requires every Trade have an associated " "Stop Loss Order.", "STOP_LOSS_ORDER_NOT_REPLACEABLE": "An attempt was made to cancel and replace a Stop Loss Order, " "however the Account’s configuration prevents the modification " "of Stop Loss Orders.", "STOP_LOSS_ORDER_GUARANTEED_LEVEL_RESTRICTION_EXCEEDED": "An attempt was made to create a guaranteed Stop Loss Order, " "however doing so would exceed the Account’s configured " "guaranteed StopLoss Order level restriction volume.", "STOP_LOSS_ORDER_PRICE_AND_DISTANCE_BOTH_SPECIFIED": "The Stop Loss Order request contains both the price and " "distance fields.", "STOP_LOSS_ORDER_PRICE_AND_DISTANCE_BOTH_MISSING": "The Stop Loss Order request contains neither the price nor " "distance fields.", "STOP_LOSS_ON_FILL_REQUIRED_FOR_PENDING_ORDER": "An attempt to create a pending Order was made with no Stop " "Loss Order on fill specified and the Account’s configuration " "requires that every Trade have an associated Stop Loss Order.", "STOP_LOSS_ON_FILL_GUARANTEED_NOT_ALLOWED": "An attempt to create a pending Order was made with a Stop " "Loss Order on fill that was explicitly configured to be " "guaranteed, however the Account’s configuration does not allow " "guaranteed Stop Loss Orders.", "STOP_LOSS_ON_FILL_GUARANTEED_REQUIRED": "An attempt to create a pending Order was made with a Stop Loss " "Order on fill that was explicitly configured to be not " "guaranteed, however the Account’s configuration requires " "guaranteed Stop Loss Orders.", "STOP_LOSS_ON_FILL_PRICE_MISSING": "The Stop Loss on fill specified does not provide a price", "STOP_LOSS_ON_FILL_PRICE_INVALID": "The Stop Loss on fill specifies an invalid price", "STOP_LOSS_ON_FILL_PRICE_PRECISION_EXCEEDED": "The Stop Loss on fill specifies a price with more precision " "than is allowed by the Order’s instrument", "STOP_LOSS_ON_FILL_GUARANTEED_MINIMUM_DISTANCE_NOT_MET": "An attempt to create a pending Order was made with the " "distance between the guaranteed Stop Loss Order on fill’s price " "and the pending Order’s price is less than the Account’s " "configured minimum guaranteed stop loss distance.", "STOP_LOSS_ON_FILL_GUARANTEED_LEVEL_RESTRICTION_EXCEEDED": "An attempt to create a pending Order was made with a guaranteed " "Stop Loss Order on fill configured, and the Order’s units " "exceed the Account’s configured guaranteed StopLoss Order level " "restriction volume.", "STOP_LOSS_ON_FILL_DISTANCE_INVALID": "The Stop Loss on fill distance is invalid", "STOP_LOSS_ON_FILL_PRICE_DISTANCE_MAXIMUM_EXCEEDED": "The Stop Loss on fill price distance exceeds the maximum " "allowed amount", "STOP_LOSS_ON_FILL_DISTANCE_PRECISION_EXCEEDED": "The Stop Loss on fill distance contains more precision than " "is allowed by the instrument", "STOP_LOSS_ON_FILL_PRICE_AND_DISTANCE_BOTH_SPECIFIED": "The Stop Loss on fill contains both the price and distance " "fields.", "STOP_LOSS_ON_FILL_PRICE_AND_DISTANCE_BOTH_MISSING": "The Stop Loss on fill contains neither the price nor distance " "fields.", "STOP_LOSS_ON_FILL_TIME_IN_FORCE_MISSING": "The Stop Loss on fill specified does not provide a TimeInForce", "STOP_LOSS_ON_FILL_TIME_IN_FORCE_INVALID": "The Stop Loss on fill specifies an invalid TimeInForce", "STOP_LOSS_ON_FILL_GTD_TIMESTAMP_MISSING": "The Stop Loss on fill specifies a GTD TimeInForce but does " "not provide a GTD timestamp", "STOP_LOSS_ON_FILL_GTD_TIMESTAMP_IN_PAST": "The Stop Loss on fill specifies a GTD timestamp that is in " "the past", "STOP_LOSS_ON_FILL_CLIENT_ORDER_ID_INVALID": "The Stop Loss on fill client Order ID specified is invalid", "STOP_LOSS_ON_FILL_CLIENT_ORDER_TAG_INVALID": "The Stop Loss on fill client Order tag specified is invalid", "STOP_LOSS_ON_FILL_CLIENT_ORDER_COMMENT_INVALID": "The Stop Loss on fill client Order comment specified is invalid", "STOP_LOSS_ON_FILL_TRIGGER_CONDITION_MISSING": "The Stop Loss on fill specified does not provide a " "TriggerCondition", "STOP_LOSS_ON_FILL_TRIGGER_CONDITION_INVALID": "The Stop Loss on fill specifies an invalid TriggerCondition", "TRAILING_STOP_LOSS_ORDER_ALREADY_EXISTS": "A Trailing Stop Loss Order for the specified Trade already " "exists", "TRAILING_STOP_LOSS_ON_FILL_PRICE_DISTANCE_MISSING": "The Trailing Stop Loss on fill specified does not provide a " "distance", "TRAILING_STOP_LOSS_ON_FILL_PRICE_DISTANCE_INVALID": "The Trailing Stop Loss on fill distance is invalid", "TRAILING_STOP_LOSS_ON_FILL_PRICE_DISTANCE_PRECISION_EXCEEDED": "The Trailing Stop Loss on fill distance contains more precision " "than is allowed by the instrument", "TRAILING_STOP_LOSS_ON_FILL_PRICE_DISTANCE_MAXIMUM_EXCEEDED": "The Trailing Stop Loss on fill price distance exceeds the " "maximum allowed amount", "TRAILING_STOP_LOSS_ON_FILL_PRICE_DISTANCE_MINIMUM_NOT_MET": "The Trailing Stop Loss on fill price distance does not meet " "the minimum allowed amount", "TRAILING_STOP_LOSS_ON_FILL_TIME_IN_FORCE_MISSING": "The Trailing Stop Loss on fill specified does not provide a " "TimeInForce", "TRAILING_STOP_LOSS_ON_FILL_TIME_IN_FORCE_INVALID": "The Trailing Stop Loss on fill specifies an invalid TimeInForce", "TRAILING_STOP_LOSS_ON_FILL_GTD_TIMESTAMP_MISSING": "The Trailing Stop Loss on fill TimeInForce is specified as GTD " "but no GTD timestamp is provided", "TRAILING_STOP_LOSS_ON_FILL_GTD_TIMESTAMP_IN_PAST": "The Trailing Stop Loss on fill GTD timestamp is in the past", "TRAILING_STOP_LOSS_ON_FILL_CLIENT_ORDER_ID_INVALID": "The Trailing Stop Loss on fill client Order ID specified is " "invalid", "TRAILING_STOP_LOSS_ON_FILL_CLIENT_ORDER_TAG_INVALID": "The Trailing Stop Loss on fill client Order tag specified is " "invalid", "TRAILING_STOP_LOSS_ON_FILL_CLIENT_ORDER_COMMENT_INVALID": "The Trailing Stop Loss on fill client Order comment specified " "is invalid", "TRAILING_STOP_LOSS_ORDERS_NOT_SUPPORTED": "A client attempted to create either a Trailing Stop Loss order " "or an order with a Trailing Stop Loss On Fill specified, which " "may not yet be supported.", "TRAILING_STOP_LOSS_ON_FILL_TRIGGER_CONDITION_MISSING": "The Trailing Stop Loss on fill specified does not provide a " "TriggerCondition", "TRAILING_STOP_LOSS_ON_FILL_TRIGGER_CONDITION_INVALID": "The Tailing Stop Loss on fill specifies an invalid " "TriggerCondition", "CLOSE_TRADE_TYPE_MISSING": "The request to close a Trade does not " "specify a full or partial close", "CLOSE_TRADE_PARTIAL_UNITS_MISSING": "The request to close a Trade partially did not specify the " "number of units to close", "CLOSE_TRADE_UNITS_EXCEED_TRADE_SIZE": "The request to partially close a Trade specifies a number of " "units that exceeds the current size of the given Trade", "CLOSEOUT_POSITION_DOESNT_EXIST": "The Position requested to be closed out does not exist", "CLOSEOUT_POSITION_INCOMPLETE_SPECIFICATION": "The request to closeout a Position was specified incompletely", "CLOSEOUT_POSITION_UNITS_EXCEED_POSITION_SIZE": "A partial Position closeout request specifies a number of units " "that exceeds the current Position", "CLOSEOUT_POSITION_REJECT": "The request to closeout a Position could not be fully satisfied", "CLOSEOUT_POSITION_PARTIAL_UNITS_MISSING": "The request to partially closeout a Position did not specify " "the number of units to close.", "MARKUP_GROUP_ID_INVALID": "The markup group ID provided is invalid", "POSITION_AGGREGATION_MODE_INVALID": "The PositionAggregationMode provided is not supported/valid.", "ADMIN_CONFIGURE_DATA_MISSING": "No configuration parameters provided", "MARGIN_RATE_INVALID": "The margin rate provided is invalid", "MARGIN_RATE_WOULD_TRIGGER_CLOSEOUT": "The margin rate provided " "would cause an immediate " "margin closeout", "ALIAS_INVALID": "The account alias string provided is invalid", "CLIENT_CONFIGURE_DATA_MISSING": "No configuration parameters provided", "MARGIN_RATE_WOULD_TRIGGER_MARGIN_CALL": "The margin rate provided would cause the Account to enter " "a margin call state.", "AMOUNT_INVALID": "Funding is not possible because the requested " "transfer amount is invalid", "INSUFFICIENT_FUNDS": "The Account does not have sufficient " "balance to complete the funding request", "AMOUNT_MISSING": "Funding amount has not been specified", "FUNDING_REASON_MISSING": "Funding reason has not been specified", "CLIENT_EXTENSIONS_DATA_MISSING": "Neither Order nor Trade on Fill client extensions were " "provided for modification", "REPLACING_ORDER_INVALID": "The Order to be replaced has a different type than the " "replacing Order.", "REPLACING_TRADE_ID_INVALID": "The replacing Order refers to a different Trade than the " "Order that is being replaced.", } } ================================================ FILE: oandapyV20/endpoints/__init__.py ================================================ ================================================ FILE: oandapyV20/endpoints/accounts.py ================================================ # -*- coding: utf-8 -*- """Handle account endpoints.""" from .apirequest import APIRequest from .decorators import dyndoc_insert, endpoint from .responses.accounts import responses from abc import abstractmethod class Accounts(APIRequest): """Accounts - class to handle the accounts endpoints.""" ENDPOINT = "" METHOD = "GET" @abstractmethod @dyndoc_insert(responses) def __init__(self, accountID=None): """Instantiate an Accounts APIRequest instance. Parameters ---------- accountID : string (optional) the accountID of the account. Optional when requesting all accounts. For all other requests to the endpoint it is required. """ endpoint = self.ENDPOINT.format(accountID=accountID) super(Accounts, self).__init__(endpoint, method=self.METHOD) @endpoint("v3/accounts") class AccountList(Accounts): """Get a list of all Accounts authorized for the provided token.""" @dyndoc_insert(responses) def __init__(self): """Instantiate an AccountList request. >>> import oandapyV20 >>> import oandapyV20.endpoints.accounts as accounts >>> client = oandapyV20.API(access_token=...) >>> r = accounts.AccountList() >>> client.request(r) >>> print r.response :: {_v3_accounts_resp} """ super(AccountList, self).__init__() @endpoint("v3/accounts/{accountID}") class AccountDetails(Accounts): """AccountDetails. Get the full details for a single Account that a client has access to. Full pending Order, open Trade and open Position representations are provided. """ @dyndoc_insert(responses) def __init__(self, accountID): """Instantiate an AccountDetails request. Parameters ---------- accountID : string (required) id of the account to perform the request on. >>> import oandapyV20 >>> import oandapyV20.endpoints.accounts as accounts >>> client = oandapyV20.API(access_token=...) >>> r = accounts.AccountDetails(accountID) >>> client.request(r) >>> print r.response :: {_v3_account_by_accountID_resp} """ super(AccountDetails, self).__init__(accountID) @endpoint("v3/accounts/{accountID}/summary") class AccountSummary(Accounts): """Get a summary for a single Account that a client has access to.""" @dyndoc_insert(responses) def __init__(self, accountID): """Instantiate an AccountSummary request. Parameters ---------- accountID : string (required) id of the account to perform the request on. >>> import oandapyV20 >>> import oandapyV20.endpoints.accounts as accounts >>> client = oandapyV20.API(access_token=...) >>> r = accounts.AccountSummary(accountID) >>> client.request(r) >>> print r.response :: {_v3_account_by_accountID_summary_resp} """ super(AccountSummary, self).__init__(accountID) @endpoint("v3/accounts/{accountID}/instruments") class AccountInstruments(Accounts): """AccountInstruments. Get the list of tradable instruments for the given Account. The list of tradeable instruments is dependent on the regulatory division that the Account is located in, thus should be the same for all Accounts owned by a single user. """ @dyndoc_insert(responses) def __init__(self, accountID, params=None): """Instantiate an AccountInstruments request. Parameters ---------- accountID : string (required) id of the account to perform the request on. params : dict (optional) query params to send, check developer.oanda.com for details. Query Params example:: {_v3_account_by_accountID_instruments_params} >>> import oandapyV20 >>> import oandapyV20.endpoints.accounts as accounts >>> client = oandapyV20.API(access_token=...) >>> params = ... >>> r = accounts.AccountInstruments(accountID=..., params=params) >>> client.request(r) >>> print r.response Output:: {_v3_account_by_accountID_instruments_resp} """ super(AccountInstruments, self).__init__(accountID) self.params = params @endpoint("v3/accounts/{accountID}/configuration", "PATCH") class AccountConfiguration(Accounts): """Set the client-configurable portions of an Account.""" HEADERS = {"Content-Type": "application/json"} @dyndoc_insert(responses) def __init__(self, accountID, data): """Instantiate an AccountConfiguration request. Parameters ---------- accountID : string (required) id of the account to perform the request on. data : dict (required) json body to send body example:: {_v3_accounts_accountID_account_config_body} >>> import oandapyV20 >>> import oandapyV20.endpoints.accounts as accounts >>> client = oandapyV20.API(access_token=...) >>> r = accounts.AccountConfiguration(accountID, data=data) >>> client.request(r) >>> print r.response :: {_v3_accounts_accountID_account_config_resp} """ super(AccountConfiguration, self).__init__(accountID) self.data = data @endpoint("v3/accounts/{accountID}/changes") class AccountChanges(Accounts): """AccountChanges. Endpoint used to poll an Account for its current state and changes since a specified TransactionID. """ @dyndoc_insert(responses) def __init__(self, accountID, params=None): """Instantiate an AccountChanges request. Parameters ---------- accountID : string (required) id of the account to perform the request on. params : dict (optional) query params to send, check developer.oanda.com for details. Query Params example:: {_v3_accounts_accountID_account_changes_params} >>> import oandapyV20 >>> import oandapyV20.endpoints.accounts as accounts >>> client = oandapyV20.API(access_token=...) >>> params = ... >>> r = accounts.AccountChanges(accountID=..., params=params) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_account_changes_resp} """ super(AccountChanges, self).__init__(accountID) self.params = params ================================================ FILE: oandapyV20/endpoints/apirequest.py ================================================ """Handling of API requests.""" import six from abc import ABCMeta, abstractmethod @six.add_metaclass(ABCMeta) class APIRequest(object): """Base Class for API-request classes.""" @abstractmethod def __init__(self, endpoint, method="GET", expected_status=200): """Instantiate an API request. Parameters ---------- endpoint : string the URL format string method : string the method for the request. Default: GET. """ self._expected_status = expected_status self._status_code = None self._response = None self._endpoint = endpoint self.method = method @property def expected_status(self): return self._expected_status @property def status_code(self): return self._status_code @status_code.setter def status_code(self, value): if value != self._expected_status: raise ValueError("{} {} {:d}".format(self, self.method, value)) self._status_code = value @property def response(self): """response - get the response of the request.""" return self._response @response.setter def response(self, value): """response - set the response of the request.""" self._response = value def __str__(self): """return the endpoint.""" return self._endpoint ================================================ FILE: oandapyV20/endpoints/decorators.py ================================================ # -*- coding: UTF-8 -*- """decorators.""" def dyndoc_insert(src): """docstring_insert - a decorator to insert API-docparts dynamically.""" # manipulating docstrings this way is tricky due to indentation # the JSON needs leading whitespace to be interpreted correctly import json import re def mkblock(d, flag=0): # response, pretty formatted v = json.dumps(d, indent=2) if flag == 1: # strip the '[' and ']' in case of a list holding items # that stand on their own (example: tick records from a stream) nw = re.findall('.*?\[(.*)\]', v, flags=re.S) v = nw[0] # add leading whitespace for each line and start with a newline return "\n{}".format("".join(["{0:>16}{1}\n".format("", L) for L in v.split('\n')])) def dec(obj): allSlots = re.findall("\{(_v3.*?)\}", obj.__doc__) docsub = {} sub = {} for k in allSlots: p = re.findall("^(_v3.*)_(.*)", k) p = list(*p) sub.update({p[1]: p[0]}) for v in sub.values(): for k in sub.keys(): docsub["{}_url".format(v)] = "{}".format(src[v]["url"]) if "resp" == k: docsub.update({"{}_resp".format(v): mkblock(src[v]["response"])}) if "body" == k: docsub.update({"{}_body".format(v): mkblock(src[v]["body"])}) if "params" == k: docsub.update({"{}_params".format(v): mkblock(src[v]["params"])}) if "ciresp" == k: docsub.update({"{}_ciresp".format(v): mkblock(src[v]["response"], 1)}) obj.__doc__ = obj.__doc__.format(**docsub) return obj return dec def endpoint(url, method="GET", expected_status=200): """endpoint - decorator to manipulate the REST-service endpoint. The endpoint decorator sets the endpoint and the method for the class to access the REST-service. """ def dec(obj): obj.ENDPOINT = url obj.METHOD = method obj.EXPECTED_STATUS = expected_status return obj return dec def abstractclass(cls): """abstractclass - class decorator. make sure the class is abstract and cannot be used on it's own. @abstractclass class A(object): def __init__(self, *args, **kwargs): # logic pass class B(A): pass a = A() # results in an AssertionError b = B() # works fine """ setattr(cls, "_ISNEVER", cls.__bases__[0].__name__) origInit = cls.__dict__["__init__"] def wrapInit(self, *args, **kwargs): # when the class is instantiated we can check for bases # we don't want it to be the base class try: assert self.__class__.__bases__[-1].__name__ != self._ISNEVER origInit(self, *args, **kwargs) except AssertionError: raise TypeError("Use of abstract base class") # replace the original __init__ setattr(wrapInit, "__doc__", getattr(origInit, "__doc__")) setattr(origInit, "__doc__", "") setattr(cls, "__init__", wrapInit) return cls class extendargs(object): """'extendargs' decorator. Add extra arguments to the argumentlist of the constructor of the class. """ def __init__(self, *loa): self.loa = loa def __call__(self, cls): # save parent class __init__ origInit = cls.__bases__[0].__dict__["__init__"] def wrapInit(wself, *args, **kwargs): for extraArg in self.loa: if extraArg in kwargs: setattr(wself, extraArg, kwargs[extraArg]) del kwargs[extraArg] origInit(wself, *args, **kwargs) setattr(cls, "__init__", wrapInit) return cls ================================================ FILE: oandapyV20/endpoints/forexlabs.py ================================================ """Handle forexlabs endpoints.""" from .apirequest import APIRequest from .decorators import dyndoc_insert, endpoint from .responses.forexlabs import responses from abc import abstractmethod class ForexLabs(APIRequest): """ForexLabs - abstractbase class to handle the 'forexlabs' endpoints.""" ENDPOINT = "" METHOD = "GET" @abstractmethod @dyndoc_insert(responses) def __init__(self): """Instantiate a ForexLabs APIRequest instance.""" endpoint = self.ENDPOINT.format() super(ForexLabs, self).__init__(endpoint, method=self.METHOD) @endpoint("labs/v1/calendar") class Calendar(ForexLabs): """Calendar. Get calendar information. """ @dyndoc_insert(responses) def __init__(self, params): """Instantiate a Calendar request. Parameters ---------- params : dict (required) query params to send, check developer.oanda.com for details. >>> import oandapyV20 >>> import oandapyV20.endpoints.forexlabs as labs >>> accountID = ... >>> client = oandapyV20.API(access_token=...) >>> params = {_v3_forexlabs_calendar_params} >>> r = labs.Calendar(params=params) >>> client.request(r) >>> print(r.response) Output:: {_v3_forexlabs_calendar_resp} """ super(Calendar, self).__init__() self.params = params @endpoint("labs/v1/historical_position_ratios") class HistoricalPositionRatios(ForexLabs): """HistoricalPositionRatios. Get the historical positionratios for an instrument. """ @dyndoc_insert(responses) def __init__(self, params): """Instantiate a HistoricalPositionRatios request. Parameters ---------- params : dict (required) query params to send, check developer.oanda.com for details. >>> import oandapyV20 >>> import oandapyV20.endpoints.forexlabs as labs >>> accountID = ... >>> client = oandapyV20.API(access_token=...) >>> params = {_v3_forexlabs_histposratios_params} >>> r = labs.HistoricalPositionRatios(params=params) >>> client.request(r) >>> print(r.response) Output:: {_v3_forexlabs_histposratios_resp} """ super(HistoricalPositionRatios, self).__init__() self.params = params @endpoint("labs/v1/spreads") class Spreads(ForexLabs): """Spreads. Get the spread information for an instrument. """ @dyndoc_insert(responses) def __init__(self, params): """Instantiate a Spreads request. Parameters ---------- params : dict (required) query params to send, check developer.oanda.com for details. >>> import oandapyV20 >>> import oandapyV20.endpoints.forexlabs as labs >>> accountID = ... >>> client = oandapyV20.API(access_token=...) >>> params = {_v3_forexlabs_spreads_params} >>> r = labs.Spreads(params=params) >>> client.request(r) >>> print(r.response) Output:: {_v3_forexlabs_spreads_resp} """ super(Spreads, self).__init__() self.params = params @endpoint("labs/v1/commitments_of_traders") class CommitmentsOfTraders(ForexLabs): """CommitmentsOfTraders. Get the 'commitments of traders' information for an instrument. """ @dyndoc_insert(responses) def __init__(self, params): """Instantiate a CommitmentsOfTraders request. Parameters ---------- params : dict (required) query params to send, check developer.oanda.com for details. >>> import oandapyV20 >>> import oandapyV20.endpoints.forexlabs as labs >>> accountID = ... >>> client = oandapyV20.API(access_token=...) >>> params = {_v3_forexlabs_commoftrad_params} >>> r = labs.CommitmentOfTraders(params=params) >>> client.request(r) >>> print(r.response) Output:: {_v3_forexlabs_commoftrad_resp} """ super(CommitmentsOfTraders, self).__init__() self.params = params @endpoint("labs/v1/orderbook_data") class OrderbookData(ForexLabs): """OrderbookData. Get the 'orderbook data' for an instrument. """ @dyndoc_insert(responses) def __init__(self, params): """Instantiate an OrderbookData request. Parameters ---------- params : dict (required) query params to send, check developer.oanda.com for details. >>> import oandapyV20 >>> import oandapyV20.endpoints.forexlabs as labs >>> accountID = ... >>> client = oandapyV20.API(access_token=...) >>> params = {_v3_forexlabs_orderbookdata_params} >>> r = labs.CommitmentOfTraders(params=params) >>> client.request(r) >>> print(r.response) Output:: {_v3_forexlabs_orderbookdata_resp} """ super(OrderbookData, self).__init__() self.params = params @endpoint("labs/v1/signal/autochartist") class Autochartist(ForexLabs): """Autochartist. Get the 'autochartist data'. """ @dyndoc_insert(responses) def __init__(self, params=None): """Instantiate an Autochartist request. Parameters ---------- params : dict (optional) query params to send, check developer.oanda.com for details. >>> import oandapyV20 >>> import oandapyV20.endpoints.forexlabs as labs >>> accountID = ... >>> client = oandapyV20.API(access_token=...) >>> params = {_v3_forexlabs_autochartist_params} >>> r = labs.Autochartist(params=params) >>> client.request(r) >>> print(r.response) Output:: {_v3_forexlabs_autochartist_resp} """ super(Autochartist, self).__init__() self.params = params ================================================ FILE: oandapyV20/endpoints/instruments.py ================================================ # -*- coding: utf-8 -*- """Handle instruments endpoints.""" from .apirequest import APIRequest from .decorators import dyndoc_insert, endpoint from .responses.instruments import responses from abc import abstractmethod class Instruments(APIRequest): """Instruments - abstract class to handle instruments endpoint.""" ENDPOINT = "" METHOD = "GET" @abstractmethod @dyndoc_insert(responses) def __init__(self, instrument): """Instantiate a Instrument APIRequest instance. Parameters ---------- instrument : string (required) the instrument to operate on params : dict with query parameters """ endpoint = self.ENDPOINT.format(instrument=instrument) super(Instruments, self).__init__(endpoint, method=self.METHOD) @endpoint("v3/instruments/{instrument}/candles") class InstrumentsCandles(Instruments): """Get candle data for a specified Instrument.""" @dyndoc_insert(responses) def __init__(self, instrument, params=None): """Instantiate an InstrumentsCandles request. Parameters ---------- instrument : string (required) the instrument to fetch candle data for params : dict optional request query parameters, check developer.oanda.com for details Params example:: {_v3_instruments_instrument_candles_params} Candle data example:: >>> import oandapyV20 >>> import oandapyV20.endpoints.instruments as instruments >>> client = oandapyV20.API(access_token=...) >>> params = ... >>> r = instruments.InstrumentsCandles(instrument="DE30_EUR", >>> params=params) >>> client.request(r) >>> print r.response Output:: {_v3_instruments_instrument_candles_resp} """ super(InstrumentsCandles, self).__init__(instrument) self.params = params @endpoint("v3/instruments/{instrument}/orderBook") class InstrumentsOrderBook(Instruments): """Get orderbook data for a specified Instrument.""" @dyndoc_insert(responses) def __init__(self, instrument, params=None): """Instantiate an InstrumentsOrderBook request. Parameters ---------- instrument : string (required) the instrument to fetch candle data for params : dict optional request query parameters, check developer.oanda.com for details Params example:: {_v3_instruments_instrument_orderbook_params} OrderBook data example:: >>> import oandapyV20 >>> import oandapyV20.endpoints.instruments as instruments >>> client = oandapyV20.API(access_token=...) >>> params = ... >>> r = instruments.InstrumentsOrderBook(instrument="EUR_USD", >>> params=params) >>> client.request(r) >>> print r.response Output:: {_v3_instruments_instrument_orderbook_resp} """ super(InstrumentsOrderBook, self).__init__(instrument) self.params = params @endpoint("v3/instruments/{instrument}/positionBook") class InstrumentsPositionBook(Instruments): """Get positionbook data for a specified Instrument.""" @dyndoc_insert(responses) def __init__(self, instrument, params=None): """Instantiate an InstrumentsPositionBook request. Parameters ---------- instrument : string (required) the instrument to fetch candle data for params : dict optional request query parameters, check developer.oanda.com for details Params example:: {_v3_instruments_instrument_positionbook_params} PositionBook data example:: >>> import oandapyV20 >>> import oandapyV20.endpoints.instruments as instruments >>> client = oandapyV20.API(access_token=...) >>> params = ... >>> r = instruments.InstrumentsPositionBook(instrument="EUR_USD", >>> params=params) >>> client.request(r) >>> print r.response Output:: {_v3_instruments_instrument_positionbook_resp} """ super(InstrumentsPositionBook, self).__init__(instrument) self.params = params ================================================ FILE: oandapyV20/endpoints/orders.py ================================================ # -*- coding: utf-8 -*- """Handle orders and pendingOrders endpoints.""" from .apirequest import APIRequest from .decorators import dyndoc_insert, endpoint from .responses.orders import responses from abc import abstractmethod class Orders(APIRequest): """Orders - abstract base class to handle the orders endpoints.""" ENDPOINT = "" METHOD = "GET" EXPECTED_STATUS = 0 @abstractmethod @dyndoc_insert(responses) def __init__(self, accountID, orderID=None): """Instantiate an Orders request. Parameters ---------- accountID : string (required) id of the account to perform the request on. orderID : string id of the order to perform the request for. """ endpoint = self.ENDPOINT.format(accountID=accountID, orderID=orderID) super(Orders, self).__init__(endpoint, method=self.METHOD, expected_status=self.EXPECTED_STATUS) @endpoint("v3/accounts/{accountID}/orders", "POST", 201) class OrderCreate(Orders): """Create an Order for an Account.""" HEADERS = {"Content-Type": "application/json"} @dyndoc_insert(responses) def __init__(self, accountID, data): """Instantiate an OrderCreate request. Parameters ---------- accountID : string (required) id of the account to perform the request on. data : JSON (required) json orderbody to send Orderbody example:: {_v3_accounts_accountID_orders_create_body} >>> import oandapyV20 >>> import oandapyV20.endpoints.orders as orders >>> client = oandapyV20.API(access_token=...) >>> r = orders.OrderCreate(accountID, data=data) >>> client.request(r) >>> print r.response :: {_v3_accounts_accountID_orders_create_resp} """ super(OrderCreate, self).__init__(accountID) self.data = data @endpoint("v3/accounts/{accountID}/orders") class OrderList(Orders): """Create an Order for an Account.""" @dyndoc_insert(responses) def __init__(self, accountID, params=None): """Instantiate an OrderList request. Parameters ---------- accountID : string (required) id of the account to perform the request on. params : dict optional request query parameters, check developer.oanda.com for details Example:: >>> import oandapyV20 >>> import oandapyV20.endpoints.orders as orders >>> client = oandapyV20.API(access_token=...) >>> r = orders.OrderList(accountID) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_orders_list_resp} """ super(OrderList, self).__init__(accountID) self.params = params @endpoint("v3/accounts/{accountID}/pendingOrders") class OrdersPending(Orders): """List all pending Orders in an Account.""" @dyndoc_insert(responses) def __init__(self, accountID): """Instantiate an OrdersPending request. Parameters ---------- accountID : string (required) id of the account to perform the request on. Example:: >>> import oandapyV20 >>> import oandapyV20.endpoints.orders as orders >>> client = oandapyV20.API(access_token=...) >>> r = orders.OrdersPending(accountID) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_orders_pending_resp} """ super(OrdersPending, self).__init__(accountID) @endpoint("v3/accounts/{accountID}/orders/{orderID}") class OrderDetails(Orders): """Get details for a single Order in an Account.""" @dyndoc_insert(responses) def __init__(self, accountID, orderID): """Instantiate an OrderDetails request. Parameters ---------- accountID : string (required) id of the account to perform the request on. orderID : string (required) id of the order to perform the request on. >>> import oandapyV20 >>> import oandapyV20.endpoints.orders as orders >>> client = oandapyV20.API(access_token=...) >>> r = orders.OrderDetails(accountID=..., orderID=...) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_order_details_resp} """ super(OrderDetails, self).__init__(accountID, orderID) @endpoint("v3/accounts/{accountID}/orders/{orderID}", "PUT", 201) class OrderReplace(Orders): """OrderReplace. Replace an Order in an Account by simultaneously cancelling it and creating a replacement Order. """ HEADERS = {"Content-Type": "application/json"} @dyndoc_insert(responses) def __init__(self, accountID, orderID, data): """Instantiate an OrderReplace request. Parameters ---------- accountID : string (required) id of the account to perform the request on. orderID : string (required) id of the order to perform the request on. data : JSON (required) json orderbody to send Orderbody example:: {_v3_accounts_accountID_order_replace_body} >>> import oandapyV20 >>> import oandapyV20.endpoints.orders as orders >>> client = oandapyV20.API(access_token=...) >>> data = {_v3_accounts_accountID_order_replace_body} >>> r = orders.OrderReplace(accountID=..., orderID=..., data=data) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_order_replace_resp} """ super(OrderReplace, self).__init__(accountID, orderID) self.data = data @endpoint("v3/accounts/{accountID}/orders/{orderID}/cancel", "PUT") class OrderCancel(Orders): """Cancel a pending Order in an Account.""" @dyndoc_insert(responses) def __init__(self, accountID, orderID): """Instantiate an OrdersCancel request. Parameters ---------- accountID : string (required) id of the account to perform the request on. orderID : string (required) id of the account to perform the request on. Example:: >>> import oandapyV20 >>> import oandapyV20.endpoints.orders as orders >>> client = oandapyV20.API(access_token=...) >>> r = orders.OrderCancel(accountID= ..., orderID=...) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_order_cancel_resp} """ super(OrderCancel, self).__init__(accountID, orderID) @endpoint("v3/accounts/{accountID}/orders/{orderID}/clientExtensions", "PUT") class OrderClientExtensions(Orders): """Update the Client Extensions for an Order in an Account. .. warning:: Do not set, modify or delete clientExtensions if your account is associated with MT4. """ HEADERS = {"Content-Type": "application/json"} @dyndoc_insert(responses) def __init__(self, accountID, orderID, data): """Instantiate an OrderCreate request. Parameters ---------- accountID : string (required) id of the account to perform the request on. orderID : string (required) id of the order to perform the request on. data : JSON (required) json orderbody to send Orderbody example:: {_v3_accounts_accountID_order_clientextensions_body} >>> import oandapyV20 >>> import oandapyV20.endpoints.orders as orders >>> client = oandapyV20.API(access_token=...) >>> r = orders.OrderClientExtensions(accountID, orderID, data=data) >>> client.request(r) >>> print r.response :: {_v3_accounts_accountID_order_clientextensions_resp} """ super(OrderClientExtensions, self).__init__(accountID, orderID) self.data = data ================================================ FILE: oandapyV20/endpoints/positions.py ================================================ """Handle position endpoints.""" from .apirequest import APIRequest from .decorators import dyndoc_insert, endpoint from .responses.positions import responses from abc import abstractmethod class Positions(APIRequest): """Positions - abstractbase class to handle the 'positions' endpoints.""" ENDPOINT = "" METHOD = "GET" @abstractmethod @dyndoc_insert(responses) def __init__(self, accountID, instrument=None): """Instantiate a Positions APIRequest instance. Parameters ---------- accountID : string (required) the id of the account to perform the request on. instrument : string (optional) the instrument for the Positions request """ endpoint = self.ENDPOINT.format(accountID=accountID, instrument=instrument) super(Positions, self).__init__(endpoint, method=self.METHOD) @endpoint("v3/accounts/{accountID}/positions") class PositionList(Positions): """PositionList. List all Positions for an Account. The Positions returned are for every instrument that has had a position during the lifetime of the Account. """ @dyndoc_insert(responses) def __init__(self, accountID): """Instantiate a PositionList request. Parameters ---------- accountID : string (required) id of the account to perform the request on. >>> import oandapyV20 >>> import oandapyV20.endpoints.positions as positions >>> accountID = ... >>> client = oandapyV20.API(access_token=...) >>> r = positions.PositionList(accountID=accountID) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_positions_resp} """ super(PositionList, self).__init__(accountID) @endpoint("v3/accounts/{accountID}/openPositions") class OpenPositions(Positions): """OpenPositions. List all open Positions for an Account. An open Position is a Position in an Account that currently has a Trade opened for it. """ @dyndoc_insert(responses) def __init__(self, accountID): """Instantiate an OpenPositions request. Parameters ---------- accountID : string (required) id of the account to perform the request on. >>> import oandapyV20 >>> import oandapyV20.endpoints.positions as positions >>> accountID = ... >>> client = oandapyV20.API(access_token=...) >>> r = positions.OpenPositions(accountID=accountID) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_openpositions_resp} """ super(OpenPositions, self).__init__(accountID) @endpoint("v3/accounts/{accountID}/positions/{instrument}") class PositionDetails(Positions): """PositionDetails. Get the details of a single instrument's position in an Account. The position may be open or not. """ @dyndoc_insert(responses) def __init__(self, accountID, instrument): """Instantiate a PositionDetails request. Parameters ---------- accountID : string (required) id of the account to perform the request on. instrument : string (required) id of the instrument to get the position details for. >>> import oandapyV20 >>> import oandapyV20.endpoints.positions as positions >>> accountID = ... >>> instrument = ... >>> client = oandapyV20.API(access_token=...) >>> r = positions.PositionDetails(accountID=accountID, instrument) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_positiondetails_resp} """ super(PositionDetails, self).__init__(accountID, instrument) @endpoint("v3/accounts/{accountID}/positions/{instrument}/close", "PUT") class PositionClose(Positions): """Closeout the open Position regarding instrument in an Account.""" HEADERS = {"Content-Type": "application/json"} @dyndoc_insert(responses) def __init__(self, accountID, instrument, data): """Instantiate a PositionClose request. Parameters ---------- accountID : string (required) id of the account to perform the request on. instrument : string (required) instrument to close partially or fully. data : dict (required) closeout specification data to send, check developer.oanda.com for details. Data body example:: {_v3_accounts_accountID_position_close_body} >>> import oandapyV20 >>> import oandapyV20.endpoints.positions as positions >>> accountID = ... >>> instrument = ... >>> client = oandapyV20.API(access_token=...) >>> data = {_v3_accounts_accountID_position_close_body} >>> r = positions.PositionClose(accountID=accountID, >>> instrument=instrument, >>> data=data) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_position_close_resp} """ super(PositionClose, self).__init__(accountID, instrument) self.data = data ================================================ FILE: oandapyV20/endpoints/pricing.py ================================================ # -*- coding: utf-8 -*- """Handle pricing endpoints.""" from .apirequest import APIRequest from ..exceptions import StreamTerminated from .decorators import dyndoc_insert, endpoint from .responses.pricing import responses from types import GeneratorType from abc import abstractmethod class Pricing(APIRequest): """Pricing - class to handle pricing endpoint.""" ENDPOINT = "" METHOD = "GET" @abstractmethod def __init__(self, accountID): """Instantiate a Pricing APIRequest instance. Parameters ---------- accountID : string (required) the accountID of the account. """ endpoint = self.ENDPOINT.format(accountID=accountID) super(Pricing, self).__init__(endpoint, method=self.METHOD) @endpoint("v3/accounts/{accountID}/pricing") class PricingInfo(Pricing): """Pricing. Get pricing information for a specified list of Instruments within an account. """ @dyndoc_insert(responses) def __init__(self, accountID, params=None): """Instantiate a PricingStream APIRequest instance. Parameters ---------- accountID : string (required) the accountID of the account. params : dict (required) parameters for the request, check developer.oanda.com for details. Example ------- >>> import oandapyV20 >>> from oandapyV20 import API >>> import oandapyV20.endpoints.pricing as pricing >>> accountID = "..." >>> api = API(access_token="...") >>> params = {_v3_accounts_accountID_pricing_params} >>> r = pricing.PricingInfo(accountID=accountID, params=params) >>> rv = api.request(r) >>> print r.response Output:: {_v3_accounts_accountID_pricing_resp} """ super(PricingInfo, self).__init__(accountID) self.params = params @endpoint("v3/accounts/{accountID}/pricing/stream") class PricingStream(Pricing): """PricingStream. Get realtime pricing information for a specified list of Instruments. """ STREAM = True @dyndoc_insert(responses) def __init__(self, accountID, params=None): """Instantiate a PricingStream APIRequest instance. Parameters ---------- accountID : string (required) the accountID of the account. params : dict (required) parameters for the request, check developer.oanda.com for details. Example ------- >>> import oandapyV20 >>> from oandapyV20 import API >>> import oandapyV20.endpoints.pricing as pricing >>> accountID = "..." >>> api = API(access_token="...") >>> params = {_v3_accounts_accountID_pricing_stream_params} >>> r = pricing.PricingStream(accountID=accountID, params=params) >>> rv = api.request(r) >>> maxrecs = 100 >>> for ticks in r: >>> print json.dumps(R, indent=4),"," >>> if maxrecs == 0: >>> r.terminate("maxrecs records received") Output:: {_v3_accounts_accountID_pricing_stream_ciresp} """ super(PricingStream, self).__init__(accountID) self.params = params def terminate(self, message=""): """terminate the stream. Calling this method will stop the generator yielding tickrecords. A message can be passed optionally. """ if not isinstance(self.response, GeneratorType): raise ValueError("request does not contain a stream response") self.response.throw(StreamTerminated(message)) ================================================ FILE: oandapyV20/endpoints/responses/__init__.py ================================================ ================================================ FILE: oandapyV20/endpoints/responses/accounts.py ================================================ """Responses. responses serve both testing purpose aswell as dynamic docstring replacement """ responses = { "_v3_accounts": { "url": "/v3/accounts", "response": { "accounts": [ { "id": "101-004-1435156-002", "tags": [] }, { "id": "101-004-1435156-001", "tags": [] } ] }, }, "_v3_account_by_accountID": { "url": "/v3/accounts/{}", "response": { "account": { "trades": [ { "instrument": "DE30_EUR", "financing": "0.0000", "openTime": "2016-07-12T09:32:18.062823776Z", "initialUnits": "-10", "currentUnits": "-10", "price": "9984.7", "unrealizedPL": "341.0000", "realizedPL": "0.0000", "state": "OPEN", "id": "821" }, { "instrument": "DE30_EUR", "financing": "0.0000", "openTime": "2016-07-12T09:32:18.206929733Z", "initialUnits": "-10", "currentUnits": "-10", "price": "9984.7", "unrealizedPL": "341.0000", "realizedPL": "0.0000", "state": "OPEN", "id": "823" } ], "marginCloseoutNAV": "49393.6580", "marginUsed": "9948.9000", "currency": "EUR", "resettablePL": "-1301.0046", "NAV": "49377.6580", "marginCloseoutMarginUsed": "9949.8000", "id": "101-004-1435156-001", "marginCloseoutPositionValue": "198996.0000", "openTradeCount": 2, "orders": [ { "partialFill": "DEFAULT_FILL", "price": "0.87000", "stopLossOnFill": { "timeInForce": "GTC", "price": "0.88000" }, "timeInForce": "GTC", "clientExtensions": { "comment": "myComment", "id": "myID" }, "id": "204", "triggerCondition": "TRIGGER_DEFAULT", "replacesOrderID": "200", "positionFill": "POSITION_DEFAULT", "createTime": "2016-07-08T07:18:47.623211321Z", "instrument": "EUR_GBP", "state": "PENDING", "units": "-50000", "type": "LIMIT" } ], "hedgingEnabled": False, "marginCloseoutPercent": "0.10072", "marginCallMarginUsed": "9949.8000", "openPositionCount": 1, "positionValue": "198978.0000", "pl": "-1301.0046", "lastTransactionID": "833", "marginAvailable": "39428.7580", "marginCloseoutUnrealizedPL": "698.0000", "marginRate": "0.05", "marginCallPercent": "0.20144", "pendingOrderCount": 1, "withdrawalLimit": "39428.7580", "unrealizedPL": "682.0000", "alias": "hootnotv20", "createdByUserID": 1435156, "positions": [ { "short": { "units": "0", "resettablePL": "0.0000", "unrealizedPL": "0.0000", "pl": "0.0000" }, "unrealizedPL": "0.0000", "long": { "units": "0", "resettablePL": "-3.8046", "unrealizedPL": "0.0000", "pl": "-3.8046" }, "instrument": "EUR_USD", "resettablePL": "-3.8046", "pl": "-3.8046" }, { "short": { "unrealizedPL": "682.0000", "tradeIDs": [ "821", "823" ], "resettablePL": "-1744.8000", "units": "-20", "averagePrice": "9984.7", "pl": "-1744.8000" }, "unrealizedPL": "682.0000", "long": { "units": "0", "resettablePL": "447.6000", "unrealizedPL": "0.0000", "pl": "447.6000" }, "instrument": "DE30_EUR", "resettablePL": "-1297.2000", "pl": "-1297.2000" } ], "createdTime": "2016-06-24T21:03:50.914647476Z", "balance": "48695.6580" }, "lastTransactionID": "833" } }, "_v3_account_by_accountID_summary": { "url": "v3/accounts/{accountID}/summary", "response": { "account": { "marginCloseoutNAV": "35454.4740", "marginUsed": "10581.5000", "currency": "EUR", "resettablePL": "-13840.3525", "NAV": "35454.4740", "marginCloseoutMarginUsed": "10581.5000", "marginCloseoutPositionValue": "211630.0000", "openTradeCount": 2, "id": "101-004-1435156-001", "openPositionCount": 1, "marginCloseoutPercent": "0.14923", "marginCallMarginUsed": "10581.5000", "hedgingEnabled": False, "positionValue": "211630.0000", "pl": "-13840.3525", "lastTransactionID": "2123", "marginAvailable": "24872.9740", "marginRate": "0.05", "marginCallPercent": "0.29845", "pendingOrderCount": 0, "withdrawalLimit": "24872.9740", "unrealizedPL": "0.0000", "alias": "hootnotv20", "createdByUserID": 1435156, "marginCloseoutUnrealizedPL": "0.0000", "createdTime": "2016-06-24T21:03:50.914647476Z", "balance": "35454.4740" }, "lastTransactionID": "2123" } }, "_v3_account_by_accountID_instruments": { "url": "/v3/accounts/{accountID}/instuments", "params": { "instruments": "EU50_EUR,EUR_USD,US30_USD," "FR40_EUR,EUR_CHF,DE30_EUR" }, "response": { "instruments": [ { "minimumTradeSize": "1", "displayName": "Europe 50", "name": "EU50_EUR", "displayPrecision": 1, "type": "CFD", "minimumTrailingStopDistance": "5.0", "marginRate": "0.05", "maximumOrderUnits": "3000", "tradeUnitsPrecision": 0, "pipLocation": 0, "maximumPositionSize": "0", "maximumTrailingStopDistance": "10000.0" }, { "minimumTradeSize": "1", "displayName": "EUR/USD", "name": "EUR_USD", "displayPrecision": 5, "type": "CURRENCY", "minimumTrailingStopDistance": "0.00050", "marginRate": "0.05", "maximumOrderUnits": "100000000", "tradeUnitsPrecision": 0, "pipLocation": -4, "maximumPositionSize": "0", "maximumTrailingStopDistance": "1.00000" }, { "minimumTradeSize": "1", "displayName": "US Wall St 30", "name": "US30_USD", "displayPrecision": 1, "type": "CFD", "minimumTrailingStopDistance": "5.0", "marginRate": "0.05", "maximumOrderUnits": "1000", "tradeUnitsPrecision": 0, "pipLocation": 0, "maximumPositionSize": "0", "maximumTrailingStopDistance": "10000.0" }, { "minimumTradeSize": "1", "displayName": "France 40", "name": "FR40_EUR", "displayPrecision": 1, "type": "CFD", "minimumTrailingStopDistance": "5.0", "marginRate": "0.05", "maximumOrderUnits": "2000", "tradeUnitsPrecision": 0, "pipLocation": 0, "maximumPositionSize": "0", "maximumTrailingStopDistance": "10000.0" }, { "minimumTradeSize": "1", "displayName": "EUR/CHF", "name": "EUR_CHF", "displayPrecision": 5, "type": "CURRENCY", "minimumTrailingStopDistance": "0.00050", "marginRate": "0.05", "maximumOrderUnits": "100000000", "tradeUnitsPrecision": 0, "pipLocation": -4, "maximumPositionSize": "0", "maximumTrailingStopDistance": "1.00000" }, { "minimumTradeSize": "1", "displayName": "Germany 30", "name": "DE30_EUR", "displayPrecision": 1, "type": "CFD", "minimumTrailingStopDistance": "5.0", "marginRate": "0.05", "maximumOrderUnits": "2500", "tradeUnitsPrecision": 0, "pipLocation": 0, "maximumPositionSize": "0", "maximumTrailingStopDistance": "10000.0" }, ], "lastTransactionID": "2124" }, }, "_v3_accounts_accountID_account_config": { "url": "/v3/accounts/{accountID}/configuration", "body": { "marginRate": "0.05" }, "response": { "lastTransactionID": "830", "clientConfigureTransaction": { "userID": 1435156, "marginRate": "0.05", "batchID": "830", "time": "2016-07-12T19:48:11.657494168Z", "type": "CLIENT_CONFIGURE", "id": "830", "accountID": "101-004-1435156-001" } }, }, "_v3_accounts_accountID_account_changes": { "url": "/v3/accounts/{accountID}/changes", "params": { "sinceTransactionID": 2308 }, "response": { "state": { "trades": [], "marginCloseoutNAV": "33848.2663", "unrealizedPL": "0.0000", "marginUsed": "0.0000", "marginAvailable": "33848.2663", "positions": [], "marginCloseoutUnrealizedPL": "0.0000", "marginCallMarginUsed": "0.0000", "marginCallPercent": "0.00000", "marginCloseoutPercent": "0.00000", "NAV": "33848.2663", "marginCloseoutMarginUsed": "0.0000", "positionValue": "0.0000", "orders": [], "withdrawalLimit": "33848.2663" }, "changes": { "tradesReduced": [], "tradesOpened": [], "ordersFilled": [], "transactions": [ { "timeInForce": "GTC", "triggerCondition": "TRIGGER_DEFAULT", "positionFill": "DEFAULT", "stopLossOnFill": { "timeInForce": "GTC", "price": "1.22000" }, "userID": 1435156, "id": "2309", "batchID": "2309", "instrument": "EUR_USD", "reason": "CLIENT_ORDER", "time": "2016-10-25T21:07:21.065554321Z", "units": "-100", "type": "LIMIT_ORDER", "price": "1.20000", "accountID": "101-004-1435156-001" } ], "ordersCreated": [ { "triggerCondition": "TRIGGER_DEFAULT", "partialFill": "DEFAULT_FILL", "price": "1.20000", "stopLossOnFill": { "timeInForce": "GTC", "price": "1.22000" }, "createTime": "2016-10-25T21:07:21.065554321Z", "timeInForce": "GTC", "instrument": "EUR_USD", "state": "PENDING", "units": "-100", "id": "2309", "type": "LIMIT", "positionFill": "POSITION_DEFAULT" } ], "positions": [], "ordersTriggered": [], "ordersCancelled": [], "tradesClosed": [] }, "lastTransactionID": "2309" } } } ================================================ FILE: oandapyV20/endpoints/responses/forexlabs.py ================================================ """Responses. responses serve both testing purpose aswell as dynamic docstring replacement """ responses = { "_v3_forexlabs_calendar": { "url": "labs/v1/calendar", "params": { "period": 86400, "instrument": "EUR_USD" }, "response": [ { "impact": 3, "actual": "60.8", "title": "ISM Manufacturing", "timestamp": 1519916400, "region": "americas", "forecast": "59.5", "currency": "USD", "unit": "index", "market": "58.7", "previous": "59.1" } ] }, "_v3_forexlabs_histposratios": { "url": "labs/v1/historical_position_ratios", "params": { "period": 86400, "instrument": "EUR_USD" }, "response": { "data": { "EUR_USD": { "data": [ [ 1519924801, 44.22, 1.2209 ], [ 1519926001, 44.33, 1.221 ], [ 1519927200, 44.16, 1.2212 ], [ 1519928400, 44.2, 1.2209 ], [ 1519929601, 43.88, 1.2201 ], [ 1519930800, 44.15, 1.2197 ], [ 1519932000, 44.51, 1.2204 ], [ 1519933200, 44.55, 1.2233 ], [ 1519934401, 44.55, 1.2254 ], [ 1519935601, 44.08, 1.226 ], [ 1519936801, 43.67, 1.2264 ], [ 1519938001, 43.55, 1.2263 ], [ 1519939201, 43.25, 1.2261 ], [ 1519940401, 43.28, 1.2263 ], [ 1519941601, 43.39, 1.2267 ], [ 1519942801, 43.69, 1.227 ], [ 1519944001, 43.57, 1.2269 ], [ 1519945201, 43.68, 1.2272 ], [ 1519946400, 43.51, 1.2268 ], [ 1519947601, 43.53, 1.2267 ], [ 1519948801, 43.71, 1.2271 ], [ 1519950001, 43.66, 1.2265 ], [ 1519951201, 43.78, 1.2269 ], [ 1519952401, 43.86, 1.2273 ], [ 1519953600, 43.85, 1.2273 ], [ 1519954800, 43.81, 1.2271 ], [ 1519956001, 44, 1.2275 ], [ 1519957200, 43.89, 1.2274 ], [ 1519958401, 43.95, 1.2273 ], [ 1519959601, 43.93, 1.2273 ], [ 1519960800, 43.86, 1.2276 ], [ 1519962000, 44.02, 1.2278 ], [ 1519963200, 44.18, 1.228 ], [ 1519964401, 44.52, 1.2283 ], [ 1519965600, 44.19, 1.2281 ], [ 1519966801, 44.14, 1.2278 ], [ 1519968000, 43.93, 1.2276 ], [ 1519969201, 43.82, 1.2277 ], [ 1519970401, 43.77, 1.2279 ], [ 1519971601, 43.02, 1.2269 ], [ 1519972801, 42.99, 1.2265 ], [ 1519974001, 42.73, 1.2263 ], [ 1519975201, 42.22, 1.2262 ], [ 1519976400, 42.13, 1.2255 ], [ 1519977601, 42.02, 1.2263 ], [ 1519978801, 42.15, 1.2261 ], [ 1519980000, 42.5, 1.2273 ], [ 1519981201, 42.2, 1.2274 ], [ 1519982400, 42.06, 1.2271 ], [ 1519983600, 42.38, 1.2279 ], [ 1519984800, 42.29, 1.2276 ], [ 1519986000, 42.16, 1.2281 ], [ 1519987201, 43.46, 1.2291 ], [ 1519988401, 43.51, 1.2291 ], [ 1519989601, 43.4, 1.2317 ], [ 1519990800, 43.46, 1.2317 ], [ 1519992001, 43.07, 1.2304 ], [ 1519993201, 43.56, 1.2316 ], [ 1519994401, 43.75, 1.2319 ], [ 1519995601, 43.15, 1.2308 ], [ 1519996801, 42.94, 1.2309 ], [ 1519998001, 42.99, 1.2315 ], [ 1519999201, 42.33, 1.2309 ], [ 1520000400, 41.93, 1.2299 ], [ 1520001601, 42.31, 1.2303 ], [ 1520002801, 42.5, 1.2313 ], [ 1520004000, 42.8, 1.2326 ], [ 1520005201, 42.67, 1.2317 ], [ 1520006401, 42.29, 1.2309 ], [ 1520007600, 42.33, 1.2309 ], [ 1520008800, 42.63, 1.2321 ], [ 1520010001, 42.11, 1.2314 ] ], "label": "EUR/USD" } } } }, "_v3_forexlabs_spreads": { "url": "labs/v1/spreads", "params": { "period": 57600, "instrument": "EUR_USD" }, "response": { "max": [ [ 1520028000, 6 ] ], "avg": [ [ 1520028000, 3.01822 ] ], "min": [ [ 1520028000, 1.7 ] ] } }, "_v3_forexlabs_commoftrad": { "url": "labs/v1/commitments_of_traders", "params": { "instrument": "EUR_USD" }, "response": { "EUR_USD": [ { "oi": "603460", "price": "1.2315925", "ncs": "109280", "ncl": "258022", "date": 1517288400, "unit": "Contracts Of EUR 125,000" }, { "oi": "596937", "price": "1.2364", "ncs": "110546", "ncl": "251369", "date": 1517893200, "unit": "Contracts Of EUR 125,000" }, { "oi": "564233", "price": "1.2330275", "ncs": "103496", "ncl": "230785", "date": 1518498000, "unit": "Contracts Of EUR 125,000" }, { "oi": "567534", "price": "1.2346025", "ncs": "103147", "ncl": "229273", "date": 1519102800, "unit": "Contracts Of EUR 125,000" }, { "oi": "567463", "price": "1.23557", "ncs": "100310", "ncl": "238287", "date": 1519707600, "unit": "Contracts Of EUR 125,000" } ] } }, "_v3_forexlabs_orderbookdata": { "url": "labs/v1/orderbook_data", "params": { "instrument": "EUR_USD", "period": 3600 }, "response": { "1520066400": { "rate": 1.2318, "price_points": { "1.288": { "ps": 0, "ol": 0.0105, "os": 0.0105, "pl": 0 }, "1.23": { "ps": 1.2155, "ol": 0.3871, "os": 0.2615, "pl": 0.5633 }, "1.223": { "ps": 1.1266, "ol": 0.5021, "os": 0.2197, "pl": 0.3854 }, "1.1825": { "ps": 0.1779, "ol": 0.1465, "os": 0.0628, "pl": 0 }, "1.22": { "ps": 0.9191, "ol": 0.6486, "os": 0.136, "pl": 0.2965 }, "1.2245": { "ps": 0.5336, "ol": 0.5021, "os": 0.3975, "pl": 0.4447 }, "1.2085": { "ps": 0.1482, "ol": 0.2092, "os": 0.2197, "pl": 0.1482 }, "1.26": { "ps": 0, "ol": 0.2197, "os": 0.68, "pl": 0 }, "1.25": { "ps": 0.0593, "ol": 0.272, "os": 1.0566, "pl": 0.1186 }, "1.24": { "ps": 0.1186, "ol": 0.4289, "os": 0.8264, "pl": 0.4447 } } } } }, "_v3_forexlabs_autochartist": { "url": "labs/v1/signal/autochartist", "params": { "instrument": "EUR_JPY" }, "response": { "signals": [ { "type": "chartpattern", "meta": { "direction": 1, "probability": 72.36, "pattern": "Channel Down", "historicalstats": { "hourofday": { "total": 1909, "percent": 71.08, "correct": 1357 }, "pattern": { "total": 3361, "percent": 73.61, "correct": 2474 }, "symbol": { "total": 429, "percent": 65.5, "correct": 281 } }, "interval": 60, "length": 73, "scores": { "breakout": 10, "clarity": 7, "quality": 8, "initialtrend": 10, "uniformity": 6 }, "trendtype": "Continuation", "completed": 1 }, "data": { "points": { "support": { "y1": 0.72456, "y0": 0.725455, "x0": 1520420400, "x1": 1520503200 }, "resistance": { "y1": 0.729755, "y0": 0.731095, "x0": 1520323200, "x1": 1520463600 } }, "patternendtime": 1520589600, "prediction": { "timefrom": 1520589600, "pricelow": 0.7316, "timeto": 1520773200, "pricehigh": 0.7349 } }, "id": 458552738, "instrument": "NZD_USD" } ], "provider": "autochartist" } }, } ================================================ FILE: oandapyV20/endpoints/responses/instruments.py ================================================ """Responses. responses serve both testing purpose aswell as dynamic docstring replacement """ responses = { "_v3_instruments_instrument_orderbook": { "url": "/v3/instruments/{instrument}/orderBook", "instrument": "EUR_USD", "params": {}, "response": { "orderBook": { "buckets": [ { "price": "1.12850", "shortCountPercent": "0.2352", "longCountPercent": "0.2666" }, { "price": "1.12900", "shortCountPercent": "0.2195", "longCountPercent": "0.3293" }, { "price": "1.12950", "shortCountPercent": "0.3136", "longCountPercent": "0.2901" }, { "price": "1.13000", "shortCountPercent": "0.3842", "longCountPercent": "0.4156" }, { "price": "1.13050", "shortCountPercent": "0.1960", "longCountPercent": "0.3685" }, { "price": "1.13100", "shortCountPercent": "0.2431", "longCountPercent": "0.2901" }, { "price": "1.13150", "shortCountPercent": "0.2509", "longCountPercent": "0.3136" }, { "price": "1.13200", "shortCountPercent": "0.2587", "longCountPercent": "0.3450" }, { "price": "1.13250", "shortCountPercent": "0.3842", "longCountPercent": "0.2666" }, { "price": "1.13300", "shortCountPercent": "0.3371", "longCountPercent": "0.3371" }, { "price": "1.13350", "shortCountPercent": "0.3528", "longCountPercent": "0.2744" }, { "price": "1.13400", "shortCountPercent": "0.3842", "longCountPercent": "0.3136" }, { "price": "1.13450", "shortCountPercent": "0.2039", "longCountPercent": "0.2744" }, { "price": "1.13500", "shortCountPercent": "0.1882", "longCountPercent": "0.3371" }, { "price": "1.13550", "shortCountPercent": "0.0235", "longCountPercent": "0.0392" }, { "price": "1.13600", "shortCountPercent": "0.0549", "longCountPercent": "0.0314" }, { "price": "1.13650", "shortCountPercent": "0.1333", "longCountPercent": "0.0314" }, { "price": "1.13700", "shortCountPercent": "0.1176", "longCountPercent": "0.1019" }, { "price": "1.13750", "shortCountPercent": "0.1568", "longCountPercent": "0.0784" }, { "price": "1.13800", "shortCountPercent": "0.1176", "longCountPercent": "0.0862" }, { "price": "1.13850", "shortCountPercent": "0.2117", "longCountPercent": "0.1960" }, { "price": "1.13900", "shortCountPercent": "0.4548", "longCountPercent": "0.2587" }, { "price": "1.13950", "shortCountPercent": "0.2979", "longCountPercent": "0.3215" }, { "price": "1.14000", "shortCountPercent": "0.7449", "longCountPercent": "0.2901" }, { "price": "1.14050", "shortCountPercent": "0.2117", "longCountPercent": "0.1176" }, { "price": "1.14100", "shortCountPercent": "0.1960", "longCountPercent": "0.1333" }, { "price": "1.14150", "shortCountPercent": "0.1882", "longCountPercent": "0.1176" }, ], "instrument": "EUR_USD", "price": "1.13609", "bucketWidth": "0.00050", "time": "2017-06-28T10:00:00Z" } } }, "_v3_instruments_instrument_positionbook": { "url": "/v3/instruments/{instrument}/positionBook", "instrument": "EUR_USD", "params": {}, "response": { "positionBook": { "buckets": [ { "price": "1.12800", "shortCountPercent": "0.2670", "longCountPercent": "0.2627" }, { "price": "1.12850", "shortCountPercent": "0.2034", "longCountPercent": "0.2712" }, { "price": "1.12900", "shortCountPercent": "0.2034", "longCountPercent": "0.2161" }, { "price": "1.12950", "shortCountPercent": "0.2670", "longCountPercent": "0.2839" }, { "price": "1.13000", "shortCountPercent": "0.2755", "longCountPercent": "0.3221" }, { "price": "1.13050", "shortCountPercent": "0.1949", "longCountPercent": "0.2839" }, { "price": "1.13100", "shortCountPercent": "0.2288", "longCountPercent": "0.2712" }, { "price": "1.13150", "shortCountPercent": "0.2416", "longCountPercent": "0.2712" }, { "price": "1.13200", "shortCountPercent": "0.2204", "longCountPercent": "0.3178" }, { "price": "1.13250", "shortCountPercent": "0.2543", "longCountPercent": "0.2458" }, { "price": "1.13300", "shortCountPercent": "0.2839", "longCountPercent": "0.2585" }, { "price": "1.13350", "shortCountPercent": "0.3602", "longCountPercent": "0.3094" }, { "price": "1.13400", "shortCountPercent": "0.2882", "longCountPercent": "0.3560" }, { "price": "1.13450", "shortCountPercent": "0.2500", "longCountPercent": "0.3009" }, { "price": "1.13500", "shortCountPercent": "0.1738", "longCountPercent": "0.3475" }, { "price": "1.13550", "shortCountPercent": "0.2119", "longCountPercent": "0.2797" }, { "price": "1.13600", "shortCountPercent": "0.1483", "longCountPercent": "0.3094" }, { "price": "1.13650", "shortCountPercent": "0.1483", "longCountPercent": "0.1314" }, { "price": "1.13700", "shortCountPercent": "0.1568", "longCountPercent": "0.2034" }, { "price": "1.13750", "shortCountPercent": "0.1398", "longCountPercent": "0.1271" }, { "price": "1.13800", "shortCountPercent": "0.1314", "longCountPercent": "0.2034" }, { "price": "1.13850", "shortCountPercent": "0.1483", "longCountPercent": "0.1695" }, { "price": "1.13900", "shortCountPercent": "0.2924", "longCountPercent": "0.1653" }, { "price": "1.13950", "shortCountPercent": "0.1526", "longCountPercent": "0.1865" }, { "price": "1.14000", "shortCountPercent": "0.4365", "longCountPercent": "0.2034" }, { "price": "1.14050", "shortCountPercent": "0.1398", "longCountPercent": "0.1144" } ], "instrument": "EUR_USD", "price": "1.13609", "bucketWidth": "0.00050", "time": "2017-06-28T10:00:00Z" } } }, "_v3_instruments_instrument_candles": { "url": "/v3/instruments/{instrument}/candles", "instrument": "DE30_EUR", "params": { "count": 5, "granularity": "M5" }, "response": { "candles": [ { "volume": 132, "time": "2016-10-17T19:35:00.000000000Z", "complete": True, "mid": { "h": "10508.0", "c": "10506.0", "l": "10503.8", "o": "10503.8" } }, { "volume": 162, "time": "2016-10-17T19:40:00.000000000Z", "complete": True, "mid": { "h": "10507.0", "c": "10504.9", "l": "10502.0", "o": "10506.0" } }, { "volume": 196, "time": "2016-10-17T19:45:00.000000000Z", "complete": True, "mid": { "h": "10509.8", "c": "10505.0", "l": "10502.6", "o": "10504.9" } }, { "volume": 153, "time": "2016-10-17T19:50:00.000000000Z", "complete": True, "mid": { "h": "10510.1", "c": "10509.0", "l": "10504.2", "o": "10505.0" } }, { "volume": 172, "time": "2016-10-17T19:55:00.000000000Z", "complete": True, "mid": { "h": "10509.8", "c": "10507.8", "l": "10503.2", "o": "10509.0" } } ], "instrument": "DE30/EUR", "granularity": "M5" } } } ================================================ FILE: oandapyV20/endpoints/responses/orders.py ================================================ # -*- coding: utf-8 -*- """Responses. responses serve both testing purpose aswell as dynamic docstring replacement """ responses = { "_v3_accounts_accountID_orders_create": { "url": "v3/accounts/{accountID}/orders", "body": { "order": { "stopLossOnFill": { "timeInForce": "GTC", "price": "1.22" }, "units": "-100", "price": "1.2", "instrument": "EUR_USD", "timeInForce": "GTC", "type": "LIMIT", "positionFill": "DEFAULT" } }, "response": { "orderCreateTransaction": { "timeInForce": "GTC", "triggerCondition": "TRIGGER_DEFAULT", "positionFill": "DEFAULT", "stopLossOnFill": { "timeInForce": "GTC", "price": "1.22000" }, "userID": 1435156, "id": "2304", "batchID": "2304", "instrument": "EUR_USD", "reason": "CLIENT_ORDER", "time": "2016-10-24T21:48:18.593753865Z", "units": "-100", "type": "LIMIT_ORDER", "price": "1.20000", "accountID": "101-004-1435156-001" }, "relatedTransactionIDs": [ "2304" ], "lastTransactionID": "2304" } }, "_v3_accounts_accountID_orders_pending": { "url": "v3/accounts/{accountID}/orders", "response": { "orders": [ { "timeInForce": "GTC", "triggerCondition": "TRIGGER_DEFAULT", "partialFill": "DEFAULT_FILL", "positionFill": "POSITION_DEFAULT", "stopLossOnFill": { "timeInForce": "GTC", "price": "1.22000" }, "id": "2304", "price": "1.20000", "instrument": "EUR_USD", "state": "PENDING", "units": "-100", "clientExtensions": { "comment": "myComment", "id": "myID" }, "type": "LIMIT", "createTime": "2016-10-24T21:48:18.593753865Z" } ], "lastTransactionID": "2305" } }, "_v3_accounts_accountID_order_cancel": { "url": "v3/accounts/{accountID}/orders/{orderID}/cancel", "orderID": "2307", "response": { "orderCancelTransaction": { "orderID": "2307", "clientOrderID": "myID", "userID": 1435156, "batchID": "2308", "reason": "CLIENT_REQUEST", "time": "2016-10-25T20:53:03.789670387Z", "type": "ORDER_CANCEL", "id": "2308", "accountID": "101-004-1435156-001" }, "relatedTransactionIDs": [ "2308" ], "lastTransactionID": "2308" } }, "_v3_accounts_accountID_orders_list": { "url": "v3/accounts/{accountID}/orders", "response": { "orders": [ { "triggerCondition": "TRIGGER_DEFAULT", "partialFill": "DEFAULT_FILL", "price": "1.20000", "stopLossOnFill": { "timeInForce": "GTC", "price": "1.22000" }, "createTime": "2016-10-05T10:25:47.627003645Z", "timeInForce": "GTC", "instrument": "EUR_USD", "state": "PENDING", "units": "-100", "id": "2125", "type": "LIMIT", "positionFill": "POSITION_DEFAULT" } ], "lastTransactionID": "2129" } }, "_v3_accounts_accountID_order_details": { "url": "v3/accounts/{accountID}/orders/{orderID}/details", "orderID": "2309", "response": { "order": { "triggerCondition": "TRIGGER_DEFAULT", "partialFill": "DEFAULT_FILL", "price": "1.20000", "stopLossOnFill": { "timeInForce": "GTC", "price": "1.22000" }, "createTime": "2016-10-25T21:07:21.065554321Z", "timeInForce": "GTC", "instrument": "EUR_USD", "state": "PENDING", "units": "-100", "id": "2309", "type": "LIMIT", "positionFill": "POSITION_DEFAULT" }, "lastTransactionID": "2309" } }, "_v3_accounts_accountID_order_replace": { "url": "v3/accounts/{accountID}/orders", "body": { "order": { "units": "-500000", "type": "LIMIT", "instrument": "EUR_USD", "price": "1.25000", } }, "response": { "orderCreateTransaction": { "timeInForce": "GTC", "triggerCondition": "TRIGGER_DEFAULT", "replacesOrderID": "2304", "positionFill": "DEFAULT", "userID": 1435156, "units": "-500000", "batchID": "2306", "instrument": "EUR_USD", "reason": "REPLACEMENT", "time": "2016-10-25T19:45:38.558056359Z", "price": "1.25000", "clientExtensions": { "comment": "myComment", "id": "myID" }, "type": "LIMIT_ORDER", "id": "2307", "accountID": "101-004-1435156-001" }, "orderCancelTransaction": { "orderID": "2304", "clientOrderID": "myID", "replacedByOrderID": "2307", "userID": 1435156, "batchID": "2306", "reason": "CLIENT_REQUEST_REPLACED", "time": "2016-10-25T19:45:38.558056359Z", "type": "ORDER_CANCEL", "id": "2306", "accountID": "101-004-1435156-001" }, "relatedTransactionIDs": [ "2306", "2307" ], "lastTransactionID": "2307" } }, "_v3_accounts_accountID_order_clientextensions": { "url": "v3/accounts/{accountID}/orders/{orderID}/clientExtensions", "orderID": 2304, "body": { "clientExtensions": { "id": "myID", "comment": "myComment", } }, "response": { "relatedTransactionIDs": [ "2305" ], "orderClientExtensionsModifyTransaction": { "orderID": "2304", "userID": 1435156, "batchID": "2305", "clientExtensionsModify": { "comment": "myComment", "id": "myID" }, "time": "2016-10-25T15:56:43.075594239Z", "type": "ORDER_CLIENT_EXTENSIONS_MODIFY", "id": "2305", "accountID": "101-004-1435156-001" }, "lastTransactionID": "2305" } } } ================================================ FILE: oandapyV20/endpoints/responses/positions.py ================================================ """Responses. responses serve both testing purpose aswell as dynamic docstring replacement """ responses = { "_v3_accounts_accountID_positions": { "url": "v3/accounts/{accountID}/positions", "response": { "positions": [ { "short": { "units": "0", "resettablePL": "-272.6805", "unrealizedPL": "0.0000", "pl": "-272.6805" }, "unrealizedPL": "0.0000", "long": { "units": "0", "resettablePL": "0.0000", "unrealizedPL": "0.0000", "pl": "0.0000" }, "instrument": "EUR_GBP", "resettablePL": "-272.6805", "pl": "-272.6805" }, { "short": { "unrealizedPL": "870.0000", "units": "-20", "resettablePL": "-13959.3000", "tradeIDs": [ "2121", "2123" ], "averagePrice": "10581.5", "pl": "-13959.3000" }, "unrealizedPL": "870.0000", "long": { "units": "0", "resettablePL": "404.5000", "unrealizedPL": "0.0000", "pl": "404.5000" }, "instrument": "DE30_EUR", "resettablePL": "-13554.8000", "pl": "-13554.8000" }, { "short": { "units": "0", "resettablePL": "0.0000", "unrealizedPL": "0.0000", "pl": "0.0000" }, "unrealizedPL": "0.0000", "long": { "units": "0", "resettablePL": "-12.8720", "unrealizedPL": "0.0000", "pl": "-12.8720" }, "instrument": "EUR_USD", "resettablePL": "-12.8720", "pl": "-12.8720" } ], "lastTransactionID": "2124" } }, "_v3_accounts_accountID_openpositions": { "url": "v3/accounts/{accountID}/positions", "response": { "positions": [ { "short": { "units": "0", "resettablePL": "-14164.3000", "unrealizedPL": "0.0000", "pl": "-14164.3000" }, "unrealizedPL": "-284.0000", "long": { "unrealizedPL": "-284.0000", "units": "10", "resettablePL": "404.5000", "tradeIDs": [ "2315" ], "averagePrice": "10678.3", "pl": "404.5000" }, "instrument": "DE30_EUR", "resettablePL": "-13759.8000", "pl": "-13759.8000" }, { "short": { "unrealizedPL": "-0.0738", "units": "-100", "resettablePL": "0.0000", "tradeIDs": [ "2323" ], "averagePrice": "1.09843", "pl": "0.0000" }, "unrealizedPL": "-0.0738", "long": { "units": "0", "resettablePL": "-44.6272", "unrealizedPL": "0.0000", "pl": "-44.6272" }, "instrument": "EUR_USD", "resettablePL": "-44.6272", "pl": "-44.6272" } ], "lastTransactionID": "2327" } }, "_v3_accounts_accountID_positiondetails": { "url": "v3/accounts/{accountID}/positions/{instrument}", "response": { "position": { "short": { "unrealizedPL": "-0.0738", "units": "-100", "resettablePL": "0.0000", "tradeIDs": [ "2323" ], "averagePrice": "1.09843", "pl": "0.0000" }, "unrealizedPL": "-0.0738", "long": { "units": "0", "resettablePL": "-44.6272", "unrealizedPL": "0.0000", "pl": "-44.6272" }, "instrument": "EUR_USD", "resettablePL": "-44.6272", "pl": "-44.6272" }, "lastTransactionID": "2327" } }, "_v3_accounts_accountID_position_close": { "url": "v3/accounts/{accountID}/positions/{instrument}/close", "body": { "longUnits": "ALL" }, "response": { "lastTransactionID": "6391", "longOrderCreateTransaction": { "accountID": "", "batchID": "6390", "id": "6390", "instrument": "EUR_USD", "longPositionCloseout": { "instrument": "EUR_USD", "units": "ALL" }, "positionFill": "REDUCE_ONLY", "reason": "POSITION_CLOSEOUT", "time": "2016-06-22T18:41:35.034041665Z", "timeInForce": "FOK", "type": "MARKET_ORDER", "units": "-251", "userID": "" }, "longOrderFillTransaction": { "accountBalance": "43650.69807", "accountID": "", "batchID": "6390", "financing": "0.00000", "id": "6391", "instrument": "EUR_USD", "orderID": "6390", "pl": "-0.03370", "price": "1.13018", "reason": "MARKET_ORDER_POSITION_CLOSEOUT", "time": "2016-06-22T18:41:35.034041665Z", "tradesClosed": [ { "financing": "0.00000", "realizedPL": "-0.00013", "tradeID": "6383", "units": "-1" }, { "financing": "0.00000", "realizedPL": "-0.03357", "tradeID": "6385", "units": "-250" } ], "type": "ORDER_FILL", "units": "-251", "userID": "" }, "relatedTransactionIDs": [ "6390", "6391" ] } } } ================================================ FILE: oandapyV20/endpoints/responses/pricing.py ================================================ """Responses. responses serve both testing purpose aswell as dynamic docstring replacement """ responses = { "_v3_accounts_accountID_pricing": { "url": "v3/accounts/{accountID}/pricing", "params": { "instruments": "EUR_USD,EUR_JPY" }, "response": { "prices": [ { "status": "tradeable", "quoteHomeConversionFactors": { "negativeUnits": "0.89160730", "positiveUnits": "0.89150397" }, "asks": [ { "price": "1.12170", "liquidity": 10000000 }, { "price": "1.12172", "liquidity": 10000000 } ], "unitsAvailable": { "default": { "short": "506246", "long": "506128" }, "reduceOnly": { "short": "0", "long": "0" }, "openOnly": { "short": "506246", "long": "506128" }, "reduceFirst": { "short": "506246", "long": "506128" } }, "closeoutBid": "1.12153", "bids": [ { "price": "1.12157", "liquidity": 10000000 }, { "price": "1.12155", "liquidity": 10000000 } ], "instrument": "EUR_USD", "time": "2016-10-05T05:28:16.729643492Z", "closeoutAsk": "1.12174" }, { "status": "tradeable", "quoteHomeConversionFactors": { "negativeUnits": "0.00867085", "positiveUnits": "0.00866957" }, "asks": [ { "price": "115.346", "liquidity": 1000000 }, { "price": "115.347", "liquidity": 2000000 }, { "price": "115.348", "liquidity": 5000000 }, { "price": "115.350", "liquidity": 10000000 } ], "unitsAvailable": { "default": { "short": "506262", "long": "506112" }, "reduceOnly": { "short": "0", "long": "0" }, "openOnly": { "short": "506262", "long": "506112" }, "reduceFirst": { "short": "506262", "long": "506112" } }, "closeoutBid": "115.325", "bids": [ { "price": "115.329", "liquidity": 1000000 }, { "price": "115.328", "liquidity": 2000000 }, { "price": "115.327", "liquidity": 5000000 }, { "price": "115.325", "liquidity": 10000000 } ], "instrument": "EUR_JPY", "time": "2016-10-05T05:28:15.621238671Z", "closeoutAsk": "115.350" } ] } }, "_v3_accounts_accountID_pricing_stream": { "url": "v3/accounts/{accountID}/pricing/stream", "params": { "instruments": "EUR_USD,EUR_JPY" }, "response": [ { "status": "tradeable", "asks": [ { "price": "114.312", "liquidity": 1000000 }, { "price": "114.313", "liquidity": 2000000 }, { "price": "114.314", "liquidity": 5000000 }, { "price": "114.316", "liquidity": 10000000 } ], "closeoutBid": "114.291", "bids": [ { "price": "114.295", "liquidity": 1000000 }, { "price": "114.294", "liquidity": 2000000 }, { "price": "114.293", "liquidity": 5000000 }, { "price": "114.291", "liquidity": 10000000 } ], "instrument": "EUR_JPY", "time": "2016-10-27T08:38:43.094548890Z", "closeoutAsk": "114.316", "type": "PRICE" }, { "type": "HEARTBEAT", "time": "2016-10-27T08:38:44.327443673Z" }, { "status": "tradeable", "asks": [ { "price": "1.09188", "liquidity": 10000000 }, { "price": "1.09190", "liquidity": 10000000 } ], "closeoutBid": "1.09173", "bids": [ { "price": "1.09177", "liquidity": 10000000 }, { "price": "1.09175", "liquidity": 10000000 } ], "instrument": "EUR_USD", "time": "2016-10-27T08:38:45.664613867Z", "closeoutAsk": "1.09192", "type": "PRICE" }, { "status": "tradeable", "asks": [ { "price": "114.315", "liquidity": 1000000 }, { "price": "114.316", "liquidity": 2000000 }, { "price": "114.317", "liquidity": 5000000 }, { "price": "114.319", "liquidity": 10000000 } ], "closeoutBid": "114.294", "bids": [ { "price": "114.298", "liquidity": 1000000 }, { "price": "114.297", "liquidity": 2000000 }, { "price": "114.296", "liquidity": 5000000 }, { "price": "114.294", "liquidity": 10000000 } ], "instrument": "EUR_JPY", "time": "2016-10-27T08:38:45.681572782Z", "closeoutAsk": "114.319", "type": "PRICE" } ] } } ================================================ FILE: oandapyV20/endpoints/responses/trades.py ================================================ """Responses. responses serve both testing purpose aswell as dynamic docstring replacement """ responses = { "_v3_accounts_accountID_trades": { "url": "v3/accounts/{accountID}/trades", "params": { "instrument": "DE30_EUR,EUR_USD" }, "response": { "trades": [ { "financing": "0.0000", "openTime": "2016-10-28T14:28:05.231759081Z", "price": "10678.3", "unrealizedPL": "25.0000", "realizedPL": "0.0000", "instrument": "DE30_EUR", "state": "OPEN", "initialUnits": "10", "currentUnits": "10", "id": "2315" }, { "financing": "0.0000", "openTime": "2016-10-28T14:27:19.011002322Z", "price": "1.09448", "unrealizedPL": "-0.0933", "realizedPL": "0.0000", "instrument": "EUR_USD", "state": "OPEN", "initialUnits": "100", "currentUnits": "100", "id": "2313" } ], "lastTransactionID": "2315" } }, "_v3_accounts_accountID_opentrades": { "url": "v3/accounts/{accountID}/openTrades", "response": { "trades": [ { "financing": "0.0000", "openTime": "2016-10-28T14:28:05.231759081Z", "price": "10678.3", "unrealizedPL": "136.0000", "realizedPL": "0.0000", "instrument": "DE30_EUR", "state": "OPEN", "initialUnits": "10", "currentUnits": "10", "id": "2315" } ], "lastTransactionID": "2317" } }, "_v3_account_accountID_trades_details": { # tradeID 2315 "url": "v3/accounts/{accountID}/trades/{tradeID}", "response": { "lastTransactionID": "2317", "trade": { "financing": "0.0000", "openTime": "2016-10-28T14:28:05.231759081Z", "price": "10678.3", "unrealizedPL": "226.0000", "realizedPL": "0.0000", "instrument": "DE30_EUR", "state": "OPEN", "initialUnits": "10", "currentUnits": "10", "id": "2315" } } }, "_v3_account_accountID_trades_close": { "url": "v3/accounts/{accountID}/trades/{tradeID}/close", "body": { "units": 100 }, "response": { "orderFillTransaction": { "orderID": "2316", "financing": "0.0000", "instrument": "EUR_USD", "price": "1.09289", "userID": 1435156, "batchID": "2316", "accountBalance": "33848.1208", "reason": "MARKET_ORDER_TRADE_CLOSE", "tradesClosed": [ { "units": "-100", "financing": "0.0000", "realizedPL": "-0.1455", "tradeID": "2313" } ], "time": "2016-10-28T15:11:58.023004583Z", "units": "-100", "type": "ORDER_FILL", "id": "2317", "pl": "-0.1455", "accountID": "101-004-1435156-001" }, "orderCreateTransaction": { "timeInForce": "FOK", "positionFill": "REDUCE_ONLY", "userID": 1435156, "batchID": "2316", "instrument": "EUR_USD", "reason": "TRADE_CLOSE", "tradeClose": { "units": "100", "tradeID": "2313" }, "time": "2016-10-28T15:11:58.023004583Z", "units": "-100", "type": "MARKET_ORDER", "id": "2316", "accountID": "101-004-1435156-001" }, "relatedTransactionIDs": [ "2316", "2317" ], "lastTransactionID": "2317" } }, "_v3_account_accountID_trades_cltext": { "url": "v3/accounts/{accountID}/trades/{tradeID}/close", "body": { "clientExtensions": { "comment": "myComment", "id": "myID2315", } }, "response": { "tradeClientExtensionsModifyTransaction": { "tradeID": "2315", "userID": 1435156, "batchID": "2319", "time": "2016-10-28T20:32:39.356516787Z", "tradeClientExtensionsModify": { "comment": "myComment", "id": "myID2315" }, "type": "TRADE_CLIENT_EXTENSIONS_MODIFY", "id": "2319", "accountID": "101-004-1435156-001" }, "relatedTransactionIDs": [ "2319" ], "lastTransactionID": "2319" } }, "_v3_account_accountID_trades_crcdo": { "url": "v3/accounts/{accountID}/trades/{tradeID}/close", "body": { "takeProfit": { "timeInForce": "GTC", "price": "1.05" }, "stopLoss": { "timeInForce": "GTC", "price": "1.10" } }, "response": { "stopLossOrderTransaction": { "timeInForce": "GTC", "triggerCondition": "TRIGGER_DEFAULT", "replacesOrderID": "2324", "tradeID": "2323", "price": "1.10000", "userID": 1435156, "batchID": "2325", "reason": "REPLACEMENT", "time": "2016-10-28T21:00:19.978476830Z", "cancellingTransactionID": "2326", "type": "STOP_LOSS_ORDER", "id": "2327", "accountID": "101-004-1435156-001" }, "takeProfitOrderTransaction": { "timeInForce": "GTC", "triggerCondition": "TRIGGER_DEFAULT", "tradeID": "2323", "price": "1.05000", "userID": 1435156, "batchID": "2325", "reason": "CLIENT_ORDER", "time": "2016-10-28T21:00:19.978476830Z", "type": "TAKE_PROFIT_ORDER", "id": "2325", "accountID": "101-004-1435156-001" }, "relatedTransactionIDs": [ "2325", "2326", "2327" ], "lastTransactionID": "2327", "stopLossOrderCancelTransaction": { "orderID": "2324", "replacedByOrderID": "2327", "userID": 1435156, "batchID": "2325", "reason": "CLIENT_REQUEST_REPLACED", "time": "2016-10-28T21:00:19.978476830Z", "type": "ORDER_CANCEL", "id": "2326", "accountID": "101-004-1435156-001" } } } } ================================================ FILE: oandapyV20/endpoints/responses/transactions.py ================================================ """Responses. responses serve both testing purpose aswell as dynamic docstring replacement """ responses = { "_v3_accounts_accountID_transactions": { "url": "v3/accounts/{accountID}/transactions", "params": { "pageSize": 200 }, "response": { "count": 2124, "from": "2016-06-24T21:03:50.914647476Z", "lastTransactionID": "2124", "pageSize": 100, "to": "2016-10-05T06:54:14.025946546Z", "pages": [ "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=1&to=100", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=101&to=200", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=201&to=300", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=301&to=400", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=401&to=500", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=501&to=600", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=601&to=700", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=701&to=800", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=801&to=900", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=901&to=1000", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=1001&to=1100", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=1101&to=1200", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=1201&to=1300", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=1301&to=1400", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=1401&to=1500", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=1501&to=1600", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=1601&to=1700", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=1701&to=1800", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=1801&to=1900", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=1901&to=2000", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=2001&to=2100", "https://api-fxpractice.oanda.com/v3/accounts/" "101-004-1435156-001/transactions/idrange?from=2101&to=2124" ] } }, "_v3_accounts_transaction_details": { "url": "v3/accounts/{accountID}/transactions/{transactionID}", "response": { "transaction": { "timeInForce": "GTC", "triggerCondition": "TRIGGER_DEFAULT", "positionFill": "DEFAULT", "stopLossOnFill": { "timeInForce": "GTC", "price": "1.22000" }, "userID": 1435156, "id": "2304", "batchID": "2304", "instrument": "EUR_USD", "reason": "CLIENT_ORDER", "time": "2016-10-24T21:48:18.593753865Z", "units": "-100", "type": "LIMIT_ORDER", "price": "1.20000", "accountID": "101-004-1435156-001" }, "lastTransactionID": "2311" } }, "_v3_accounts_transaction_idrange": { "url": "v3/accounts/{accountID}/transactions/idrange", "params": { "from": 2304, "to": 2306 }, "response": { "lastTransactionID": "2311", "transactions": [ { "timeInForce": "GTC", "triggerCondition": "TRIGGER_DEFAULT", "positionFill": "DEFAULT", "stopLossOnFill": { "timeInForce": "GTC", "price": "1.22000" }, "userID": 1435156, "id": "2304", "batchID": "2304", "instrument": "EUR_USD", "reason": "CLIENT_ORDER", "time": "2016-10-24T21:48:18.593753865Z", "units": "-100", "type": "LIMIT_ORDER", "price": "1.20000", "accountID": "101-004-1435156-001" }, { "orderID": "2304", "userID": 1435156, "batchID": "2305", "clientExtensionsModify": { "comment": "myComment", "id": "myID" }, "time": "2016-10-25T15:56:43.075594239Z", "type": "ORDER_CLIENT_EXTENSIONS_MODIFY", "id": "2305", "accountID": "101-004-1435156-001" }, { "orderID": "2304", "clientOrderID": "myID", "replacedByOrderID": "2307", "userID": 1435156, "batchID": "2306", "reason": "CLIENT_REQUEST_REPLACED", "time": "2016-10-25T19:45:38.558056359Z", "type": "ORDER_CANCEL", "id": "2306", "accountID": "101-004-1435156-001" } ] } }, "_v3_accounts_transaction_sinceid": { "url": "v3/accounts/{accountID}/transactions/sinceid", "params": { "id": 2306 }, "response": { "lastTransactionID": "2311", "transactions": [ { "timeInForce": "GTC", "triggerCondition": "TRIGGER_DEFAULT", "replacesOrderID": "2304", "positionFill": "DEFAULT", "userID": 1435156, "units": "-500000", "batchID": "2306", "instrument": "EUR_USD", "reason": "REPLACEMENT", "time": "2016-10-25T19:45:38.558056359Z", "price": "1.25000", "clientExtensions": { "comment": "myComment", "id": "myID" }, "type": "LIMIT_ORDER", "id": "2307", "accountID": "101-004-1435156-001" }, { "orderID": "2307", "clientOrderID": "myID", "userID": 1435156, "batchID": "2308", "reason": "CLIENT_REQUEST", "time": "2016-10-25T20:53:03.789670387Z", "type": "ORDER_CANCEL", "id": "2308", "accountID": "101-004-1435156-001" }, { "timeInForce": "GTC", "triggerCondition": "TRIGGER_DEFAULT", "positionFill": "DEFAULT", "stopLossOnFill": { "timeInForce": "GTC", "price": "1.22000" }, "userID": 1435156, "id": "2309", "batchID": "2309", "instrument": "EUR_USD", "reason": "CLIENT_ORDER", "time": "2016-10-25T21:07:21.065554321Z", "units": "-100", "type": "LIMIT_ORDER", "price": "1.20000", "accountID": "101-004-1435156-001" }, { "userID": 1435156, "marginRate": "0.01", "batchID": "2310", "time": "2016-10-26T13:28:00.507651360Z", "type": "CLIENT_CONFIGURE", "id": "2310", "accountID": "101-004-1435156-001" }, { "userID": 1435156, "marginRate": "0.01", "batchID": "2311", "time": "2016-10-26T13:28:13.597103123Z", "type": "CLIENT_CONFIGURE", "id": "2311", "accountID": "101-004-1435156-001" } ] } }, "_v3_accounts_transactions_stream": { "url": "v3/accounts/{accountID}/transactions/stream", "response": [ { "type": "HEARTBEAT", "lastTransactionID": "2311", "time": "2016-10-28T11:56:12.002855862Z" }, { "type": "HEARTBEAT", "lastTransactionID": "2311", "time": "2016-10-28T11:56:17.059535527Z" }, { "type": "HEARTBEAT", "lastTransactionID": "2311", "time": "2016-10-28T11:56:22.142256403Z" }, { "type": "HEARTBEAT", "lastTransactionID": "2311", "time": "2016-10-28T11:56:27.238853774Z" }, { "type": "HEARTBEAT", "lastTransactionID": "2311", "time": "2016-10-28T11:56:32.289316796Z" } ] } } ================================================ FILE: oandapyV20/endpoints/trades.py ================================================ # -*- coding: utf-8 -*- """Handle trades endpoints.""" from .apirequest import APIRequest from .decorators import dyndoc_insert, endpoint from .responses.trades import responses from abc import abstractmethod class Trades(APIRequest): """Trades - abstract baseclass to handle the trades endpoints.""" ENDPOINT = "" METHOD = "GET" @abstractmethod @dyndoc_insert(responses) def __init__(self, accountID, tradeID=None): """Instantiate a Trades APIRequest instance. Parameters ---------- accountID : string the account_id of the account. tradeID : string ID of the trade """ endpoint = self.ENDPOINT.format(accountID=accountID, tradeID=tradeID) super(Trades, self).__init__(endpoint, method=self.METHOD) @endpoint("v3/accounts/{accountID}/trades") class TradesList(Trades): """Get a list of trades for an Account.""" @dyndoc_insert(responses) def __init__(self, accountID, params=None): """Instantiate a TradesList request. Parameters ---------- accountID : string (required) id of the account to perform the request on. params : dict (optional) query params to send, check developer.oanda.com for details. Query Params example:: {_v3_accounts_accountID_trades_params} >>> import oandapyV20 >>> import oandapyV20.endpoints.trades as trades >>> client = oandapyV20.API(access_token=...) >>> params = {_v3_accounts_accountID_trades_params} >>> r = trades.TradesList(accountID=..., params=params) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_trades_resp} """ super(TradesList, self).__init__(accountID) self.params = params @endpoint("v3/accounts/{accountID}/openTrades") class OpenTrades(Trades): """Get the list of open Trades for an Account.""" @dyndoc_insert(responses) def __init__(self, accountID): """Instantiate an OpenTrades request. Parameters ---------- accountID : string (required) id of the account to perform the request on. >>> import oandapyV20 >>> import oandapyV20.endpoints.trades as trades >>> client = oandapyV20.API(access_token=...) >>> r = trades.OpenTrades(accountID=...) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_opentrades_resp} """ super(OpenTrades, self).__init__(accountID) @endpoint("v3/accounts/{accountID}/trades/{tradeID}") class TradeDetails(Trades): """Get the details of a specific Trade in an Account.""" @dyndoc_insert(responses) def __init__(self, accountID, tradeID): """Instantiate a TradeDetails request. Parameters ---------- accountID : string (required) id of the account to perform the request on. tradeID : string (required) id of the trade. >>> import oandapyV20 >>> import oandapyV20.endpoints.trades as trades >>> client = oandapyV20.API(access_token=...) >>> r = trades.TradeDetails(accountID=..., tradeID=...) >>> client.request(r) >>> print r.response Output:: {_v3_account_accountID_trades_details_resp} """ super(TradeDetails, self).__init__(accountID, tradeID) @endpoint("v3/accounts/{accountID}/trades/{tradeID}/close", "PUT") class TradeClose(Trades): """TradeClose. Close (partially or fully) a specific open Trade in an Account. """ HEADERS = {"Content-Type": "application/json"} @dyndoc_insert(responses) def __init__(self, accountID, tradeID, data=None): """Instantiate a TradeClose request. Parameters ---------- accountID : string (required) id of the account to perform the request on. tradeID : string (required) id of the trade to close. data : dict (optional) data to send, use this to close a trade partially. Check developer.oanda.com for details. Data body example:: {_v3_account_accountID_trades_close_body} >>> import oandapyV20 >>> import oandapyV20.endpoints.trades as trades >>> client = oandapyV20.API(access_token=...) >>> data = {_v3_account_accountID_trades_close_body} >>> r = trades.TradeClose(accountID=..., data=data) >>> client.request(r) >>> print r.response Output:: {_v3_account_accountID_trades_close_resp} """ super(TradeClose, self).__init__(accountID, tradeID) self.data = data @endpoint("v3/accounts/{accountID}/trades/{tradeID}/clientExtensions", "PUT") class TradeClientExtensions(Trades): """TradeClientExtensions. Update the Client Extensions for a Trade. Do not add, update or delete the Client Extensions if your account is associated with MT4. """ HEADERS = {"Content-Type": "application/json"} @dyndoc_insert(responses) def __init__(self, accountID, tradeID, data=None): """Instantiate a TradeClientExtensions request. Parameters ---------- accountID : string (required) id of the account to perform the request on. tradeID : string (required) id of the trade to update client extensions for. data : dict (required) clientextension data to send, check developer.oanda.com for details. Data body example:: {_v3_account_accountID_trades_cltext_body} >>> import oandapyV20 >>> import oandapyV20.endpoints.trades as trades >>> accountID = ... >>> tradeID = ... >>> client = oandapyV20.API(access_token=...) >>> data = {_v3_account_accountID_trades_cltext_body} >>> r = trades.TradeClientExtensions(accountID=accountID, >>> tradeID=tradeID, >>> data=data) >>> client.request(r) >>> print r.response Output:: {_v3_account_accountID_trades_cltext_resp} """ super(TradeClientExtensions, self).__init__(accountID, tradeID) self.data = data @endpoint("v3/accounts/{accountID}/trades/{tradeID}/orders", "PUT") class TradeCRCDO(Trades): """Trade Create Replace Cancel Dependent Orders.""" HEADERS = {"Content-Type": "application/json"} @dyndoc_insert(responses) def __init__(self, accountID, tradeID, data): """Instantiate a TradeClientExtensions request. Parameters ---------- accountID : string (required) id of the account to perform the request on. tradeID : string (required) id of the trade to update client extensions for. data : dict (required) clientextension data to send, check developer.oanda.com for details. Data body example:: {_v3_account_accountID_trades_crcdo_body} >>> import oandapyV20 >>> import oandapyV20.endpoints.trades as trades >>> accountID = ... >>> tradeID = ... >>> client = oandapyV20.API(access_token=...) >>> data = {_v3_account_accountID_trades_crcdo_body} >>> r = trades.TradeCRCDO(accountID=accountID, >>> tradeID=tradeID, >>> data=data) >>> client.request(r) >>> print r.response Output:: {_v3_account_accountID_trades_crcdo_resp} """ super(TradeCRCDO, self).__init__(accountID, tradeID) self.data = data ================================================ FILE: oandapyV20/endpoints/transactions.py ================================================ # -*- coding: utf-8 -*- """Handle transactions endpoints.""" from .apirequest import APIRequest from ..exceptions import StreamTerminated from .decorators import dyndoc_insert, endpoint from .responses.transactions import responses from types import GeneratorType from abc import abstractmethod class Transactions(APIRequest): """Transactions - abstract baseclass to handle transaction endpoints.""" ENDPOINT = "" METHOD = "GET" @abstractmethod @dyndoc_insert(responses) def __init__(self, accountID, transactionID=None): """Instantiate a Transactions APIRequest instance. Parameters ---------- accountID : string (required) the id of the account. transactionID : string the id of the transaction """ endpoint = self.ENDPOINT.format(accountID=accountID, transactionID=transactionID) super(Transactions, self).__init__(endpoint, method=self.METHOD) @endpoint("v3/accounts/{accountID}/transactions") class TransactionList(Transactions): """TransactionList. Get a list of Transactions pages that satisfy a time-based Transaction query. """ @dyndoc_insert(responses) def __init__(self, accountID, params=None): """Instantiate a TransactionList request. Parameters ---------- accountID : string (required) id of the account to perform the request on. params : dict (optional) query params to send, check developer.oanda.com for details. Query Params example:: {_v3_accounts_accountID_transactions_params} >>> import oandapyV20 >>> import oandapyV20.endpoints.transactions as trans >>> client = oandapyV20.API(access_token=...) >>> r = trans.TransactionList(accountID) # params optional >>> client.request(r) >>> print r.response Output:: {_v3_accounts_accountID_transactions_resp} """ super(TransactionList, self).__init__(accountID) self.params = params @endpoint("v3/accounts/{accountID}/transactions/{transactionID}") class TransactionDetails(Transactions): """Get the details of a single Account Transaction.""" @dyndoc_insert(responses) def __init__(self, accountID, transactionID): """Instantiate a TransactionDetails request. Parameters ---------- accountID : string (required) id of the account to perform the request on. transactionID : string (required) id of the transaction >>> import oandapyV20 >>> import oandapyV20.endpoints.transactions as trans >>> client = oandapyV20.API(access_token=...) >>> r = trans.TransactionDetails(accountID=..., transactionID=...) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_transaction_details_resp} """ super(TransactionDetails, self).__init__(accountID, transactionID) @endpoint("v3/accounts/{accountID}/transactions/idrange") class TransactionIDRange(Transactions): """TransactionIDRange. Get a range of Transactions for an Account based on Transaction IDs. """ @dyndoc_insert(responses) def __init__(self, accountID, params=None): """Instantiate an TransactionIDRange request. Parameters ---------- accountID : string (required) id of the account to perform the request on. params : dict (required) query params to send, check developer.oanda.com for details. Query Params example:: {_v3_accounts_transaction_idrange_params} >>> import oandapyV20 >>> import oandapyV20.endpoints.transactions as trans >>> client = oandapyV20.API(access_token=...) >>> params = {_v3_accounts_transaction_idrange_params} >>> r = trans.TransactionIDRange(accountID=..., params=params) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_transaction_idrange_resp} """ super(TransactionIDRange, self).__init__(accountID) self.params = params @endpoint("v3/accounts/{accountID}/transactions/sinceid") class TransactionsSinceID(Transactions): """TransactionsSinceID. Get a range of Transactions for an Account starting at (but not including) a provided Transaction ID. """ @dyndoc_insert(responses) def __init__(self, accountID, params=None): """Instantiate an TransactionsSince request. Parameters ---------- accountID : string (required) id of the account to perform the request on. params : dict (required) query params to send, check developer.oanda.com for details. Query Params example:: {_v3_accounts_transaction_sinceid_params} >>> import oandapyV20 >>> import oandapyV20.endpoints.transactions as trans >>> client = oandapyV20.API(access_token=...) >>> params = {_v3_accounts_transaction_sinceid_params} >>> r = trans.TransactionsSinceID(accountID=..., params=params) >>> client.request(r) >>> print r.response Output:: {_v3_accounts_transaction_sinceid_resp} """ super(TransactionsSinceID, self).__init__(accountID) self.params = params @endpoint("v3/accounts/{accountID}/transactions/stream") class TransactionsStream(Transactions): """TransactionsStream. Get a stream of Transactions for an Account starting from when the request is made. """ STREAM = True @dyndoc_insert(responses) def __init__(self, accountID, params=None): """Instantiate an TransactionsStream request. Performing this request will result in a generator yielding transactions. Parameters ---------- accountID : string (required) id of the account to perform the request on. >>> import oandapyV20 >>> import oandapyV20.endpoints.transactions as trans >>> client = oandapyV20.API(access_token=...) >>> r = trans.TransactionsStream(accountID=...) >>> rv = client.request(r) >>> maxrecs = 5 >>> try: >>> for T in r.response: # or rv ... >>> print json.dumps(R, indent=4), "," >>> maxrecs -= 1 >>> if maxrecs == 0: >>> r.terminate("Got them all") >>> except StreamTerminated as e: >>> print("Finished: {{msg}}".format(msg=e)) Output:: {_v3_accounts_transactions_stream_ciresp} Finished: Got them all """ super(TransactionsStream, self).__init__(accountID) self.params = params def terminate(self, message=""): """terminate the stream. Calling this method will stop the generator yielding transaction records. A message can be passed optionally. """ if not isinstance(self.response, GeneratorType): raise ValueError("request does not contain a stream response") self.response.throw(StreamTerminated(message)) ================================================ FILE: oandapyV20/exceptions.py ================================================ """Exceptions.""" class StreamTerminated(Exception): """StreamTerminated.""" class V20Error(Exception): """Generic error class. In case of HTTP response codes >= 400 this class can be used to raise an exception representing that error. """ def __init__(self, code, msg): """Instantiate a V20Error. Parameters ---------- code : int the HTTP-code of the response msg : str the message returned with the response """ self.code = code self.msg = msg super(V20Error, self).__init__(msg) ================================================ FILE: oandapyV20/oandapyV20.py ================================================ # -*- coding: utf-8 -*- """OANDA API wrapper for OANDA's REST-V20 API.""" import json import requests import logging from .exceptions import V20Error ITER_LINES_CHUNKSIZE = 60 TRADING_ENVIRONMENTS = { "practice": { "stream": 'https://stream-fxpractice.oanda.com', "api": 'https://api-fxpractice.oanda.com' }, "live": { "stream": 'https://stream-fxtrade.oanda.com', "api": 'https://api-fxtrade.oanda.com' } } DEFAULT_HEADERS = { "Accept-Encoding": "gzip, deflate" } logger = logging.getLogger(__name__) class API(object): r"""API - class to handle APIRequests objects to access API endpoints. Examples -------- :: # get a list of trades from oandapyV20 import API import oandapyV20.endpoints.trades as trades api = API(access_token="xxx") accountID = "101-305-3091856-001" r = trades.TradesList(accountID) # show the endpoint as it is constructed for this call print("REQUEST:{}".format(r)) rv = api.request(r) print("RESPONSE:\n{}".format(json.dumps(rv, indent=2))) Output:: REQUEST:v3/accounts/101-305-3091856-001/trades RESPONSE: "trades": [ { "financing": "0.0000", "openTime": "2016-07-21T15:47:05.170212014Z", "price": "10133.9", "unrealizedPL": "8.0000", "realizedPL": "0.0000", "instrument": "DE30_EUR", "state": "OPEN", "initialUnits": "-10", "currentUnits": "-10", "id": "1032" }, { "financing": "0.0000", "openTime": "2016-07-21T15:47:04.963590941Z", "price": "10134.4", "unrealizedPL": "13.0000", "realizedPL": "0.0000", "instrument": "DE30_EUR", "state": "OPEN", "initialUnits": "-10", "currentUnits": "-10", "id": "1030" } ], "lastTransactionID": "1040" } :: # reduce a trade by it's id from oandapyV20 import API import oandapyV20.endpoints.trades as trades api = API(access_token="...") accountID = "101-305-3091856-001" tradeID = "1030" cfg = {"units": 5} r = trades.TradeClose(accountID, tradeID=tradeID, data=cfg) # show the endpoint as it is constructed for this call print("REQUEST:{}".format(r)) rv = api.request(r) print("RESPONSE\n{}".format(json.dumps(rv, indent=2))) or by using it in a *with context*: :: with API(access_token="...") as api: accountID = "101-305-3091856-001" tradeID = "1030" cfg = {"units": 5} r = trades.TradeClose(accountID, tradeID=tradeID, data=cfg) # show the endpoint as it is constructed for this call print("REQUEST:{}".format(r)) rv = api.request(r) print("RESPONSE\n{}".format(json.dumps(rv, indent=2))) in this case the API-client instance *api* will close connections explicitely. Output:: REQUEST:v3/accounts/101-305-3091856-001/trades/1030/close RESPONSE: { "orderFillTransaction": { "orderID": "1041", "financing": "-0.1519", "instrument": "DE30_EUR", "userID": 1435156, "price": "10131.6", "tradeReduced": { "units": "5", "financing": "-0.1519", "realizedPL": "14.0000", "tradeID": "1030" }, "batchID": "1041", "accountBalance": "44876.2548", "reason": "MARKET_ORDER_TRADE_CLOSE", "time": "2016-07-21T17:32:51.361464739Z", "units": "5", "type": "ORDER_FILL", "id": "1042", "pl": "14.0000", "accountID": "101-305-3091856-001" }, "orderCreateTransaction": { "timeInForce": "FOK", "positionFill": "REDUCE_ONLY", "userID": 1435156, "batchID": "1041", "instrument": "DE30_EUR", "reason": "TRADE_CLOSE", "tradeClose": { "units": "5", "tradeID": "1030" }, "time": "2016-07-21T17:32:51.361464739Z", "units": "5", "type": "MARKET_ORDER", "id": "1041", "accountID": "101-305-3091856-001" }, "relatedTransactionIDs": [ "1041", "1042" ], "lastTransactionID": "1042" } """ def __init__(self, access_token, environment="practice", headers=None, request_params=None): """Instantiate an instance of OandaPy's API wrapper. Parameters ---------- access_token : string Provide a valid access token. environment : string Provide the environment for OANDA's REST api. Valid values: 'practice' or 'live'. Default: 'practice'. headers : dict (optional) Provide request headers to be set for a request. .. note:: There is no need to set the 'Content-Type: application/json' for the endpoints that require this header. The API-request classes covering those endpoints will take care of the header. request_params : (optional) parameters to be passed to the request. This can be used to apply for instance a timeout value: request_params={"timeout": 0.1} See specs of the requests module for full details of possible parameters. .. warning:: parameters belonging to a request need to be set on the requestinstance and are NOT passed via the client. """ logger.info("setting up API-client for environment %s", environment) try: TRADING_ENVIRONMENTS[environment] except KeyError as err: # noqa F841 logger.error("unkown environment %s", environment) raise KeyError("Unknown environment: {}".format(environment)) else: self.environment = environment self.access_token = access_token self.client = requests.Session() self.client.stream = False self._request_params = request_params if request_params else {} # personal token authentication if self.access_token: self.client.headers['Authorization'] = 'Bearer '+self.access_token self.client.headers.update(DEFAULT_HEADERS) if headers: self.client.headers.update(headers) logger.info("applying headers %s", ",".join(headers.keys())) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() def close(self): """close. explicit close of the session. """ self.client.close() @property def request_params(self): """request_params property.""" return self._request_params def __request(self, method, url, request_args, headers=None, stream=False): """__request. make the actual request. This method is called by the request method in case of 'regular' API-calls. Or indirectly by the__stream_request method if it concerns a 'streaming' call. """ func = getattr(self.client, method) headers = headers if headers else {} response = None try: logger.info("performing request %s", url) response = func(url, stream=stream, headers=headers, **request_args) except requests.RequestException as err: logger.error("request %s failed [%s]", url, err) raise err # Handle error responses if response.status_code >= 400: logger.error("request %s failed [%d,%s]", url, response.status_code, response.content.decode('utf-8')) raise V20Error(response.status_code, response.content.decode('utf-8')) return response def __stream_request(self, method, url, request_args, headers=None): """__stream_request. make a 'stream' request. This method is called by the 'request' method after it has determined which call applies: regular or streaming. """ headers = headers if headers else {} response = self.__request(method, url, request_args, headers=headers, stream=True) lines = response.iter_lines(ITER_LINES_CHUNKSIZE) for line in lines: if line: data = json.loads(line.decode("utf-8")) yield data def request(self, endpoint): """Perform a request for the APIRequest instance 'endpoint'. Parameters ---------- endpoint : APIRequest The endpoint parameter contains an instance of an APIRequest containing the endpoint, method and optionally other parameters or body data. Raises ------ V20Error in case of HTTP response code >= 400 """ method = endpoint.method method = method.lower() params = None try: params = getattr(endpoint, "params") except AttributeError: # request does not have params params = {} headers = {} if hasattr(endpoint, "HEADERS"): headers = getattr(endpoint, "HEADERS") request_args = {} if method == 'get': request_args['params'] = params elif hasattr(endpoint, "data") and endpoint.data: request_args['json'] = endpoint.data # if any parameter for request then merge them request_args.update(self._request_params) # which API to access ? if not (hasattr(endpoint, "STREAM") and getattr(endpoint, "STREAM") is True): url = "{}/{}".format( TRADING_ENVIRONMENTS[self.environment]["api"], endpoint) response = self.__request(method, url, request_args, headers=headers) content = response.content.decode('utf-8') content = json.loads(content) # update endpoint endpoint.response = content endpoint.status_code = response.status_code return content else: url = "{}/{}".format( TRADING_ENVIRONMENTS[self.environment]["stream"], endpoint) endpoint.response = self.__stream_request(method, url, request_args, headers=headers) return endpoint.response ================================================ FILE: oandapyV20/types/__init__.py ================================================ from .types import ( AccountID, DateTime, OrderID, TradeID, AccountUnits, PriceValue, Units, ClientID, ClientTag, ClientComment, OrderIdentifier, OrderSpecifier ) __all__ = ( 'AccountID', 'DateTime', 'OrderID', 'TradeID', 'AccountUnits', 'PriceValue', 'Units', 'ClientID', 'ClientTag', 'ClientComment', 'OrderIdentifier', 'OrderSpecifier' ) ================================================ FILE: oandapyV20/types/types.py ================================================ # -*- coding: utf-8 -*- """types.""" import six import re from abc import ABCMeta import datetime as natdatetime # native datetime @six.add_metaclass(ABCMeta) class OAType(object): """baseclass for OANDA types.""" @property def value(self): """value property.""" return self._v class AccountID(OAType): """representation of an AccountID, string value of an Account Identifier. Parameters ---------- accountID : string (required) the accountID of a v20 account Example ------- >>> print AccountID("001-011-5838423-001").value A ValueError exception is raised in case of an incorrect value. """ def __init__(self, accountID): l = re.match(r"(?P\d+)-(?P\d+)" "-(?P\d+)-(?P\d+)", accountID) if not l: msg = "AccountID {} not a valid V20 account".format(accountID) raise ValueError(msg) self._v = l.groupdict() class OrderID(OAType): """representation of an orderID, string value of an integer. Parameters ---------- orderID : integer or string (required) the orderID as a positive integer or as a string Example ------- >>> print OrderID(1234).value A ValueError exception is raised in case of a negative integer value """ def __init__(self, orderID): if int(orderID) < 0: raise ValueError("OrderID must be a positive integer value") self._v = "{:d}".format(int(orderID)) class DateTime(OAType): """representation of a DateTime as a RFC 3339 string. Parameters ---------- dateTime : string, datetime instance, dict (required) the dateTime parameter must be: - a valid RFC3339 string representing a date-time, or - a dict holding the relevant datetime parts, or - a datetime.datetime instance The value property is always RFC3339 datetime string Fractional seconds are in microseconds. This compatible with datetime.datetime. Example ------- >>> print DateTime("2014-07-02T04:00:00.000000Z").value >>> print DateTime({"year": 2014, "month": 12, "day": 2, ... "hour": 13, "minute": 48, "second": 12}).value >>> from datetime import datetime >>> print DateTime(datetime.now()).value A ValueError exception is raised in case of an invalid value """ def __init__(self, dateTime): def formatDT(dtd): _date = natdatetime.datetime( int(dtd.get("year")), int(dtd.get("month")), int(dtd.get("day")), int(dtd.get("hour")), int(dtd.get("minute")), int(dtd.get("second"))) dt = natdatetime.datetime.strftime(_date, "%Y-%m-%dT%H:%M:%S") if "subsecond" in dtd and dtd.get("subsecond") is not None: dt = "{}.{:>06d}".format(dt, int(dtd.get("subsecond"))) return dt+"Z" if isinstance(dateTime, str): l = re.match(r"(?P\d+)-(?P\d+)-(?P\d+)" "T(?P\d+):(?P\d+):(?P\d+)" "(?:.(?P\d{1,6})|)" "Z", dateTime) if not l: msg = "Invalid RFC 3339 string: {}".format(dateTime) raise ValueError(msg) # print l.groupdict() self._v = formatDT(l.groupdict()) elif isinstance(dateTime, dict): self._v = formatDT(dateTime) elif isinstance(dateTime, natdatetime.datetime): self._v = formatDT({"year": dateTime.year, "month": dateTime.month, "day": dateTime.day, "hour": dateTime.hour, "minute": dateTime.minute, "second": dateTime.second, "subsecond": dateTime.microsecond}) class TradeID(OAType): """representation of a tradeID, string value of an integer. Parameters ---------- tradeID : integer or string (required) the tradeID as a positive integer or as a string Example ------- >>> print TradeID(1234).value A ValueError exception is raised in case of a negative integer value """ def __init__(self, tradeID): if int(tradeID) < 0: raise ValueError("TradeID must be a positive integer value") self._v = "{:d}".format(int(tradeID)) class AccountUnits(OAType): """representation AccountUnits, string value of a float.""" def __init__(self, units): self._v = "{:.5f}".format(float(units)) class PriceValue(OAType): """representation PriceValue, string value of a float.""" def __init__(self, priceValue): self._v = "{:.5f}".format(float(priceValue)) class Units(OAType): """representation Units, string value of an integer or float up to 1 decimal.""" def __init__(self, units): _units = str(units) # validate the number if re.fullmatch(r'[+-]{0,1}(\d+)', _units): self._v = "{:d}".format(int(_units)) elif re.fullmatch(r'[+-]{0,1}\d+(\.\d{0,1})', _units): self._v = "{:.1f}".format(float(_units)) elif re.fullmatch(r'[+-]{0,1}\d+(\.\d{0,2})', _units): self._v = "{:.2f}".format(float(_units)) else: raise ValueError("incorrect units: {}".format(_units)) class ClientID(OAType): """representation of ClientID, a string value of max 128 chars.""" def __init__(self, clientID): length = len(clientID) if not length or length > 128: raise ValueError("ClientID: length {}".format(length)) self._v = clientID class ClientTag(OAType): """representation of ClientTag, a string value of max 128 chars.""" def __init__(self, clientTag): length = len(clientTag) if not length or length > 128: raise ValueError("ClientTag: length {}".format(length)) self._v = clientTag class ClientComment(OAType): """representation of ClientComment, a string value of max 128 chars.""" def __init__(self, clientComment): length = len(clientComment) if not length or length > 128: raise ValueError("ClientComment: length {}".format(length)) self._v = clientComment class OrderIdentifier(OAType): """representation of the OrderIdentifier object.""" def __init__(self, orderID, clientID): self._v = { "orderID": OrderID(orderID).value, "clientOrderID": ClientID(clientID).value } class OrderSpecifier(OAType): """representation of the OrderSpecifier.""" def __init__(self, specifier): if str(specifier).startswith('@'): self._v = ClientID(specifier.lstrip('@')).value else: self._v = OrderID(specifier).value ================================================ FILE: readthedocs.yml ================================================ requirements_file: requirements.txt python: setup_py_install: true ================================================ FILE: requirements.txt ================================================ requests>=2.10 six>=1.10 ================================================ FILE: setup.py ================================================ from setuptools import setup, find_packages import os import sys import re import shutil def get_version(package): """ Return package version as listed in `__version__` in `init.py`. """ init_py = open(os.path.join(package, '__init__.py')).read() return re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1) def read_all(f): with open(f) as I: return I.read() requirements = map(str.strip, open("requirements.txt").readlines()) version = get_version('oandapyV20') if sys.argv[-1] == 'publish': print("You probably want to also tag the version now:") print(" git tag -a %s -m 'version %s'" % (version, version)) print(" git push --tags") shutil.rmtree('dist') shutil.rmtree('build') shutil.rmtree('oandapyV20.egg-info') sys.exit() setup(name='oandapyV20', version=version, description="Python wrapper for the OANDA REST-V20 API", long_description=read_all("README.rst"), classifiers=[ 'Programming Language :: Python', 'License :: OSI Approved :: MIT License', 'Intended Audience :: Developers', 'Intended Audience :: Financial and Insurance Industry', 'Operating System :: OS Independent', 'Development Status :: 3 - Alpha', 'Topic :: Software Development :: Libraries :: Python Modules', 'Programming Language :: Python', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', ], # Get from http://pypi.python.org/pypi?%3Aaction=list_classifiers keywords='OANDA FOREX/CFD wrapper REST-V20 API', author='F. Brekeveld', author_email='f.brekeveld@gmail.com', url='http://github.com/hootnot/oanda-api-v20', license='MIT', packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), test_suite="tests", include_package_data=True, zip_safe=False, install_requires=requirements, entry_points=""" # -*- Entry points: -*- """, ) ================================================ FILE: tests/__init__.py ================================================ ================================================ FILE: tests/account.txt ================================================ 101-004-1435156-001|EUR ================================================ FILE: tests/test_accounts.py ================================================ import sys import unittest import json from . import unittestsetup from .unittestsetup import environment as environment from .unittestsetup import fetchTestData import requests_mock try: from nose_parameterized import parameterized except: print("*** Please install 'nose_parameterized' to run these tests ***") exit(0) from oandapyV20 import API from oandapyV20.exceptions import V20Error import oandapyV20.endpoints.accounts as accounts from oandapyV20.endpoints.accounts import responses access_token = None accountID = None account_cur = None api = None class TestAccounts(unittest.TestCase): """Tests regarding the accounts endpoints.""" def setUp(self): """setup for all tests.""" global access_token global accountID global account_cur global api # self.maxDiff = None try: accountID, account_cur, access_token = unittestsetup.auth() setattr(sys.modules["oandapyV20.oandapyV20"], "TRADING_ENVIRONMENTS", {"practice": { "stream": "https://test.com", "api": "https://test.com", }}) api = API(environment=environment, access_token=access_token, headers={"Content-Type": "application/json"}) api.api_url = 'https://test.com' except Exception as e: print("%s" % e) exit(0) @requests_mock.Mocker() def test__account_list(self, mock_req): """get the list of accounts.""" tid = "_v3_accounts" resp, data = fetchTestData(responses, tid) r = accounts.AccountList() mock_req.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__account_details(self, mock_req): """get the details of specified account.""" tid = "_v3_account_by_accountID" resp, data = fetchTestData(responses, tid) r = accounts.AccountDetails(accountID=accountID) mock_req.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) @parameterized.expand([ (None, 200), ("X", 404, "Account does not exist"), ]) @requests_mock.Mocker(kw='mock') def test__get_account_summary(self, accID, status_code, fail=None, **kwargs): """get the summary of specified account.""" tid = "_v3_account_by_accountID_summary" resp, data = fetchTestData(responses, tid) if not accID: # hack to use the global accountID accID = accountID r = accounts.AccountSummary(accountID=accID) text = fail if not fail: text = json.dumps(resp) kwargs['mock'].register_uri('GET', "{}/{}".format(api.api_url, r), text=text, status_code=status_code) if fail: # The test should raise an exception with code == fail oErr = None with self.assertRaises(V20Error) as oErr: result = api.request(r) self.assertTrue(fail in "{}".format(oErr.exception)) else: result = api.request(r) self.assertTrue(result["account"]["id"] == accountID and result["account"]["currency"] == account_cur) @requests_mock.Mocker() def test__account_instruments(self, mock_req): """get the instruments of specified account.""" tid = "_v3_account_by_accountID_instruments" resp, data, params = fetchTestData(responses, tid) r = accounts.AccountInstruments(accountID=accountID, params=params) mock_req.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__account_configuration(self, mock_req): """set configurable parts of account.""" tid = "_v3_accounts_accountID_account_config" resp, data = fetchTestData(responses, tid) r = accounts.AccountConfiguration(accountID=accountID, data=data) mock_req.register_uri('PATCH', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__account_changes(self, mock_get): """get account state since ID of transaction.""" tid = "_v3_accounts_accountID_account_changes" resp, data, params = fetchTestData(responses, tid) r = accounts.AccountChanges(accountID=accountID, params=params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_contrib_factories.py ================================================ import math import unittest try: from nose_parameterized import parameterized except ImportError: print("*** Please install 'nose_parameterized' to run these tests ***") exit(0) import oandapyV20.contrib.factories as req class TestContribFactories(unittest.TestCase): """Tests regarding contrib factories. The reference is created using the second dict parameter. The first dict parameter is merge with this, but only for the keys that do NOT exist. That allows us to override parameters. The result should reflect the data constructed by the class """ @parameterized.expand([ (req.InstrumentsCandlesFactory, "DE30_EUR", {}, {"len": 1}, ), (req.InstrumentsCandlesFactory, "DE30_EUR", {"from": "2017-01-01T00:00:00Z", "to": "2017-01-02T00:00:00Z", "granularity": "M1"}, {"len": int(math.ceil(24*60.0 / 500))}, ), (req.InstrumentsCandlesFactory, "DE30_EUR", {"from": "2017-01-01T00:00:00Z", "granularity": "M1"}, {}, ), (req.InstrumentsCandlesFactory, "DE30_EUR", {"from": "2017-01-01T00:00:00Z", "to": "2022-06-30T00:00:00Z", "granularity": "M1"}, {}, ), (req.InstrumentsCandlesFactory, "DE30_EUR", {"to": "2017-06-30T00:00:00Z", "granularity": "M1"}, {}, ValueError, ), (req.InstrumentsCandlesFactory, "EUR_GBP", {"from": "2017-04-01T00:00:00Z", "to": "2017-09-23T00:00:00Z", "granularity": "M30"}, {"len": 17}, ), (req.InstrumentsCandlesFactory, "DE30_EUR", {"from": "2017-01-01T00:00:00Z", "to": "2017-06-30T00:00:00Z", "granularity": "H4"}, {"len": 3}, ), (req.InstrumentsCandlesFactory, "DE30_EUR", {"from": "2017-01-01T00:00:00Z", "to": "2017-06-30T00:00:00Z", "count": 5000, # same as previous, but increase batchsize "granularity": "H4"}, {"len": 1}, ), ]) def test__candlehistory(self, factory, instrument, inpar, refpar, exc=None): """candlehistoryfactory.""" if not exc: # run the factory i = 0 for r in factory(instrument, params=inpar): if i == 0 and inpar: self.assertTrue(r.params['from'] == inpar['from']) # the calculated 'to' should be there self.assertTrue('to' in r.params) if 'len' in refpar and i == refpar['len']: self.assertTrue('to' not in r.params) i += 1 # else: # with self.assertRaises(exc) as err: # r = factory(instrument, params=inpar) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_contrib_generic.py ================================================ import unittest try: from nose_parameterized import parameterized except ImportError: print("*** Please install 'nose_parameterized' to run these tests ***") exit(0) import oandapyV20.contrib.generic as gen class TestContribGeneric(unittest.TestCase): """Tests regarding contrib generic.""" def test__secs2time(self): d = gen.secs2time(1497499200) self.assertTrue(d.strftime("%Y%m%d-%H:%M:%S") == '20170615-04:00:00') @parameterized.expand([ (gen.granularity_to_time, "M1", 1*60), (gen.granularity_to_time, "M2", 2*60), (gen.granularity_to_time, "M5", 5*60), (gen.granularity_to_time, "M15", 15*60), (gen.granularity_to_time, "H1", 3600), (gen.granularity_to_time, "H4", 4*3600), (gen.granularity_to_time, "D", 86400), (gen.granularity_to_time, "D1", 86400), (gen.granularity_to_time, "W", 604800), (gen.granularity_to_time, "W1", 604800), (gen.granularity_to_time, "K1", 86400, ValueError), ]) def test__granularity_to_time(self, meth, granularity, refval, exc=None): """granularity_to_time.""" if not exc: # run the factory r = meth(granularity) self.assertTrue(r == refval) else: with self.assertRaises(exc): r = meth(granularity) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_contrib_orders.py ================================================ import unittest try: from nose_parameterized import parameterized except: print("*** Please install 'nose_parameterized' to run these tests ***") exit(0) import oandapyV20.contrib.requests as req import oandapyV20.definitions.orders as OD import oandapyV20.types as types class TestContribRequests(unittest.TestCase): """Tests regarding contrib requests. The reference is created using the second dict parameter. The first dict parameter is merge with this, but only for the keys that do NOT exist. That allows us to override parameters. The result should reflect the data constructed by the class """ @parameterized.expand([ # MO (req.MarketOrderRequest, {"instrument": "EUR_USD", "units": 10000}, # integer! {'timeInForce': 'FOK', # the default 'units': '10000', # override, should be the str equiv. 'positionFill': 'DEFAULT', 'type': 'MARKET'} ), (req.MarketOrderRequest, {"instrument": "EUR_USD", "priceBound": 12345, # integer "units": "10000"}, {'timeInForce': 'FOK', "priceBound": types.PriceValue(12345).value, 'positionFill': 'DEFAULT', 'type': 'MARKET'} ), (req.MarketOrderRequest, {"instrument": "EUR_USD", 'timeInForce': 'GFD', # should result in a ValueError "units": "10000"}, {'positionFill': 'DEFAULT', 'type': 'MARKET'}, ValueError ), (req.MarketOrderRequest, {"instrument": "EUR_USD", 'timeInForce': 'FOK', 'positionFill': 'WRONG', "units": "10000"}, {'positionFill': 'WRONG', 'type': 'MARKET'}, ValueError ), # LO (req.LimitOrderRequest, {"instrument": "EUR_USD", "units": 10000, # integer "price": 1.08}, {'timeInForce': 'GTC', "price": '1.08000', 'positionFill': 'DEFAULT', 'type': 'LIMIT' } ), (req.LimitOrderRequest, {"instrument": "EUR_USD", "units": "10000", # string "price": "1.08"}, {'timeInForce': 'GTC', "price": '1.08000', 'positionFill': 'DEFAULT', 'type': 'LIMIT' } ), # ... GTD, should raise a ValueError with missing date (req.LimitOrderRequest, {"instrument": "EUR_USD", 'timeInForce': 'GTD', "units": 10000, "price": 1.08}, {'timeInForce': 'GTD', "price": '1.08000', 'positionFill': 'DEFAULT', 'type': 'LIMIT'}, ValueError ), # MIT (req.MITOrderRequest, {"instrument": "EUR_USD", "units": 10000, "price": 1.08}, {'timeInForce': 'GTC', "price": '1.08000', 'positionFill': 'DEFAULT', 'type': 'MARKET_IF_TOUCHED'} ), # ... GTD, should raise a ValueError with missing date (req.MITOrderRequest, {"instrument": "EUR_USD", 'timeInForce': 'GTD', "units": 10000, "price": 1.08}, {'timeInForce': 'GTD', "price": '1.08000', 'positionFill': 'DEFAULT', 'type': 'MARKET_IF_TOUCHED'}, ValueError ), # ... FOK, should raise a ValueError (not allowed) (req.MITOrderRequest, {"instrument": "EUR_USD", 'timeInForce': 'FOK', "units": 10000, "price": 1.08}, {'timeInForce': 'FOK', "price": '1.08000', 'positionFill': 'DEFAULT', 'type': 'MARKET_IF_TOUCHED'}, ValueError ), # TPO (req.TakeProfitOrderRequest, {"tradeID": "1234", "price": 1.22}, {'timeInForce': 'GTC', "price": '1.22000', 'type': 'TAKE_PROFIT'} ), # ... GTD, should raise a ValueError with missing date (req.TakeProfitOrderRequest, {"tradeID": "1234", "timeInForce": "GTD", "price": 1.22}, {'timeInForce': 'GTD', "price": '1.22000', 'type': 'TAKE_PROFIT'}, ValueError ), # ... FOK, should raise a ValueError (not allowed) (req.TakeProfitOrderRequest, {"tradeID": "1234", "timeInForce": "FOK", "price": 1.22}, {'timeInForce': 'FOK', "price": '1.22000', 'type': 'TAKE_PROFIT'}, ValueError ), # SLO (req.StopLossOrderRequest, {"tradeID": "1234", "price": 1.07}, {'timeInForce': 'GTC', 'type': 'STOP_LOSS', 'price': '1.07000'} ), # ... GTD, should raise a ValueError with missing date (req.StopLossOrderRequest, {"tradeID": "1234", "timeInForce": "GTD", "price": 1.07}, {'timeInForce': 'GTD', 'type': 'STOP_LOSS'}, ValueError ), # ... FOK, should raise a ValueError (req.StopLossOrderRequest, {"tradeID": "1234", "timeInForce": "FOK", "price": 1.07}, {'timeInForce': 'FOK', 'type': 'STOP_LOSS'}, ValueError ), # TSLO (req.TrailingStopLossOrderRequest, {"tradeID": "1234", "distance": 20.5}, {'timeInForce': 'GTC', "distance": '20.50000', 'type': 'TRAILING_STOP_LOSS'} ), # ... GTD, should raise a ValueError with missing date (req.TrailingStopLossOrderRequest, {"tradeID": "1234", "timeInForce": "GTD", "distance": 20.5}, {'timeInForce': 'GTD', 'type': 'TRAILING_STOP_LOSS'}, ValueError ), # ... FOK, should raise a ValueError (not allowed) (req.TrailingStopLossOrderRequest, {"tradeID": "1234", "timeInForce": "FOK", "distance": 20.5}, {'timeInForce': 'FOK', "distance": "20.50000", 'type': 'TRAILING_STOP_LOSS'}, ValueError ), # SO (req.StopOrderRequest, {"instrument": "EUR_USD", "units": 10000, "price": 1.07}, {'timeInForce': 'GTC', 'positionFill': 'DEFAULT', "price": "1.07000", 'type': 'STOP'} ), # ... GTD, should raise a ValueError with missing date (req.StopOrderRequest, {"instrument": "EUR_USD", "units": 10000, "timeInForce": "GTD", "price": 1.07}, {'timeInForce': 'GTD', 'positionFill': 'DEFAULT', "price": "1.07000", 'type': 'STOP'}, ValueError ), ]) def test__orders(self, cls, inpar, refpar, exc=None): reference = dict({"order": refpar}) # update in reference all keys if they do not exists for k in inpar.keys(): if k not in reference['order']: reference['order'][k] = str(inpar[k]) if not exc: r = cls(**inpar) self.assertTrue(r.data == reference) else: with self.assertRaises(exc): r = cls(**inpar) @parameterized.expand([ # regular (req.PositionCloseRequest, {"longUnits": 10000, "shortUnits": 2000}, {"longUnits": "10000", "shortUnits": "2000"}, ), # nothing (req.PositionCloseRequest, {}, {}, ValueError ), # client ext (req.PositionCloseRequest, {"longUnits": 10000, "shortUnits": 2000, "longClientExtensions": {"key": "val"} }, {"longUnits": "10000", "shortUnits": "2000", "longClientExtensions": {"key": "val"} }, ), # client ext (req.PositionCloseRequest, {"longUnits": 10000, "shortUnits": 2000, "shortClientExtensions": {"key": "val"} }, {"longUnits": "10000", "shortUnits": "2000", "shortClientExtensions": {"key": "val"} }, ), # regular (req.TradeCloseRequest, {"units": 10000}, {"units": "10000"} ), # default (req.TradeCloseRequest, {}, {"units": "ALL"} ), # TakeProfitDetails (req.TakeProfitDetails, {"price": 1.10}, {'timeInForce': 'GTC', 'price': '1.10000'} ), # .. raises ValueError because GTD required gtdTime (req.TakeProfitDetails, {"price": 1.10, "timeInForce": OD.TimeInForce.GTD}, {'timeInForce': 'GTD', 'price': '1.10000'}, ValueError ), # .. raises ValueError because timeInForce must be GTC/GTD/GFD (req.TakeProfitDetails, {"price": 1.10, "timeInForce": OD.TimeInForce.FOK}, {'timeInForce': 'FOK', 'price': '1.10000'}, ValueError ), # StopLossDetails (req.StopLossDetails, {"price": 1.10}, {'timeInForce': 'GTC', 'price': '1.10000'} ), # .. raises ValueError because GTD required gtdTime (req.StopLossDetails, {"price": 1.10, "timeInForce": OD.TimeInForce.GTD}, {'timeInForce': 'GTD', 'price': '1.10000'}, ValueError ), # .. raises ValueError because timeInForce must be GTC/GTD/GFD (req.StopLossDetails, {"price": 1.10, "timeInForce": OD.TimeInForce.FOK}, {'timeInForce': 'FOK', 'price': '1.10000'}, ValueError ), # TrailingStopLossDetails (req.TrailingStopLossDetails, {"distance": 25}, {'timeInForce': 'GTC', 'distance': '25.00000'} ), # .. raises ValueError because GTD required gtdTime (req.TrailingStopLossDetails, {"distance": 100, "timeInForce": OD.TimeInForce.GTD}, {'timeInForce': 'GTD', 'distance': '100.00000'}, ValueError ), # .. raises ValueError because timeInForce must be GTC/GTD/GFD (req.TrailingStopLossDetails, {"distance": 100, "timeInForce": OD.TimeInForce.FOK}, {'timeInForce': 'FOK', 'distance': '100.00000'}, ValueError ), # ClientExtensions (req.ClientExtensions, {"clientID": "myID"}, {"id": "myID"}, ), (req.ClientExtensions, {"clientTag": "myTag"}, {"tag": "myTag"}, ), (req.ClientExtensions, {"clientComment": "myComment"}, {"comment": "myComment"}, ), # .. raises ValueError because no values were set (req.ClientExtensions, {}, {}, ValueError ), ]) def test__anonymous_body(self, cls, inpar, refpar, exc=None): if not exc: r = cls(**inpar) if inpar else cls() self.assertTrue(r.data == refpar) else: with self.assertRaises(exc): r = cls(**inpar) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_decorators.py ================================================ import unittest from oandapyV20.endpoints.decorators import extendargs, abstractclass from abc import ABCMeta, abstractmethod import six class TestDecorators(unittest.TestCase): """Tests decorators .""" def test__extendargs(self): class Something(object): def __init__(self, x=10): self.x = x @extendargs("y") class SomethingExtra(Something): def add(self): return self.x + self.y tst = SomethingExtra(x=10, y=20) self.assertEqual(tst.add(), 30) def test__abstractclass(self): @six.add_metaclass(ABCMeta) class Something(object): @abstractmethod def __init__(self, x=10): self.x = x @abstractclass class SomethingElse(Something): # derived classes from this class make use # of this class __init__ # since this __init__ overrides the parent's # @abstractmethod instances could be created, # by making the class abstract with @abstractclass # this can't be done, derived classes can # ... that is the goal def __init__(self, x=10, y=20): super(SomethingElse, self).__init__(x) self.y = y class ABCDerived(SomethingElse): pass with self.assertRaises(TypeError): Something(x=20) with self.assertRaises(TypeError): SomethingElse(x=20, y=30) x = 20 y = 30 abcDerived = ABCDerived(x, y) self.assertEqual(abcDerived.x + abcDerived.y, x+y) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_definitions.py ================================================ import unittest import oandapyV20.definitions as allDEF from oandapyV20.definitions.orders import definitions as orderDefs class TestDefinitions(unittest.TestCase): """Tests regarding the definitions.""" def test__order_definitions(self): """test for the dynamically generated definition classes.""" c = allDEF.orders.OrderType() self.assertTrue(isinstance(c, allDEF.orders.OrderType) and c['MARKET'] == orderDefs['OrderType']['MARKET']) def test__order_definitions_dictproperty(self): """test for the definitions property.""" c = allDEF.orders.OrderType() self.assertTrue(isinstance(c.definitions, dict) and c.definitions['MARKET'] == c['MARKET']) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_forexlabs.py ================================================ import sys import unittest import json from . import unittestsetup from .unittestsetup import environment as environment from .unittestsetup import TestData import requests_mock from oandapyV20 import API import oandapyV20.endpoints.forexlabs as labs from oandapyV20.endpoints.forexlabs import responses access_token = None accountID = None account_cur = None api = None class TestForexLabs(unittest.TestCase): """Tests regarding the forexlabs endpoints.""" def setUp(self): """setup for all tests.""" global access_token global accountID global account_cur global api # self.maxDiff = None try: accountID, account_cur, access_token = unittestsetup.auth() setattr(sys.modules["oandapyV20.oandapyV20"], "TRADING_ENVIRONMENTS", {"practice": { "stream": "https://test.com", "api": "https://test.com", }}) api = API(environment=environment, access_token=access_token, headers={"Content-Type": "application/json"}) api.api_url = 'https://test.com' except Exception as e: print("%s" % e) exit(0) @requests_mock.Mocker() def test__calendar(self, mock_get): """get the calendar information for an instrument.""" tid = "_v3_forexlabs_calendar" td = TestData(responses, tid) r = labs.Calendar(params=td.params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(td.resp)) api.request(r) self.assertTrue(td.resp == r.response) @requests_mock.Mocker() def test__histposratios(self, mock_get): """get the hist. pos. ratios information for an instrument.""" tid = "_v3_forexlabs_histposratios" td = TestData(responses, tid) r = labs.HistoricalPositionRatios(params=td.params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(td.resp)) api.request(r) self.assertTrue(td.resp == r.response) @requests_mock.Mocker() def test__spreads(self, mock_get): """get the spreads information for an instrument.""" tid = "_v3_forexlabs_spreads" td = TestData(responses, tid) r = labs.Spreads(params=td.params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(td.resp)) api.request(r) self.assertTrue(td.resp == r.response) @requests_mock.Mocker() def test__commoftrad(self, mock_get): """get the commitments of traders information for an instrument.""" tid = "_v3_forexlabs_commoftrad" td = TestData(responses, tid) r = labs.CommitmentsOfTraders(params=td.params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(td.resp)) api.request(r) self.assertTrue(td.resp == r.response) @requests_mock.Mocker() def test__orderbookdata(self, mock_get): """get the orderbookdata information for an instrument.""" tid = "_v3_forexlabs_orderbookdata" td = TestData(responses, tid) r = labs.OrderbookData(params=td.params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(td.resp)) api.request(r) self.assertTrue(td.resp == r.response) @requests_mock.Mocker() def test__autochartist(self, mock_get): """get autochartist information for an instrument.""" tid = "_v3_forexlabs_autochartist" td = TestData(responses, tid) r = labs.Autochartist(params=td.params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(td.resp)) api.request(r) self.assertTrue(td.resp == r.response) ================================================ FILE: tests/test_instruments.py ================================================ import sys import unittest import json from . import unittestsetup from .unittestsetup import environment as environment from .unittestsetup import fetchTestData import requests_mock from oandapyV20 import API from oandapyV20.endpoints.instruments import responses import oandapyV20.endpoints.instruments as instruments access_token = None accountID = None account_cur = None api = None class TestInstruments(unittest.TestCase): """Tests regarding the instruments endpoints.""" def setUp(self): """setup for all tests.""" global access_token global accountID global account_cur global api # self.maxDiff = None try: accountID, account_cur, access_token = unittestsetup.auth() setattr(sys.modules["oandapyV20.oandapyV20"], "TRADING_ENVIRONMENTS", {"practice": { "stream": "https://test.com", "api": "https://test.com", }}) api = API(environment=environment, access_token=access_token, headers={"Content-Type": "application/json"}) api.api_url = "https://test.com" except Exception as e: print("%s" % e) exit(0) @requests_mock.Mocker() def test__instruments_candles(self, mock_get): """get the candle information for instruments.""" instrument = "DE30_EUR" tid = "_v3_instruments_instrument_candles" resp, data, params = fetchTestData(responses, tid) r = instruments.InstrumentsCandles(instrument=instrument, params=params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__instruments_orderbook(self, mock_get): """get the orderbook information for instruments.""" instrument = "EUR_USD" tid = "_v3_instruments_instrument_orderbook" resp, data, params = fetchTestData(responses, tid) r = instruments.InstrumentsOrderBook(instrument=instrument, params=params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__instruments_positionbook(self, mock_get): """get the positionbook information for instruments.""" instrument = "EUR_USD" tid = "_v3_instruments_instrument_positionbook" resp, data, params = fetchTestData(responses, tid) r = instruments.InstrumentsPositionBook(instrument=instrument, params=params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_oandapyv20.py ================================================ import sys import unittest from . import unittestsetup from .unittestsetup import environment as environment from oandapyV20 import API access_token = None accountID = None account_cur = None api = None class TestOandapyV20(unittest.TestCase): """Tests regarding the client.""" def setUp(self): """setup for all tests.""" global access_token global accountID global account_cur global api # self.maxDiff = None try: accountID, account_cur, access_token = unittestsetup.auth() setattr(sys.modules["oandapyV20.oandapyV20"], "TRADING_ENVIRONMENTS", {"practice": { "stream": "https://test.com", "api": "https://test.com", }}) api = API(environment=environment, access_token=access_token, headers={"Content-Type": "application/json"}) except Exception as e: print("%s" % e) exit(0) def test__oandapyv20_environment(self): """test the exception on a faulty environment.""" with self.assertRaises(KeyError) as envErr: API(environment="faulty", access_token=access_token, headers={"Content-Type": "application/json"}) self.assertTrue("Unknown environment" in "{}".format(envErr.exception)) def test__requests_params(self): """request parameters.""" request_params = {"timeout": 10} api = API(environment=environment, access_token=access_token, headers={"Content-Type": "application/json"}, request_params=request_params) self.assertTrue(api.request_params == request_params) def test__requests_exception(self): """force a requests exception.""" from requests.exceptions import RequestException import oandapyV20.endpoints.accounts as accounts setattr(sys.modules["oandapyV20.oandapyV20"], "TRADING_ENVIRONMENTS", {"practice": { "stream": "ttps://test.com", "api": "ttps://test.com", }}) api = API(environment=environment, access_token=access_token, headers={"Content-Type": "application/json"}) text = "No connection " \ "adapters were found for 'ttps://test.com/v3/accounts'" r = accounts.AccountList() with self.assertRaises(RequestException) as oErr: api.request(r) self.assertEqual("{}".format(oErr.exception), text) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_orders.py ================================================ import sys import unittest import json from . import unittestsetup from .unittestsetup import environment as environment from .unittestsetup import fetchTestData import requests_mock from oandapyV20 import API import oandapyV20.endpoints.orders as orders from oandapyV20.endpoints.orders import responses access_token = None accountID = None account_cur = None api = None class TestOrders(unittest.TestCase): """Tests regarding the orders endpoints.""" def setUp(self): """setup for all tests.""" global access_token global accountID global account_cur global api # self.maxDiff = None try: accountID, account_cur, access_token = unittestsetup.auth() setattr(sys.modules["oandapyV20.oandapyV20"], "TRADING_ENVIRONMENTS", {"practice": { "stream": "https://test.com", "api": "https://test.com", }}) api = API(environment=environment, access_token=access_token, headers={"Content-Type": "application/json"}) api.api_url = 'https://test.com' except Exception as e: print("%s" % e) exit(0) def test__orders_base_exception(self): """test for the exception when using the baseclass.""" with self.assertRaises(TypeError) as bcErr: orders.Orders(accountID) bcErr = bcErr.exception print("FOUTJE: ", str(bcErr)) self.assertTrue("Can't instantiate abstract class Orders " "with abstract method" in "{}".format(bcErr)) @requests_mock.Mocker() def test__order_create(self, mock_post): """order create.""" tid = "_v3_accounts_accountID_orders_create" resp, data = fetchTestData(responses, tid) r = orders.OrderCreate(accountID, data=data) mock_post.register_uri('POST', "{}/{}".format(api.api_url, r), text=json.dumps(resp), status_code=r.expected_status) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__order_clientextensions(self, mock_put): """set order client extensions.""" tid = "_v3_accounts_accountID_order_clientextensions" resp, data = fetchTestData(responses, tid) r = orders.OrderClientExtensions(accountID, orderID="2304", data=data) mock_put.register_uri('PUT', "{}/{}".format(api.api_url, r), text=json.dumps(resp), status_code=r.expected_status) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__orders_pending(self, mock_get): """get the orders pending for an account.""" tid = "_v3_accounts_accountID_orders_pending" resp, data = fetchTestData(responses, tid) r = orders.OrdersPending(accountID) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp), status_code=r.expected_status) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__orders_list(self, mock_get): """get the orders for an account.""" tid = "_v3_accounts_accountID_orders_list" resp, data = fetchTestData(responses, tid) r = orders.OrderList(accountID) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue( len(result['orders']) == len(resp['orders']) and result['orders'][0]['instrument'] == resp['orders'][0]['instrument']) @requests_mock.Mocker() def test__order_details(self, mock_get): """details of an order.""" orderID = "2309" tid = "_v3_accounts_accountID_order_details" resp, data = fetchTestData(responses, tid) r = orders.OrderDetails(accountID, orderID=orderID) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) result = result["order"] self.assertTrue(result['id'] == orderID and result['units'] == resp["order"]["units"]) @requests_mock.Mocker() def test__order_cancel(self, mock_get): """cancel an order.""" orderID = "2307" tid = "_v3_accounts_accountID_order_cancel" resp, data = fetchTestData(responses, tid) r = orders.OrderCancel(accountID, orderID=orderID) mock_get.register_uri('PUT', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) result = result["orderCancelTransaction"] self.assertTrue(result['orderID'] == orderID and result['reason'] == "CLIENT_REQUEST" and result['type'] == "ORDER_CANCEL") @requests_mock.Mocker() def test__order_replace(self, mock_put): """replace an order.""" orderID = "2304" # to replace with data tid = "_v3_accounts_accountID_order_replace" resp, data = fetchTestData(responses, tid) r = orders.OrderReplace(accountID, orderID, data=data) mock_put.register_uri('PUT', "{}/{}".format(api.api_url, r), text=json.dumps(resp), status_code=r.expected_status) result = api.request(r) self.assertTrue( "orderCreateTransaction" in result and "orderCancelTransaction" in result and result["orderCancelTransaction"]["orderID"] == orderID and result["orderCreateTransaction"]["replacesOrderID"] == orderID and result["orderCreateTransaction"]["units"] == data["order"]['units'] and result["orderCreateTransaction"]["price"] == data["order"]['price']) @requests_mock.Mocker() def test__order_replace_wrong_status_exception(self, mock_get): """replacing an order with success but wrong status_code.""" orderID = "2125" # to replace with tmp = {"order": { "units": "-50000", "type": "LIMIT", "instrument": "EUR_USD", "price": "1.25", } } uri = 'https://test.com/v3/accounts/{}/orders/{}'.format(accountID, orderID) resp = responses["_v3_accounts_accountID_order_replace"]['response'] text = json.dumps(resp) r = orders.OrderReplace(accountID, orderID, data=tmp) # force the wrong status code mock_get.register_uri('PUT', uri, text=text, status_code=200) with self.assertRaises(ValueError) as err: api.request(r) self.assertTrue("200" in "{}".format(err.exception) and r.status_code is None) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_positions.py ================================================ import sys import unittest import json from . import unittestsetup from .unittestsetup import environment as environment from .unittestsetup import fetchTestData import requests_mock from oandapyV20 import API import oandapyV20.endpoints.positions as positions from oandapyV20.endpoints.positions import responses access_token = None accountID = None account_cur = None api = None class TestPositions(unittest.TestCase): """Tests regarding the positions endpoints.""" def setUp(self): """setup for all tests.""" global access_token global accountID global account_cur global api # self.maxDiff = None try: accountID, account_cur, access_token = unittestsetup.auth() setattr(sys.modules["oandapyV20.oandapyV20"], "TRADING_ENVIRONMENTS", {"practice": { "stream": "https://test.com", "api": "https://test.com", }}) api = API(environment=environment, access_token=access_token) api.api_url = 'https://test.com' except Exception as e: print("%s" % e) exit(0) @requests_mock.Mocker() def test__positions_list(self, mock_get): """get the positions list for an account.""" tid = "_v3_accounts_accountID_positions" resp, data = fetchTestData(responses, tid) r = positions.PositionList(accountID) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(resp == result) @requests_mock.Mocker() def test__openpositions_list(self, mock_get): """get the openpositions list for an account.""" tid = "_v3_accounts_accountID_openpositions" resp, data = fetchTestData(responses, tid) r = positions.OpenPositions(accountID) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(resp == result) @requests_mock.Mocker() def test__positiondetails(self, mock_get): """get the details of a single instrument's position.""" tid = "_v3_accounts_accountID_positiondetails" resp, data = fetchTestData(responses, tid) r = positions.PositionDetails(accountID, instrument="EUR_USD") mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(resp == result) @requests_mock.Mocker() def test__positionclose(self, mock_put): """close single instrument's position long side.""" tid = "_v3_accounts_accountID_position_close" resp, data = fetchTestData(responses, tid) r = positions.PositionClose(accountID, instrument="EUR_USD", data=data) mock_put.register_uri('PUT', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(resp == result) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_pricing.py ================================================ import sys import unittest import json from . import unittestsetup from .unittestsetup import environment as environment from .unittestsetup import fetchTestData import requests_mock from oandapyV20 import API from oandapyV20.exceptions import StreamTerminated import oandapyV20.endpoints.pricing as pricing from oandapyV20.endpoints.pricing import responses access_token = None accountID = None account_cur = None api = None class TestPricing(unittest.TestCase): """Tests regarding the pricing endpoints.""" def setUp(self): """setup for all tests.""" global access_token global accountID global account_cur global api # self.maxDiff = None try: accountID, account_cur, access_token = unittestsetup.auth() setattr(sys.modules["oandapyV20.oandapyV20"], "TRADING_ENVIRONMENTS", {"practice": { "stream": "https://test.com", "api": "https://test.com", }}) api = API(environment=environment, access_token=access_token) api.api_url = 'https://test.com' except Exception as e: print("%s" % e) exit(0) @requests_mock.Mocker() def test__pricing(self, mock_get): """get the pricing information for instruments.""" tid = "_v3_accounts_accountID_pricing" resp, data, params = fetchTestData(responses, tid) r = pricing.PricingInfo(accountID, params=params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) instr = params["instruments"].split(",") self.assertTrue(result["prices"][0]["instrument"] == instr[0] and result["prices"][1]["instrument"] == instr[1]) @requests_mock.Mocker() def test__pricing_stream(self, mock_get): """get the streaming pricing information for instruments.""" tid = "_v3_accounts_accountID_pricing_stream" resp, data, params = fetchTestData(responses, tid) text = "\n".join([json.dumps(t) for t in resp]) r = pricing.PricingStream(accountID, params=params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=text) result = [] n = 0 m = 3 with self.assertRaises(StreamTerminated): for rec in api.request(r): result.append(rec) n += 1 # terminate when we have m response lines if n == m: r.terminate() # the result containing m items, should equal the first m items self.assertTrue(result == resp[0:m]) def test__pricing_stream_termination_1(self): """terminate a stream that does not exist.""" params = {"instruments": "EUR_USD,EUR_JPY"} r = pricing.PricingStream(accountID, params=params) with self.assertRaises(ValueError): r.terminate() if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_trades.py ================================================ import sys import unittest import json from . import unittestsetup from .unittestsetup import environment as environment from .unittestsetup import fetchTestData import requests_mock from oandapyV20 import API import oandapyV20.endpoints.trades as trades from oandapyV20.endpoints.trades import responses access_token = None accountID = None account_cur = None api = None class TestTrades(unittest.TestCase): """Tests regarding the trades endpoints.""" def setUp(self): """setup for all tests.""" global access_token global accountID global account_cur global api # self.maxDiff = None try: accountID, account_cur, access_token = unittestsetup.auth() setattr(sys.modules["oandapyV20.oandapyV20"], "TRADING_ENVIRONMENTS", {"practice": { "stream": "https://test.com", "api": "https://test.com", }}) api = API(environment=environment, access_token=access_token) api.api_url = 'https://test.com' except Exception as e: print("%s" % e) exit(0) @requests_mock.Mocker() def test__trades_list(self, mock_get): """get the trades information for an account.""" tid = "_v3_accounts_accountID_trades" resp, data, params = fetchTestData(responses, tid) r = trades.TradesList(accountID, params=params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__open_trades(self, mock_get): """get the open trades information for an account.""" tid = "_v3_accounts_accountID_opentrades" resp, data = fetchTestData(responses, tid) r = trades.OpenTrades(accountID) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__trade_details(self, mock_get): """get the trade details for a trade.""" tid = "_v3_account_accountID_trades_details" resp, data = fetchTestData(responses, tid) r = trades.TradeDetails(accountID, tradeID=2315) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__trade_close(self, mock_put): """close trade by id .""" tid = "_v3_account_accountID_trades_close" resp, data = fetchTestData(responses, tid) r = trades.TradeClose(accountID, tradeID=2315) mock_put.register_uri('PUT', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__trade_cltext(self, mock_put): """trade client extensions.""" tid = "_v3_account_accountID_trades_cltext" resp, data = fetchTestData(responses, tid) r = trades.TradeClientExtensions(accountID, tradeID=2315, data=data) mock_put.register_uri('PUT', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__trade_crcdo(self, mock_put): """trade client extensions.""" tid = "_v3_account_accountID_trades_crcdo" resp, data = fetchTestData(responses, tid) r = trades.TradeCRCDO(accountID, tradeID=2323, data=data) mock_put.register_uri('PUT', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) result = api.request(r) self.assertTrue(result == resp) @requests_mock.Mocker() def test__trades_list_byids(self, mock_get): """get the trades information for an account.""" uri = 'https://test.com/v3/accounts/{}/trades'.format(accountID) resp = responses["_v3_accounts_accountID_trades"]['response'] text = json.dumps(resp) mock_get.register_uri('GET', uri, text=text) params = {"ids": "2121, 2123"} r = trades.TradesList(accountID, params=params) result = api.request(r) self.assertTrue(len(result['trades']) == 2 and result['trades'][0]['instrument'] == "DE30_EUR") if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_transactions.py ================================================ import sys import unittest import json from . import unittestsetup from .unittestsetup import environment as environment from .unittestsetup import fetchTestData import requests_mock from oandapyV20 import API from oandapyV20.exceptions import StreamTerminated import oandapyV20.endpoints.transactions as transactions from oandapyV20.endpoints.transactions import responses access_token = None accountID = None account_cur = None api = None class TestTransactions(unittest.TestCase): """Tests regarding the transactions endpoints.""" def setUp(self): """setup for all tests.""" global access_token global accountID global account_cur global api # self.maxDiff = None try: accountID, account_cur, access_token = unittestsetup.auth() setattr(sys.modules["oandapyV20.oandapyV20"], "TRADING_ENVIRONMENTS", {"practice": { "stream": "https://test.com", "api": "https://test.com", }}) api = API(environment=environment, access_token=access_token, headers={"Content-Type": "application/json"}) api.api_url = 'https://test.com' except Exception as e: print("%s" % e) exit(0) @requests_mock.Mocker() def test__transactions(self, mock_get): """get the transactions information for an account.""" tid = "_v3_accounts_accountID_transactions" resp, data, params = fetchTestData(responses, tid) r = transactions.TransactionList(accountID) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) api.request(r) self.assertTrue(resp == r.response) @requests_mock.Mocker() def test__transactions_details(self, mock_get): """get the transactions details for a transaction.""" tid = "_v3_accounts_transaction_details" resp, data = fetchTestData(responses, tid) transactionID = 2304 r = transactions.TransactionDetails(accountID, transactionID) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) api.request(r) self.assertTrue(resp == r.response) @requests_mock.Mocker() def test__transactions_idrange(self, mock_get): """get the transactions by an idrange.""" tid = "_v3_accounts_transaction_idrange" resp, data, params = fetchTestData(responses, tid) r = transactions.TransactionIDRange(accountID, params=params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) api.request(r) self.assertTrue(resp == r.response) @requests_mock.Mocker() def test__transactions_sinceid(self, mock_get): """get the transactions since an id.""" tid = "_v3_accounts_transaction_sinceid" resp, data, params = fetchTestData(responses, tid) r = transactions.TransactionsSinceID(accountID, params=params) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=json.dumps(resp)) api.request(r) self.assertTrue(resp == r.response) @requests_mock.Mocker() def test__transaction_stream(self, mock_get): """get the streaming transaction information.""" tid = "_v3_accounts_transactions_stream" resp, data = fetchTestData(responses, tid) text = "\n".join([json.dumps(t) for t in resp]) r = transactions.TransactionsStream(accountID) mock_get.register_uri('GET', "{}/{}".format(api.api_url, r), text=text) result = [] n = 0 m = 5 with self.assertRaises(StreamTerminated): api.request(r) for rv in r.response: result.append(rv) n += 1 # terminate when we have 3 response lines if n == m: r.terminate() # the result containing m items, should equal the first m items # of the source self.assertTrue(result == resp[0:m]) def test__transaction_stream_termination_1(self): """terminate a stream that does not exist.""" r = transactions.TransactionsStream(accountID) with self.assertRaises(ValueError): r.terminate() if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_types.py ================================================ import unittest from datetime import datetime try: from nose_parameterized import parameterized except: print("*** Please install 'nose_parameterized' to run these tests ***") exit(0) import oandapyV20.types as tp NOW = datetime.now() class TestTypes(unittest.TestCase): """Tests types.""" @parameterized.expand([ # AccountID (tp.AccountID, {"accountID": "001-011-5838423-001"}, {"siteID": "001", "divisionID": "011", "userID": "5838423", "accountNumber": "001"} ), (tp.AccountID, {"accountID": "0010115838423001"}, {"siteID": "001", "divisionID": "011", "userID": "5838423", "accountNumber": "001"}, ValueError ), # OrderID (tp.OrderID, {"orderID": "1234"}, "1234", ), (tp.OrderID, {"orderID": 1234}, "1234", ), (tp.OrderID, {"orderID": -1234}, "1234", ValueError ), # TradeID (tp.TradeID, {"tradeID": "1234"}, "1234", ), (tp.TradeID, {"tradeID": 1234}, "1234", ), (tp.TradeID, {"tradeID": -1234}, "1234", ValueError ), # AccountUnits (tp.AccountUnits, {"units": 1234}, "1234.00000", ), (tp.AccountUnits, {"units": "1234"}, "1234.00000", ), # PriceValue (tp.PriceValue, {"priceValue": 1234}, "1234.00000", ), (tp.PriceValue, {"priceValue": "1234"}, "1234.00000", ), # Units (tp.Units, {"units": 1234}, "1234", ), (tp.Units, {"units": "-1234"}, "-1234", ), (tp.Units, {"units": 10.5}, "10.5", ), (tp.Units, {"units": -10.5}, "-10.5", ), (tp.Units, {"units": -10.55}, "-10.55", ), (tp.Units, {"units": -12.655}, "-10.55", ValueError ), # ClientID (tp.ClientID, {"clientID": "my valid custom id"}, "my valid custom id" ), (tp.ClientID, {"clientID": "to long"+"x"*125}, "to long"+"x"*125, ValueError ), # ClientTag (tp.ClientTag, {"clientTag": "my valid custom tag"}, "my valid custom tag" ), (tp.ClientTag, {"clientTag": "to long"+"x"*125}, "to long"+"x"*125, ValueError ), # ClientComment (tp.ClientComment, {"clientComment": "my valid custom comment"}, "my valid custom comment" ), (tp.ClientComment, {"clientComment": "to long"+"x"*125}, "to long"+"x"*125, ValueError ), # OrderIdentifier (tp.OrderIdentifier, {"orderID": 1234, "clientID": "my valid custom id"}, {"orderID": "1234", "clientOrderID": "my valid custom id"}, ), (tp.OrderIdentifier, {"orderID": "X1234", "clientID": "my valid custom id"}, {"orderID": "1234", "clientOrderID": "my valid custom id"}, ValueError ), (tp.OrderIdentifier, {"orderID": "1234", "clientID": "to long"+"x"*125}, {"orderID": "1234", "clientOrderID": "to long"+"x"*125}, ValueError ), # OrderSpecifier (tp.OrderSpecifier, {"specifier": 1234}, "1234", ), (tp.OrderSpecifier, {"specifier": "1234"}, "1234", ), (tp.OrderSpecifier, {"specifier": "@"}, "@", ValueError ), (tp.OrderSpecifier, {"specifier": "@my valid custom id"}, "my valid custom id", ), (tp.OrderSpecifier, {"specifier": "@"+"to long"+"x"*125}, "to long"+"x"*125, ValueError ), # DateTime # no sub-seconds (tp.DateTime, {"dateTime": "2014-07-02T04:00:00Z"}, "2014-07-02T04:00:00Z", ), # sub-seconds (milli) (tp.DateTime, {"dateTime": "2014-07-02T04:00:00.000Z"}, "2014-07-02T04:00:00.000000Z", ), # sub-seconds (micro) (tp.DateTime, {"dateTime": "2014-07-02T04:00:00.000000Z"}, "2014-07-02T04:00:00.000000Z", ), # sub-seconds (nano) (tp.DateTime, {"dateTime": "2014-07-02T04:00:00.000000Z"}, "2014-07-02T04:00:00.000000Z", ), # using a dict with date/time values (tp.DateTime, {"dateTime": {"year": 2014, "month": 12, "day": 2, "hour": 13, "minute": 48, "second": 12}}, "2014-12-02T13:48:12Z", ), # using a dict with date/time values + sub-seconds (tp.DateTime, {"dateTime": {"year": 2014, "month": 12, "day": 2, "hour": 13, "minute": 48, "second": 12, "subsecond": 0}}, "2014-12-02T13:48:12.000000Z", ), # using a datetime.datetime instance (tp.DateTime, {"dateTime": NOW}, datetime.strftime(NOW, "%Y-%m-%dT%H:%M:%S.%fZ") ), # test for exception (missing digit in seconds) (tp.DateTime, {"dateTime": "2014-07-02T04:00:0"}, "2014-07-02T04:00:00.000000Z", ValueError ), ]) def test__types(self, cls, inpar, reference, exc=None): """test_types.""" if not exc: r = cls(**inpar) self.assertTrue(r.value == reference) else: with self.assertRaises(exc) as err: r = cls(**inpar) self.assertTrue("ValueError" in err) if __name__ == "__main__": unittest.main() ================================================ FILE: tests/token.txt ================================================ aaaa9999bbbb8888cccc7777dddd6666-eeee5555ffff4444aaaa3333bbbb2222 ================================================ FILE: tests/unittestsetup.py ================================================ """ initialization of unittests and data for unittests """ import time environment = "practice" # calculate expiryDate as an offset from today # now + 5 days days = 5 expiryDate = time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(int(time.time() + 86400*days))) def fetchTestData(responses, k): resp = responses[k]['response'] params, data = None, None if 'body' in responses[k]: data = responses[k]['body'] if "params" in responses[k]: params = responses[k]['params'] if params is not None: return (resp, data, params) return (resp, data) class TestData(object): def __init__(self, responses, tid): self._responses = responses[tid] @property def resp(self): return self._responses['response'] @property def body(self): return self._responses['body'] @property def params(self): return self._responses['params'] def auth(): access_token = None account = None currency = None with open("tests/account.txt") as A: account, currency = A.read().strip().split("|") with open("tests/token.txt") as T: access_token = T.read().strip() if account == "99999999|EUR": raise Exception( "\n" "**************************************************\n" "*** PLEASE PROVIDE YOUR account in account.txt ***\n" "*** AND token IN token.txt TO RUN THE TESTS ***\n" "**************************************************\n") return account, currency, access_token