ETrade Auto Trading With Astronomer Signals
Image credit: NASA, ESA and Orsola De Marco (Macquarie University)

ETrade Auto Trading With Astronomer Signals

By The astronomer Team

ETrade Auto Trading With Astronomer Signals

The Signal Stream Client hands you a fully specified option contract the moment a signal is created. This walkthrough turns that into a small, long-lived Node service that previews and places an ETrade option order for each signal. It is also designed to work with an eligible ETrade IRA account.

ETrade supports options trading in eligible Roth and Traditional IRAs, subject to account approval and strategy restrictions. That makes it a useful broker for a signal-driven strategy you want to run in a taxable or retirement account. Roth IRA earnings may qualify for tax-free withdrawal, while Traditional IRA investments generally grow tax-deferred; consult a tax professional about your circumstances.

The example uses ETrade's sandbox by default and runs as a dry run—signals are logged but no broker requests are made—until every required ETrade credential is configured.

Why ETrade For IRA Accounts

Many automated-trading examples assume a taxable brokerage account. ETrade also lets approved customers trade supported options strategies in an IRA:

  • Roth IRA — eligible options strategies can run inside a Roth IRA.
  • Traditional IRA — the same client can target a Traditional IRA.
  • One integration, any eligible account — ETrade addresses accounts with an accountIdKey, so changing the configured account key selects the account without changing the order code.

IRA rules and ETrade's options approval requirements still apply. Astronomer signals describe long calls and puts; confirm that the selected account is approved for the intended strategy before enabling live trading.

Get The Example

The full source lives on GitHub in astronomer-app/signal-stream-examples, under etrade/node. Clone it and install:

git clone https://github.com/astronomer-app/signal-stream-examples.git
cd signal-stream-examples/etrade/node
npm install
cp .env.example .env

The service uses:

ETrade offers an official downloadable Node sample, but does not publish an official Node SDK to npm. e-trade-api is a third-party typed client, so the example pins its exact version.

Configure

Start by adding your Signal Stream key and ETrade sandbox consumer credentials to .env:

# Required: your Signal Stream key.
ASTRONOMER_SIGNAL_STREAM_KEY=ast_live_...

# ETrade consumer credentials.
ETRADE_CONSUMER_KEY=
ETRADE_CONSUMER_SECRET=

# Generated daily by `npm run auth`.
ETRADE_ACCESS_TOKEN=
ETRADE_ACCESS_SECRET=

# ETrade's accountIdKey, not the display account number.
ETRADE_ACCOUNT_ID_KEY=

# Sandbox is the safe default.
ETRADE_ENVIRONMENT=sandbox

ORDER_LIMIT_SLIPPAGE=0.02
ORDER_QUANTITY=1
PORT=8080

To run without placing orders, leave all five ETrade credential variables blank. A partial ETrade configuration fails fast so that a missing token or account key cannot silently disable trading.

Authorize And Select An Account

ETrade uses an interactive OAuth 1.0 flow. After adding your consumer key and secret, run:

npm run auth

The helper requests a temporary token, prints the ETrade authorization URL, and prompts for the verification code. After authorization it prints:

  • ETRADE_ACCESS_TOKEN
  • ETRADE_ACCESS_SECRET
  • the available accounts and their accountIdKey values

Copy the access token, access secret, and selected account key into .env. Choose the key for the Roth IRA, Traditional IRA, or taxable account you intend to trade.

The temporary request token expires after five minutes. ETrade access tokens expire at midnight US Eastern and must be generated again each trading day. They also become inactive after two idle hours; the client attempts to renew an inactive token automatically when the first quote request reports an authentication error.

How It Works

The service does three things:

  1. Starts a status server on PORT with GET /health for liveness and GET /status for runtime counters.
  2. Opens the Signal Stream connection and runs a callback for every signal.
  3. Builds the ETrade option contract, fetches its quote, previews a limit order, and places it with the returned preview ID.

The listener passes the signal ID into the broker client as well as the option contract:

const listener = client.signals.listen({
  onOpen() {
    console.log('Signal Stream connected.');
  },
  async onSignal({ signal }) {
    if (!etrade) {
      console.log(`[dry run] would buy ${signal.symbol}`);
      return;
    }

    await etrade.placeOptionOrder({
      signalId: signal.id,
      underlyingSymbol: signal.symbol,
      optionType: signal.optionType,
      strikePrice: signal.strikePrice,
      expirationDate: signal.expirationDate,
      quantity,
    });
  },
  onError(error) {
    console.error('Signal Stream error:', error);
  },
});

