Complete Guide to CFD Trading: What Are CFDs & How to Automate Trading with Python (2025)
Contracts for Difference (CFDs) have become one of the most popular ways to trade financial markets globally. This comprehensive guide will explain what CFDs are, how they work, and most importantly—how to automate your CFD trading using Python.
What is a CFD (Contract for Difference)?
A Contract for Difference (CFD) is a financial derivative that allows you to speculate on the price movement of an asset without owning the actual underlying asset.
How CFDs Work
Instead of buying actual stocks, commodities, or currencies, you enter into a contract with a broker to exchange the difference in price between when you open and close your trade.
Profit/Loss = (Exit Price - Entry Price) × Position Size × Contract Value
Simple Example
| Scenario | Action | Entry Price | Exit Price | Units | Result |
|---|---|---|---|---|---|
| Bullish | BUY (Long) | $100 | $110 | 10 | +$100 profit |
| Bullish | BUY (Long) | $100 | $90 | 10 | -$100 loss |
| Bearish | SELL (Short) | $100 | $90 | 10 | +$100 profit |
| Bearish | SELL (Short) | $100 | $110 | 10 | -$100 loss |
Key Characteristics of CFDs
- No Ownership: You never own the underlying asset
- Leverage: Trade larger positions with smaller capital (e.g., 1:30 leverage means $1,000 controls $30,000)
- Go Long or Short: Profit from both rising AND falling markets
- No Expiry Date: Unlike futures, CFDs have no fixed settlement date
- Wide Market Access: Trade stocks, indices, forex, commodities, crypto—all from one account
CFDs vs Futures vs Stocks
| Feature | CFDs | Futures | Stocks |
|---|---|---|---|
| Ownership | No | No | Yes |
| Leverage | High (1:5 to 1:30) | High | Low (margin accounts) |
| Expiry Date | None | Yes (quarterly) | None |
| Short Selling | Easy | Easy | Restricted |
| Overnight Fees | Yes (swap) | No | No |
| Markets | All | Limited | Stocks only |
| Regulation | Varies | Strict (exchanges) | Strict |
Popular CFD Instruments
Index CFDs
- US100 - NASDAQ 100 Index
- US500 - S&P 500 Index
- US30 - Dow Jones Industrial Average
- GER40 - DAX 40 (Germany)
- UK100 - FTSE 100 (UK)
Forex CFDs
- EUR/USD, GBP/USD, USD/JPY, AUD/USD
Commodity CFDs
- Gold (XAUUSD), Silver (XAGUSD), Oil (WTI, Brent)
Stock CFDs
- Apple, Tesla, Nvidia, Amazon, Microsoft, etc.
Crypto CFDs
- Bitcoin, Ethereum, and other major cryptocurrencies
Understanding CFD Costs
1. Spread
The difference between the buy (ask) and sell (bid) price.
spread = ask_price - bid_price
spread_cost = spread * position_size * contract_value
2. Swap/Overnight Fees
Charged when holding positions overnight. Can be positive or negative depending on the instrument and direction.
3. Commission (some brokers)
Fixed fee per trade or per lot traded.
Example Cost Calculation
# Trading 1 lot of US100 CFD
entry_price = 21500
spread = 1.5 # points
contract_value = 1 # $1 per point for mini lot
position_size = 1
spread_cost = spread * contract_value * position_size
print(f"Spread cost: ${spread_cost}") # $1.50
# If held overnight (example swap rate)
swap_rate = -2.50 # $ per night for long position
print(f"Overnight cost: ${swap_rate}")
CFD Brokers with API Access
1. IG Markets
Best for: Professional API trading, wide market coverage
- API: REST API + Streaming (Lightstreamer)
-
Python Library:
trading-ig - Markets: 17,000+ CFD markets
- Regulation: FCA, ASIC, BaFin
2. OANDA
Best for: Forex and CFD automation
- API: REST v20 API
-
Python Library:
oandapyV20,v20 - Markets: Forex, indices, commodities, bonds
- Regulation: FCA, CFTC, ASIC
3. XTB
Best for: European traders
- API: xStation5 WebSocket API
-
Python Library:
XTBApi,xtbclient - Markets: 5,800+ CFD instruments
- Regulation: FCA, CySEC, BaFin
4. MetaTrader 5 Brokers
Best for: Widest broker compatibility
- API: MetaTrader 5 Python integration
-
Python Library:
MetaTrader5 - Brokers: IC Markets, Pepperstone, FP Markets, etc.
- Markets: Depends on broker
5. cTrader Brokers
Best for: Advanced algo trading
- API: cTrader Open API
-
Python Library:
ctrader-open-api - Brokers: IC Markets, Pepperstone, FxPro
- Markets: Depends on broker
Broker Comparison Table
| Broker | API Type | Python Library | Min Deposit | Spreads |
|---|---|---|---|---|
| IG Markets | REST + Stream | trading-ig | $0 | From 0.6 |
| OANDA | REST v20 | oandapyV20 | $0 | From 1.0 |
| XTB | WebSocket | XTBApi | $0 | From 0.5 |
| MT5 Brokers | Native | MetaTrader5 | Varies | Varies |
| cTrader | Open API | ctrader-open-api | Varies | Varies |
⚠️ Risk Warning: 67-80% of retail investor accounts lose money when trading CFDs. You should consider whether you understand how CFDs work and whether you can afford to take the high risk of losing your money.
Setting Up Python for CFD Trading
Install Required Libraries
# Core libraries
pip install pandas numpy matplotlib
# Broker-specific libraries
pip install trading-ig # IG Markets
pip install oandapyV20 # OANDA
pip install XTBApi # XTB
pip install MetaTrader5 # MT5 (Windows only)
pip install ctrader-open-api # cTrader
# Technical analysis
pip install ta pandas-ta
Automated CFD Trading with IG Markets
Setup
- Create an IG Markets account (demo or live)
- Enable API access in your account settings
- Get your API key from IG Labs
Basic Connection
from trading_ig import IGService
from trading_ig.config import config
import pandas as pd
# Initialize connection
ig_service = IGService(
username='your_username',
password='your_password',
api_key='your_api_key',
acc_type='DEMO' # or 'LIVE'
)
# Login
ig_service.create_session()
print("Connected to IG Markets!")
# Get account info
account_info = ig_service.fetch_accounts()
print(account_info)
Get Market Data
# Search for US100 (NASDAQ 100)
markets = ig_service.search_markets("US Tech 100")
print(markets)
# Get specific market details
# EPIC for US100 CFD is typically: IX.D.NASDAQ.IFD.IP
epic = "IX.D.NASDAQ.IFD.IP"
market_info = ig_service.fetch_market_by_epic(epic)
print(f"Instrument: {market_info['instrument']['name']}")
print(f"Bid: {market_info['snapshot']['bid']}")
print(f"Ask: {market_info['snapshot']['offer']}")
# Get historical prices
prices = ig_service.fetch_historical_prices_by_epic(
epic=epic,
resolution="HOUR",
numpoints=100
)
df = pd.DataFrame(prices['prices'])
print(df.tail())
Place CFD Orders
# Open a BUY position on US100
response = ig_service.create_open_position(
epic="IX.D.NASDAQ.IFD.IP",
direction="BUY",
size=1, # 1 contract
order_type="MARKET",
currency_code="USD",
force_open=True,
guaranteed_stop=False,
stop_level=None,
stop_distance=50, # 50 points stop loss
limit_level=None,
limit_distance=100 # 100 points take profit
)
print(f"Deal Reference: {response['dealReference']}")
# Check position status
deal_ref = response['dealReference']
confirm = ig_service.fetch_deal_by_deal_reference(deal_ref)
print(f"Status: {confirm['dealStatus']}")
Close Position
# Get all open positions
positions = ig_service.fetch_open_positions()
print(positions)
# Close a specific position
deal_id = "DEAL_ID_HERE"
close_response = ig_service.close_open_position(
deal_id=deal_id,
direction="SELL", # Opposite of open direction
size=1,
order_type="MARKET"
)
print(f"Position closed: {close_response}")
Automated CFD Trading with OANDA
Setup
- Create an OANDA fxTrade account
- Generate API token in Account Management Portal
- Note your account ID
Basic Connection
import oandapyV20
import oandapyV20.endpoints.accounts as accounts
import oandapyV20.endpoints.instruments as instruments
import oandapyV20.endpoints.orders as orders
import oandapyV20.endpoints.positions as positions
# Configuration
ACCESS_TOKEN = "your_access_token"
ACCOUNT_ID = "your_account_id"
ENVIRONMENT = "practice" # or "live"
# Initialize client
client = oandapyV20.API(
access_token=ACCESS_TOKEN,
environment=ENVIRONMENT
)
# Get account summary
r = accounts.AccountSummary(ACCOUNT_ID)
client.request(r)
print(f"Balance: {r.response['account']['balance']}")
print(f"NAV: {r.response['account']['NAV']}")
Get Market Data
import pandas as pd
# Get current price
params = {"instruments": "EUR_USD,US30_USD,NAS100_USD"}
r = instruments.InstrumentsCandles(
instrument="NAS100_USD", # US100/NASDAQ CFD
params={"count": 100, "granularity": "H1"}
)
client.request(r)
# Convert to DataFrame
candles = r.response['candles']
data = []
for candle in candles:
data.append({
'time': candle['time'],
'open': float(candle['mid']['o']),
'high': float(candle['mid']['h']),
'low': float(candle['mid']['l']),
'close': float(candle['mid']['c']),
'volume': candle['volume']
})
df = pd.DataFrame(data)
df['time'] = pd.to_datetime(df['time'])
print(df.tail())
Place CFD Orders
# Market Order - Buy 1 unit of NAS100 (NASDAQ 100 CFD)
order_data = {
"order": {
"instrument": "NAS100_USD",
"units": "1", # Positive for BUY, negative for SELL
"type": "MARKET",
"positionFill": "DEFAULT"
}
}
r = orders.OrderCreate(ACCOUNT_ID, data=order_data)
client.request(r)
print(f"Order placed: {r.response}")
# Market Order with Stop Loss and Take Profit
order_data = {
"order": {
"instrument": "NAS100_USD",
"units": "1",
"type": "MARKET",
"positionFill": "DEFAULT",
"stopLossOnFill": {
"distance": "50" # 50 points
},
"takeProfitOnFill": {
"distance": "100" # 100 points
}
}
}
r = orders.OrderCreate(ACCOUNT_ID, data=order_data)
client.request(r)
print(f"Order with SL/TP: {r.response}")
# Limit Order
limit_order = {
"order": {
"instrument": "NAS100_USD",
"units": "1",
"type": "LIMIT",
"price": "21000.0",
"timeInForce": "GTC" # Good Till Cancelled
}
}
r = orders.OrderCreate(ACCOUNT_ID, data=limit_order)
client.request(r)
Get and Close Positions
# Get all open positions
r = positions.OpenPositions(ACCOUNT_ID)
client.request(r)
print(f"Open positions: {r.response}")
# Close a specific position
close_data = {
"longUnits": "ALL" # Close all long units
# or "shortUnits": "ALL" for short positions
}
r = positions.PositionClose(
ACCOUNT_ID,
instrument="NAS100_USD",
data=close_data
)
client.request(r)
print(f"Position closed: {r.response}")
Automated CFD Trading with XTB
Setup
- Create an XTB account
- Enable API access
- Note your account ID and password
Basic Connection
from XTBApi.api import Client
# Initialize client
client = Client()
# Login
client.login("your_user_id", "your_password", mode="demo") # or "real"
print("Connected to XTB!")
# Get account info
account_info = client.get_margin_level()
print(f"Balance: {account_info['balance']}")
print(f"Equity: {account_info['equity']}")
Get Market Data
# Get current price for US100
symbol = "US100"
price = client.get_symbol(symbol)
print(f"Symbol: {price['symbol']}")
print(f"Ask: {price['ask']}")
print(f"Bid: {price['bid']}")
# Get all available symbols
all_symbols = client.get_all_symbols()
for sym in all_symbols:
if 'US' in sym['symbol']:
print(f"{sym['symbol']}: {sym['description']}")
Place Orders
# Buy US100 CFD
trade = client.open_trade(
mode=0, # 0=BUY, 1=SELL
symbol="US100",
volume=0.1 # Lot size
)
print(f"Trade opened: {trade}")
# Buy with Stop Loss and Take Profit
trade = client.open_trade(
mode=0,
symbol="US100",
volume=0.1,
sl=21400.0, # Stop Loss price
tp=21600.0 # Take Profit price
)
# Get open trades
open_trades = client.get_trades()
for t in open_trades:
print(f"Order: {t['order']}, Symbol: {t['symbol']}, Profit: {t['profit']}")
# Close a trade
client.close_trade(order_id=12345)
# Logout when done
client.logout()
Automated CFD Trading with MetaTrader 5
Prerequisites
- Windows 10 or higher (MT5 Python API is Windows-only)
- MetaTrader 5 terminal installed
- Account with an MT5 broker (IC Markets, Pepperstone, etc.)
Setup
import MetaTrader5 as mt5
import pandas as pd
from datetime import datetime
# Initialize MT5 connection
if not mt5.initialize():
print(f"Initialize failed: {mt5.last_error()}")
quit()
print(f"MetaTrader5 version: {mt5.version()}")
# Login to account
account = 12345678 # Your account number
password = "your_password"
server = "ICMarketsSC-Demo" # Your broker's server
authorized = mt5.login(account, password=password, server=server)
if authorized:
print(f"Connected to account #{account}")
account_info = mt5.account_info()
print(f"Balance: {account_info.balance}")
print(f"Equity: {account_info.equity}")
else:
print(f"Login failed: {mt5.last_error()}")
Get Market Data
# Get symbol info
symbol = "US100.cash" # Symbol name varies by broker
symbol_info = mt5.symbol_info(symbol)
if symbol_info is None:
print(f"Symbol {symbol} not found")
else:
print(f"Symbol: {symbol_info.name}")
print(f"Bid: {symbol_info.bid}")
print(f"Ask: {symbol_info.ask}")
print(f"Spread: {symbol_info.spread}")
# Enable symbol for trading
if not symbol_info.visible:
mt5.symbol_select(symbol, True)
# Get historical data
rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_H1, 0, 100)
df = pd.DataFrame(rates)
df['time'] = pd.to_datetime(df['time'], unit='s')
print(df.tail())
# Get tick data
ticks = mt5.copy_ticks_from(symbol, datetime.now(), 1000, mt5.COPY_TICKS_ALL)
ticks_df = pd.DataFrame(ticks)
print(ticks_df.tail())
Place CFD Orders
# Prepare trade request - Market Buy
symbol = "US100.cash"
lot = 0.1
point = mt5.symbol_info(symbol).point
price = mt5.symbol_info_tick(symbol).ask
deviation = 20
request = {
"action": mt5.TRADE_ACTION_DEAL,
"symbol": symbol,
"volume": lot,
"type": mt5.ORDER_TYPE_BUY,
"price": price,
"sl": price - 50 * point, # Stop Loss 50 points below
"tp": price + 100 * point, # Take Profit 100 points above
"deviation": deviation,
"magic": 234000, # Expert Advisor ID
"comment": "Python CFD Bot",
"type_time": mt5.ORDER_TIME_GTC,
"type_filling": mt5.ORDER_FILLING_IOC,
}
# Send order
result = mt5.order_send(request)
print(f"Order result: {result}")
if result.retcode != mt5.TRADE_RETCODE_DONE:
print(f"Order failed: {result.comment}")
else:
print(f"Order placed! Ticket: {result.order}")
Manage Positions
# Get all open positions
positions = mt5.positions_get()
if positions:
for pos in positions:
print(f"Ticket: {pos.ticket}")
print(f"Symbol: {pos.symbol}")
print(f"Type: {'BUY' if pos.type == 0 else 'SELL'}")
print(f"Volume: {pos.volume}")
print(f"Profit: {pos.profit}")
print("---")
# Close a specific position
position_id = 12345678
position = mt5.positions_get(ticket=position_id)
if position:
pos = position[0]
close_request = {
"action": mt5.TRADE_ACTION_DEAL,
"symbol": pos.symbol,
"volume": pos.volume,
"type": mt5.ORDER_TYPE_SELL if pos.type == 0 else mt5.ORDER_TYPE_BUY,
"position": pos.ticket,
"price": mt5.symbol_info_tick(pos.symbol).bid if pos.type == 0 else mt5.symbol_info_tick(pos.symbol).ask,
"deviation": 20,
"magic": 234000,
"comment": "Close by Python",
"type_time": mt5.ORDER_TIME_GTC,
"type_filling": mt5.ORDER_FILLING_IOC,
}
result = mt5.order_send(close_request)
print(f"Close result: {result}")
# Shutdown MT5 connection
mt5.shutdown()
Complete CFD Trading Bot Example
import oandapyV20
import oandapyV20.endpoints.instruments as instruments
import oandapyV20.endpoints.orders as orders
import oandapyV20.endpoints.positions as positions
import oandapyV20.endpoints.accounts as accounts
import pandas as pd
import numpy as np
import time
import logging
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class CFDTradingBot:
def __init__(self, access_token, account_id, environment="practice"):
self.client = oandapyV20.API(
access_token=access_token,
environment=environment
)
self.account_id = account_id
self.position = 0
def get_account_balance(self):
r = accounts.AccountSummary(self.account_id)
self.client.request(r)
return float(r.response['account']['balance'])
def get_candles(self, instrument, count=100, granularity="H1"):
params = {"count": count, "granularity": granularity}
r = instruments.InstrumentsCandles(instrument=instrument, params=params)
self.client.request(r)
data = []
for candle in r.response['candles']:
if candle['complete']:
data.append({
'time': candle['time'],
'open': float(candle['mid']['o']),
'high': float(candle['mid']['h']),
'low': float(candle['mid']['l']),
'close': float(candle['mid']['c']),
})
df = pd.DataFrame(data)
df['time'] = pd.to_datetime(df['time'])
return df
def calculate_signals(self, df, fast_period=10, slow_period=20):
df['SMA_fast'] = df['close'].rolling(window=fast_period).mean()
df['SMA_slow'] = df['close'].rolling(window=slow_period).mean()
# Generate signals
df['signal'] = 0
df.loc[df['SMA_fast'] > df['SMA_slow'], 'signal'] = 1 # BUY
df.loc[df['SMA_fast'] < df['SMA_slow'], 'signal'] = -1 # SELL
return df
def get_open_positions(self, instrument):
r = positions.OpenPositions(self.account_id)
self.client.request(r)
for pos in r.response.get('positions', []):
if pos['instrument'] == instrument:
long_units = int(pos['long']['units'])
short_units = int(pos['short']['units'])
return long_units + short_units
return 0
def place_order(self, instrument, units, stop_distance=50, take_profit_distance=100):
order_data = {
"order": {
"instrument": instrument,
"units": str(units),
"type": "MARKET",
"positionFill": "DEFAULT",
"stopLossOnFill": {"distance": str(stop_distance)},
"takeProfitOnFill": {"distance": str(take_profit_distance)}
}
}
r = orders.OrderCreate(self.account_id, data=order_data)
self.client.request(r)
return r.response
def close_position(self, instrument, direction="long"):
close_data = {
f"{direction}Units": "ALL"
}
r = positions.PositionClose(
self.account_id,
instrument=instrument,
data=close_data
)
try:
self.client.request(r)
return r.response
except Exception as e:
logger.warning(f"No position to close: {e}")
return None
def run(self, instrument="NAS100_USD", units=1, interval=300):
logger.info(f"Starting CFD Trading Bot for {instrument}")
logger.info(f"Account Balance: ${self.get_account_balance():.2f}")
try:
while True:
# Get market data
df = self.get_candles(instrument)
df = self.calculate_signals(df)
current_signal = df['signal'].iloc[-1]
current_price = df['close'].iloc[-1]
current_position = self.get_open_positions(instrument)
logger.info(f"Price: {current_price:.2f} | Signal: {current_signal} | Position: {current_position}")
# Trading logic
if current_signal == 1 and current_position <= 0:
# Close short if exists
if current_position < 0:
self.close_position(instrument, "short")
logger.info("Closed SHORT position")
# Open long
result = self.place_order(instrument, units)
logger.info(f"Opened LONG: {result}")
elif current_signal == -1 and current_position >= 0:
# Close long if exists
if current_position > 0:
self.close_position(instrument, "long")
logger.info("Closed LONG position")
# Open short
result = self.place_order(instrument, -units)
logger.info(f"Opened SHORT: {result}")
# Wait before next iteration
time.sleep(interval)
except KeyboardInterrupt:
logger.info("Bot stopped by user")
except Exception as e:
logger.error(f"Error: {e}")
# Run the bot
if __name__ == "__main__":
bot = CFDTradingBot(
access_token="YOUR_ACCESS_TOKEN",
account_id="YOUR_ACCOUNT_ID",
environment="practice" # Use "practice" for demo
)
bot.run(
instrument="NAS100_USD", # NASDAQ 100 CFD
units=1,
interval=300 # Check every 5 minutes
)
CFD Trading Hours
Index CFDs
| Index | Trading Hours (ET) | Notes |
|---|---|---|
| US100/NAS100 | Sun 6PM - Fri 5PM | 1hr daily break |
| US500/SPX500 | Sun 6PM - Fri 5PM | 1hr daily break |
| US30/DJ30 | Sun 6PM - Fri 5PM | 1hr daily break |
| GER40/DAX | 3AM - 5PM | Cash session |
| UK100/FTSE | 3AM - 5PM | Cash session |
Forex CFDs
- 24/5 Trading: Sunday 5PM ET to Friday 5PM ET
- Continuous trading with no daily breaks
Stock CFDs
- Follow respective exchange hours
- US stocks: 9:30 AM - 4:00 PM ET
Risk Management for CFD Trading
Position Sizing Formula
def calculate_cfd_position_size(
account_balance,
risk_percent,
stop_loss_points,
point_value
):
"""
Calculate position size for CFD trading
Args:
account_balance: Total account value
risk_percent: % of account to risk (e.g., 0.01 = 1%)
stop_loss_points: Distance to stop loss in points
point_value: Value per point per lot
Returns:
Position size in lots
"""
risk_amount = account_balance * risk_percent
position_size = risk_amount / (stop_loss_points * point_value)
return round(position_size, 2)
# Example: $10,000 account, 1% risk, 50 point SL, $1/point
position = calculate_cfd_position_size(10000, 0.01, 50, 1)
print(f"Position size: {position} lots") # Output: 2.0 lots
Risk Management Rules
- Never risk more than 1-2% per trade
- Always use stop losses - automate them
- Account for overnight swap fees in your calculations
- Be aware of leverage - it amplifies losses too
- Start with demo accounts - test thoroughly first
- Monitor margin levels - avoid margin calls
Conclusion
CFD trading offers a flexible way to access global markets with leverage, but requires careful risk management. With Python automation, you can:
- Build custom trading strategies using technical indicators
- Automate order execution with proper risk controls
- Backtest strategies before going live
- Monitor multiple markets simultaneously
Recommended Learning Path
- Start with a demo account on OANDA or IG Markets
- Learn the API using the examples above
- Build simple strategies (moving averages, RSI)
- Backtest thoroughly before live trading
- Start small with minimal position sizes
- Scale gradually as you gain confidence
Resources
- IG Markets API Documentation
- OANDA v20 API Documentation
- trading-ig Python Library
- oandapyV20 Documentation
- MetaTrader 5 Python Integration
- XTB API GitHub
Disclaimer: CFDs are complex instruments and come with a high risk of losing money rapidly due to leverage. 67-80% of retail investor accounts lose money when trading CFDs. You should consider whether you understand how CFDs work and whether you can afford to take the high risk of losing your money. This article is for educational purposes only and does not constitute financial advice.
Top comments (0)