Skip to content

Add eToro Brokerage Integration - #9886

Open
Romazes wants to merge 16 commits into
QuantConnect:masterfrom
Romazes:feature-brokerage-etoro
Open

Romazes wants to merge 16 commits into
QuantConnect:masterfrom
Romazes:feature-brokerage-etoro

Conversation

@Romazes

@Romazes Romazes commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Registers the eToro brokerage in Lean core for the plugin Lean.Brokerages.Etoro.

  • BrokerageName.Etoro, wired in BrokerageModel.Create and the reverse lookup.

  • EtoroBrokerageModel: equity (US stocks and ETFs), crypto and forex; market orders, and the stop loss and the take profit of a position as contingent exit orders.

    • Defaults to AccountType.Cash, so every security starts at leverage 1. An algorithm sets more with SetLeverage, and the plugin sends that leverage with the order.
    • The stop loss and the take profit are Lean orders the entry triggers (Add contingent orders: OCO, OTO, OUO and brackets #9828): a stop market order is the stop loss, a limit order the take profit, both together a bracket where one cancels the other. eToro keeps them on the position and closes it whole, so CanSubmitContingentOrder accepts a market entry that opens a position, exits on the opposite side for the quantity of the entry, and three orders only as a bracket. The exits skip the cross-zero check: they close the position the entry opens, whatever is held now.
    • A short sale or a position with leverage above 1 needs the stop loss. eToro answers 400 without it: "StopLossRate must be provided when Leverage is greater than 1 or for SellShort transactions". The messages tell the algorithm to attach a stop market order.
    • CanUpdateOrder is true for a price change of an exit order, eToro moves the stop loss or the take profit of the position. A quantity change or any other update is refused: eToro has no order update.
    • A crypto stop loss has to be at least 10% away from the price, at submission and at update. eToro keeps every crypto stop loss at least 10% of the margin away and moves a closer one there without an error: live, a BTC stop asked 5% under the fill came back at exactly 10%. The model refuses it first, with "The stop loss of BTCUSD at eToro has to be at least 10% away from the price ...". Lean checks every order of a set before placing any, so refusing the stop refuses the entry with it.
    • A market sell beyond the long holdings is refused as a cross-zero order; eToro closes positions by units.

    What eToro's eligibility endpoint says about the stop loss and the take profit, as percent of the margin (7934 instruments of the demo account, 2026-11-03; at leverage 1 the margin is the price):

    Type Config, leverages min SL % max SL % min TP % max TP %
    Crypto, 417 tradable real long, 1 10 on all 100 0 1000, 10000 on 143 incl. BTC
    ETF, 592 cfd long 1 0, SCHD 10 100 0 1000
    ETF cfd long 2,5 and short 1,2,5 0, SCHD 10 50 0 1000
    Forex, 108 cfd long 1 0, EURUSD 1 100, 95 on 19 pairs 0 1000, 500 on 28 pairs
    Forex cfd long 2 to 30 and short 1 to 30 0, EURUSD 1 50 0 1000 or 500
    Stocks, 6135 real long 1 0 on 6039; 20 on 54; 10 on 5 100 0 1000, 500 on 3
    Stocks cfd long 2,5 and short 1,2,5 0 on most; 20 on 46; 10 on 6 50, 40 on 11 0 1000

    The take profit has no minimum anywhere. The stop loss minimum is flat for crypto, so the model carries it; it is 0 for the other types except EURUSD and about 60 volatile stocks, which only the eligibility of each instrument tells.

    In an algorithm:

    // a long position with a take profit and a stop loss, a bracket: eToro keeps both on the position
    var tickets = BracketOrder(symbol, 5, takeProfitPrice: price * 1.1m, stopLossPrice: price * 0.9m);
    
    // a short sale needs its stop loss: a one triggers other with a stop market order
    var entry = OrderFactory.MarketOrder(symbol, -5);
    var stopLoss = OrderFactory.StopMarketOrder(symbol, 5, price * 1.1m);
    var shortTickets = OneTriggersOtherOrder(entry, [stopLoss]);
    
    // once the entry filled: move the stop loss of the position, or take the take profit off it
    tickets[2].Update(new UpdateOrderFields { StopPrice = price * 0.95m });
    tickets[1].Cancel();
  • OrderContingency.AbsoluteQuantities: the distinct absolute quantities of the orders of a set, next to Symbols, Directions and OrderTypes, so a brokerage model can check the quantities of a set from one order.

  • EtoroOrderProperties: the order properties type of the brokerage, with no field of its own.

  • EtoroFeeModel: crypto pays 1% of the trade value on every buy and sell (eToro US fees page; a live 85.75 USD BTC buy paid 0.86 USD). Stocks, ETFs and forex pay nothing (eToro US: "We charge zero commissions when you buy or sell a stock"; live EURUSD fills paid no fee).

  • Messages.EtoroBrokerageModel: the short sale and the leverage texts.

  • Launcher/config.json: etoro-api-url, etoro-api-key, etoro-user-key, etoro-demo-environment and the live-etoro environment.

  • BrokerageTests.PlaceOrderWaitForStatus: on a timeout, the failure names each order's expected and actual status and its Invalid message.

Related PR(s)

#9828 (contingent orders). Plugin: https://github.com/QuantConnect/Lean.Brokerages.Etoro/pull/5 and https://github.com/QuantConnect/Lean.Brokerages.Etoro/pull/9

Related Issue

N/A

Motivation and Context

The eToro plugin needs its brokerage name, model, order properties and fee model in Lean core, so Lean routes orders to it, refuses what eToro cannot execute before the order leaves the algorithm, and prices fees in backtests. The stop loss eToro requires came as a price in the order properties before, so Lean never knew it as an order and the close eToro placed at the stop reached Lean as an order placed outside Lean. As a contingent exit order Lean knows it, the plugin fills it when eToro closes the position, and the algorithm can move or remove it.

Requires Documentation Change

Yes: an eToro brokerage page (supported assets and order types, the demo environment flag, the cash account default and SetLeverage, the stop loss as a stop market exit order for a short sale and for leverage above 1, the take profit as a limit exit order, a bracket, the price update of an exit).

How Has This Been Tested?

  • EtoroBrokerageModelTests (51 cases): supported and unsupported security and order types, the stop loss for a short sale and for leverage above 1 as a contingent stop market order, 13 shapes of a contingent set (stop loss, take profit, bracket, short, leveraged, exits only, same side, half quantity, limit entry, market exit, two stop losses), exits beyond the holdings, a crypto stop loss at 5%, 10% and 20% from the price at submission and a move to 5% and 20% at update, a cross-zero sell, CanUpdateOrder for an exit price and for a quantity, GetFeeModel, the leverage by account type and security type, the cash default.
  • EtoroFeeModelTests (6 cases): zero for equity, 1% of the trade value for crypto, including the live 85.75 USD BTC buy.
  • Live through the plugin (plugin PR Fix for indicators to support custom data based on TradeBar class #9, demo account): entries with a stop loss, a take profit or a bracket opened with the rates on the eToro position, long and short; eToro's close at the stop loss filled the Lean stop market order (AAPL long and short) and its close at the take profit the Lean limit order (AAPL, BTC); price updates moved the rates on the position and a wrong rate was refused with the position keeping its rate; removals took the take profit and the stop loss off a long position, and eToro kept the stop loss of a short.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Refactor (non-breaking change which improves implementation)
  • Performance (non-breaking change which improves performance. Please add associated performance test and results)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Non-functional change (xml comments/documentation/etc)

Checklist:

  • My code follows the code style of this project.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • My branch follows the naming convention bug-<issue#>-<description> or feature-<issue#>-<description>

@Romazes Romazes self-assigned this Oct 9, 2026
@Romazes Romazes added feature New functionality or support for a new security; not a bug report. Brokerage labels Oct 9, 2026
Comment thread Common/Orders/Fees/EtoroFeeModel.cs Resolved
@Romazes
Romazes force-pushed the feature-brokerage-etoro branch from 9e93415 to f7312a1 Compare October 19, 2026 16:03
- BrokerageName.Etoro as the last enum member
- EtoroOrderProperties with StopLossRate, the absolute price at which eToro closes a short position
- crypto pays 1% of the trade value on every buy and sell, from the eToro US fees page
- stocks and ETFs are commission free on the eToro US fees page, so the equity fee is zero
- Equity and Crypto with Market orders only, no contingent orders
- a short sale needs a positive StopLossRate in EtoroOrderProperties
- a market sell beyond the long holdings is refused as a cross-zero order
- CanUpdateOrder returns false, eToro has no order update
- Messages.EtoroBrokerageModel for the two model texts
- BrokerageName.Etoro wired in the brokerage model factory
- etoro-api-url, etoro-api-key, etoro-user-key and etoro-demo-environment
- live-etoro environment with EtoroBrokerage as brokerage and data queue handler
- CanSubmitOrder: supported and unsupported security and order types, short sale stop loss rate, cross-zero sell
- CanUpdateOrder returns false and GetFeeModel returns EtoroFeeModel
- EtoroFeeModel: zero for equity, 1% of the trade value for crypto including the live 85.75 USD BTC buy
- CanUpdateOrder answers Messages.DefaultBrokerageModel.OrderUpdateNotSupported
- the custom update message and the short-sale comment are removed
PlaceOrderWaitForStatus keeps the Invalid order events and, when the expected status does not come within the timeout, fails with each order's expected and actual status plus its Invalid message. The status handler is unhooked in a finally block, so every failure path removes it.
- why: the etoro plugin advertises Forex (etoro.json assets, symbol
  mapper on Market.Oanda) and streams and downloads it, but this model
  refused every Forex order as an unsupported security type, so a Forex
  algorithm could read prices and never trade
- Forex joins Equity and Crypto in the supported security types; the
  market order, short sale stop loss and cross-zero rules apply the same
- the fee stays zero for Forex: eToro charges currencies the spread only,
  no commission
- the Forex test case moves from the unsupported list to the market
  order accepted list
- why: eToro opens every position at leverage 1, since no leverage is
  sent with an order and the account eligibility lists only "real long
  [1]" for every instrument; the model inherited the margin defaults
  (equity 2, forex 50), so Lean counted buying power eToro does not give
- GetLeverage returns 1, as the Coinbase model does
- a test checks equity, crypto and forex
- why: the eToro order now carries the leverage of the security; a
  margin account gives equities 2 and forex 50 by default, so a plain
  order would open a leveraged position, and eToro refuses a leverage
  above 1 without a stop loss
- the model defaults to AccountType.Cash and uses the base GetLeverage,
  1 for a cash account; an algorithm sets more with SetLeverage
- CanSubmitOrder refuses an opening order at a leverage above 1 without
  a StopLossRate, for a buy and for a short sale
- tests: the leverage by account type and security type, the cash
  default, and the stop loss rule
@Romazes
Romazes force-pushed the feature-brokerage-etoro branch from b945d01 to 2e3430b Compare November 3, 2026 21:11
- why: the fee was 1% of the trade value but always labelled USD, while
  the trade value of a pair is in its quote currency, so a BTCEUR trade
  got its EUR fee booked as USD
- the fee uses security.QuoteCurrency.Symbol, as the Coinbase, Binance,
  Bitfinex and Bybit fee models do; Lean converts it to the account
  currency
- a test checks BTCUSD (fee in USD) and BTCEUR (fee in EUR)
- a brokerage model validates one order at a time and the set only told it the symbols, directions and order types of the other members, so a model could not refuse an exit order sized differently from its entry, which a broker that closes the whole position at the stop loss or the take profit cannot take
- the set of distinct absolute quantities is built lazily like the other views, so a set is valid for such a broker when it has one element
- the stop loss eToro requires on a short sale or a leveraged position came as a price in the eToro order properties, so Lean never knew it as an order and could not see the close eToro places when it is hit; the stop loss is now a stop market order the entry triggers, and a take profit a limit order, a bracket when one cancels the other
- eToro keeps both on the position and closes it whole, so the set has to be a market entry that opens a position, with exits on the opposite side for the entry quantity, and three orders only as a bracket; the exits skip the cross zero check since they close the position the entry opens, whatever is held now
- an exit order can change its price, eToro moves the stop loss or the take profit of the position; a quantity change or any other update stays refused
…rice

- eToro keeps the stop loss of every crypto position at least 10% of the margin away and moves a closer one there without an error, so an algorithm that set a tighter stop ran with twice the risk it thought it had and only learned it from the fill at the stop
- the model refuses a crypto stop market exit, or a move of one, closer than 10% to the security price, so the set is refused before it leaves the algorithm with a message that names the rule; stocks, ETFs and forex have no such minimum but a few instruments, which the model cannot know without the eligibility of each
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Brokerage feature New functionality or support for a new security; not a bug report.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants