fxcpy
fxcpy is an open-soured python implementation of the Forexconnect API SDK offered by FXCM. The full documentation can be found here
Current Features
-
Trading Tables - fxcpy supports all trading tables in memory for fast access updated automatically by the trading server.
AccountsTable
- contains the data such as account balance, used margin, daily PnL, Gross PnL etc...OffersTable
- all instrument attributes, such as symbol, live bid/ask pricing, point-size, contract currency etc ...OrdersTable
- holds order attributes until they are executed.TradesTable
- once orders are executed, trades are inserted and tracked with various attributes.ClosedTradesTable
- contains trades that are closed for the current trading day.SummaryTable
- contains summarised information for every instrument with an open position.MessagesTable
- deals with messages sent from the trading server, such as a margin call.
-
Trading - fxcpy is very flexible with many options for executing different types of orders.
- OCO (One-Cancels-Other)
- OTO (One-Triggers-Others)
- OTOCO (One-Triggers-OCO)
- Limit, Entry Limit, Trailing Entry Limit
- Open/Close Market, Market Range
- Open/Close Limit, Limit Range
- Stop, Entry Stop, Trailing Stop
- NET Orders
- Order cloning
-
Data - fxcpy supports both live streaming and historical price data
Requirements
- Ubuntu 16.04
- boost 1.65.1
- cmake 3.9.6
- ForexConnectAPI 1.4.1 (included)
Installation
A large part of installing this API has to do with Boost & CMAKE, therefore the install_script.sh
includes the installation of both and this API. Currently, Ubuntu 16.04 is supported, there are no plans to support the Windows operating system. However, support will be added for other Linux variants shortly.
Please become familiar with the installation process and remove any elements already installed on your system. If boost 1.65.1 or higher is installed using a different path than the usual /usr:/usr/local...etc
, edit the commented values in the CMakeLists.txt file located in the /cpp
directory.
First download this repository.
git clone https://github.com/JamesKBowler/fxcpy.git
Switch to the fxcpy/
directory.
cd fxcpy/
Once happy with the script execute following.
chmod +x install_script.sh && sudo ./install_script.sh
The script will add an environment variable to /etc/environment file. However, this will not come into effect until your machine is either rebooted or logged out and back in.
To find out more about environment variables, please read this question on askubuntu.
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/fxcpy/cpp/lib
After installation your system will have two modules forexconnect
and fxcpy
. Oh and pandas
if not already installed.
The forexconnect
module is a C++ Wrapper and fxcpy
is the python implementation. Before using this API in a live trading environment, I highly recommend testing it first on a demo account, opened for free at FXCM
Having FXCM's Trading Station open at the same to see trades simultaneously executed is a good idea.
Basic Usage
We won't discuss best practices for storing passwords and for simplicity, create a setting.py file to hold your FXCM user, password, environment and url information.
An example of such is.
# /fxcpy/fxcpy/settings.py
USER = "DEM12345"
PASS = "123456"
URL = "http://www.fxcorporate.com/Hosts.jsp"
ENV = "demo" # or "real"
To get started create a session by logging into your FXCM account
from fxcpy.session_handler import SessionHandler
from fxcpy.settings import USER, PASS, URL, ENV
session_handler = SessionHandler(USER, PASS, URL, ENV, load_tables=True)
Monitoring session status is carried out through the SessionMonitoring
class, located within the SessionHandler
status = session_handler.session_monitoring.get_status()
To obtain information for all offers at FXCM, get the OffersTable
class from the SessionHandler
offers_table = session_handler.get_offers_table()
The Forexconnect API has no built-in function to find attributes using the instrument symbol, so we must always pass the unique offer_id
. Sure, one could build such a feature. However, this would involve looping over each row in the table on every call.
All offer attributes are accessed through the OffersTable
like this.
offer = offers_table.get_offer_ids()
offer = {
'AUD/CAD': '16',
'AUD/CHF': '39',
'AUD/JPY': '17',
'AUD/NZD': '28',
'AUD/USD': '6',
'AUS200': '1001',
'Bund': '3001',
'CAD/CHF': '90',
'CAD/JPY': '18',
'CHF/JPY': '12',
'CHN50': '1020',
'Copper': '1016',
'ESP35': '1002',
'EUR/AUD': '14',
'EUR/USD': '1',
.....
}
offers_table.get_contract_currency(offer['EUR/USD'])
All other tables are accessed the same way.
orders_table.get_whatever(order_id)
trades_table.get_whatever(trade_id)
and so on ..
Executing a trade is super easy using theTradingCommands
class.
trading_commands = session_handler.get_trading_commands()
Next, execute 5 SHORT trades for the EUR/USD, with stop loss and limit orders with one API call.
This example will place a stop loss 15 pips above and a limit order 30 pips below the current price.
# Instrument
offer_id = offer['EUR/USD']
# Point Size
psize = offers_table.get_point_size(offer_id) # 0.0001
# 15 pip Stop Loss Order
stop = offers_table.get_bid(offer_id) + 15.0 * psize
# 30 pip Limit Order
limit = offers_table.get_bid(offer_id) - 30.0 * psize
# Order amount
amount = 1 # 1k lot
# BuySell direction
buysell = "S" # Short
# Master valuemap container
master = trading_commands.create_valuemap()
for i in range(5):
# Create the order
child = trading_commands.create_open_market_order(
offer_id,
buysell,
amount
)
# Attach Market order to the master_valuemap
master.appendChild(child)
# Attach Limit & Stop Order to the child valuemap
master = trading_commands.attach_stoplimit_orders(
i, # valuemap index
master,
rate_stop=stop,
rate_limit=limit
)
# Execute with one API call
trading_commands.execute_order(master)
# Lock the GIL (global interpreter lock) until trade is executed.
response_listener.wait_events()
Check out the /tests
directory for more examples.
Monitoring of trade execution is carried out using the OrderMoitor
class, which is updated by the TableListener
.
order_monitor = session_handler.get_order_monitor()
orders = order_monitor.get_monitors()
Each trade_id
has its own monitoring class, with each subsequent order appended to the initial trade conveniently wrapped in a dictionary.
monitors = order_monitor.get_monitors()
{'91133665': <fxcpy.listeners.order.Order at 0x7fa038068630>,
'91133774': <fxcpy.listeners.order.Order at 0x7fa038068668>,
'91145522': <fxcpy.listeners.order.Order at 0x7fa038068cf8>,
'91145541': <fxcpy.listeners.order.Order at 0x7fa038068cc0>,
...
...}
# Access using trade_id
order = monitors['91133665']
order.get_result()
"Executed"
order.get_state()
"OrderExecuted"
To close all positions at the current market price, extract trade attributes from the TradesTable
class.
master_valuemap = trading_commands.create_valuemap()
for trade_id, offer_id in trades_table.get_trade_ids().items():
direction = trades_table.get_buysell(trade_id)
if direction == 'B':
buysell = 'S'
else: # direction == 'S'
buysell = 'B'
amount = trades_table.get_amount(trade_id)
child_valuemap = trading_commands.create_close_market_order(
offer_id,
buysell,
trade_id=trade_id,
amount=amount,
net_quantity='N'
)
master_valuemap.appendChild(child_valuemap)
trading_commands.execute_order(master_valuemap)
response_listener.wait_events()
Price History
FXCM has tons of free data, and the MarketData
class will provide access to these data.
Note:
- FXCM servers will never return more than 300 bars of data in one API call.
- All datetime is stored in UTC and of type OLE automation, for instance
float(0.0) = datetime(1899,12,30)
, take a look in the/utils
directory.
from datetime import datetime
market_data = session_handler.get_market_data()
data_gen = market_data.get_price_data("GBP/USD", "D1", datetime(2018,2,22,22,0), datetime(2018,3,9,22,0))
data = next(data_gen)
print(data)
Generator returns a structured numpy array.
np.array([
('2018-02-22T22:00:00', 1.39587, 1.40062, 1.39044, 1.3977 , 1.39506, 1.4005 , 1.39043, 1.39689, 315585),
('2018-02-25T22:00:00', 1.3991 , 1.40706, 1.39288, 1.39702, 1.3985 , 1.40695, 1.39275, 1.39651, 306833),
('2018-02-26T22:00:00', 1.39702, 1.39974, 1.38583, 1.39124, 1.39651, 1.39958, 1.3857 , 1.3904 , 393485),
('2018-02-27T22:00:00', 1.39124, 1.39169, 1.37571, 1.37629, 1.3904 , 1.39157, 1.37562, 1.3759 , 377407),
('2018-02-28T22:00:00', 1.37629, 1.37863, 1.37123, 1.37783, 1.3759 , 1.37848, 1.37111, 1.37745, 300786),
('2018-03-01T22:00:00', 1.37783, 1.38177, 1.37562, 1.38046, 1.37745, 1.38162, 1.37548, 1.37992, 289091),
('2018-03-04T22:00:00', 1.38026, 1.38783, 1.37674, 1.38503, 1.37984, 1.38769, 1.37658, 1.38478, 319845),
('2018-03-05T22:00:00', 1.38503, 1.39304, 1.38175, 1.3889 , 1.38478, 1.39289, 1.3816 , 1.38848, 305963),
('2018-03-06T22:00:00', 1.3889 , 1.39135, 1.38469, 1.3902 , 1.38848, 1.39121, 1.38453, 1.38943, 326001),
('2018-03-07T22:00:00', 1.3902 , 1.39111, 1.37824, 1.38121, 1.38943, 1.39093, 1.37803, 1.38061, 672878),
('2018-03-08T22:00:00', 1.38121, 1.389 , 1.3789 , 1.38549, 1.38061, 1.38886, 1.37875, 1.38486, 309030)],
dtype=[('date', '<M8[s]'), ('askopen', '<f8'), ('askhigh', '<f8'), ('asklow', '<f8'), ('askclose', '<f8'),
('bidopen', '<f8'), ('bidhigh', '<f8'), ('bidlow', '<f8'), ('bidclose', '<f8'), ('volume', '<i8')]
)
Charting
Please see the /tests
directory for a completed example.
# Collection attribs
dtto = datetime.utcnow()
dtfm = dtto - timedelta(days=200)
instrument = "GBP/USD"
time_frame = "D1"
# Plot chart
BasicChart(market_data).graph(instrument, time_frame, dtfm, dtto)
Development
There is a lot of testing to be completed, and I am slowly working on it in my spare time.
Feel free to offer advice on any improvements.
License Terms
Copyright (c) 2018 James K Bowler
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.
Forex Trading Disclaimer
Trading foreign exchange on margin carries a high level of risk, and may not be suitable for all investors. Past performance is not indicative of future results. The high degree of leverage can work against you as well as for you. Before deciding to invest in foreign exchange you should carefully consider your investment objectives, level of experience, and risk appetite. The possibility exists that you could sustain a loss of some or all of your initial investment and therefore you should not invest money that you cannot afford to lose. You should be aware of all the risks associated with foreign exchange trading, and seek advice from an independent financial advisor if you have any doubts.