Building The Contract

A signal includes symbol, optionType, strikePrice, and expirationDate. ETrade represents an option as a structured product rather than an OCC symbol in the order request:

function parseContract(req: OptionOrderRequest): OptionContract {
  const [year, month, day] = req.expirationDate.split('-');

  return {
    symbol: req.underlyingSymbol.toUpperCase(),
    callPut: req.optionType === 'C' ? 'CALL' : 'PUT',
    expiryYear: Number(year),
    expiryMonth: Number(month),
    expiryDay: Number(day),
    strikePrice: req.strikePrice,
  };
}

The implementation validates the ISO date and strike price before sending a broker request.

Pricing The Order

ETrade's quote endpoint identifies an option with six colon-separated fields:

underlier:year:month:day:optionType:strikePrice

For example, the SPY June 18, 2026 $746 call becomes:

SPY:2026:6:18:CALL:746

The typed client fetches the option detail set and reads the current ask:

const quote = await api.getQuotes({
  symbols: quoteSymbol,
  detailFlag: 'OPTIONS',
});

const ask = quote.Option?.ask ?? quote.All?.ask;

Option quotes are priced per share of the underlying, so a 1.20 ask normally represents $120 for a standard 100-share contract.

The service adds ORDER_LIMIT_SLIPPAGE to the ask and rounds upward to the nearest cent:

function roundUpToCent(price: number): number {
  return Math.ceil((price - Number.EPSILON) * 100) / 100;
}

const limitPrice = roundUpToCent(
  ask * (1 + limitSlippage),
);

ETrade's mandatory preview performs the final price and exchange-tick validation before the order is placed.

Previewing And Placing The Order

ETrade orders use a two-step flow: preview, then place. The preview ID must be used to place the order within three minutes.

The example hashes signal.id into a 20-character alphanumeric clientOrderId. Re-delivery of the same signal therefore produces the same broker client ID instead of a fresh random identifier.

const clientOrderId = createHash('sha256')
  .update(req.signalId)
  .digest('hex')
  .slice(0, 20);

const order = [{
  allOrNone: false,
  priceType: 'LIMIT',
  limitPrice,
  stopPrice: 0,
  orderTerm: 'GOOD_FOR_DAY',
  marketSession: 'REGULAR',
  Instrument: [{
    Product: {
      ...contract,
      securityType: 'OPTN',
    },
    orderAction: 'BUY_OPEN',
    quantityType: 'QUANTITY',
    quantity: req.quantity,
    orderedQuantity: req.quantity,
  }],
}];

const preview = await api.previewOrder({
  accountIdKey,
  orderType: 'OPTN',
  clientOrderId,
  order,
});

const previewIds = toArray(preview.PreviewIds);

const placed = await api.placeOrder({
  accountIdKey,
  orderType: 'OPTN',
  clientOrderId,
  previewIds,
  order,
});

ETrade sandbox fixtures sometimes return a single object where the API types describe an array. The example normalizes both PreviewIds and OrderIds before reading them.

Run It

npm run dev    # watch mode
# or
npm start

Probe the status server from another terminal:

curl localhost:8080/health
curl localhost:8080/status

Signals only arrive during Signal Stream hours, so outside that window the service remains connected with no activity. That is expected.

ETrade's sandbox returns fixed fixture data and can return contracts or order details different from the request. Use it to validate authorization and request shape, not actual execution behavior.

Going Live

This is an example. Before trading real money—especially inside a retirement account—add at least:

  • Durable idempotency and reconciliation — persist processed signal IDs and reconcile broker order state after reconnects or ambiguous API failures.
  • Position sizing and risk limits — derive quantity from account equity and enforce caps on open positions and daily losses.
  • IRA suitability checks — confirm that the selected account has the required options approval and that the requested strategy is permitted.
  • Quote quality guards — ETrade returns delayed data unless the account has accepted the relevant market-data agreement. Reject stale quotes and wide spreads.
  • Preview policy — inspect warnings, disclosures, commissions, and buying power instead of automatically placing every successful preview.
  • Daily OAuth operations — securely distribute fresh tokens before the trading session and alert when authorization or renewal fails.

Set:

ETRADE_ENVIRONMENT=live

only when you intend to place orders in the configured ETrade account with real money. Sandbox remains the default.

More broker and language examples live in astronomer-app/signal-stream-examples.