Automated Trading - Experiment With Butterflies

07 Nov 2013

Simple Demo Strategy

The following strategy is not a real one but something just to demonstrate how AlgoEye is used to automate trading and run backtests. The strategy is simple:

  • If there's a strike at least 20 points away from underlying price where we can put a long butterfly then trade it.
  • If a strike is below underlying price then trade put butterfly otherwise trade call butterfly.
  • Stop trading 5 days before expiry but hold all positions until expiration.
  • Save on transaction costs by providing liquidity to the market.

Backtesting

We'll run this strategy on Korean Index options against 2 year's worth of tick data. AlgoEye is configured to use MySQL database for reference data. The database has information on 24 expiries and all its options. AlgoEye is running an exchange simulator which reads historical tick data and simulates orders with round-trip latency of 300 milliseconds. The configuration file also specifies tick table rules and broker commission.

Results

Results make sense. The strategy bleeds day to day and looses money with every butterfly that finishes out of the money. It cashes in once in a while when the underlying is near the body strike of a butterfly on expiry. In the long run this strategy will be close to break even. Obviously the strategy needs some more attention before considered acceptable as its risk profile is far from being perfect. It's not something you would want to trade but in the end of the day this wasn't the goal of this post.

When a strategy is ready to go live the only change is to replace simulator configuration with a broker adapter.

Here is theoretical and mark-to-market PnL of the strategy.

fly_experiment_pnl

Below is the underlying price chart with blue lines showing where the body of a call butterfly is and red lines showing the body of a put butterfly. Sometimes there was no strike outside 20 point range as KOSPI options are issued only for strikes inside normal trading range. There are usually no strikes above 280 so when the price rises to 270 the 290 strike where we would put a long call butterfly does not exist. This explanes the breaks in blue and read lines.

fly_experiment_chart

Source Code

So, here is the full source code of the strategy:

public class SimpleFly extends Strategy
{
    @Attribute  int volume = 1;
    @Attribute  double strikeDistance = 20.0;
    @Attribute  double minInitialDte = 5;

    public SimpleFly(ITrader trader)
    {
        super("Fly", trader);
    }

    // check if there's no position for this option yet and that there's a two-sided market
    // we don't want to quote a butterfly if there's no bid
    boolean isFreeOption(IOption option)
    {
        return option != null && portfolio.getInstrumentVolume(option) == 0
                && option.getPriceDepth().isTwoSided();
    }

    // given ATM put or call option find an OTM option that is at least
    // strikeDistance away from current underlying price
    IOption getFlyBodyStrike(IOption atm)
    {
        IOption body = atm.getLowerDeltaOption();
        while (body != null
            && Math.abs(atm.getStrikePrice() - body.getStrikePrice()) < strikeDistance)
        {
            body = body.getLowerDeltaOption();
        }
        return body;
    }

    // trade butterfly given the body option and using adjacent options as wings,
    // assumes equidistant strikes
    void tradeFly(IOption body)
    {
        // ComplexStrategyTrader manages quotes/orders in the market until fully traded
        // Partial fills at the body are hedged with wings to maintain vega-neutral portfolio
        ComplexStrategyTrader trader = new ComplexStrategyTrader(
            getNextSubstrategyId(), this, this);
        trader.addHedgeLeg(body.getHigherDeltaOption(),
            ComplexStrategyTrader.VolumeStrategy.Vega, volume, 0.01);
        trader.addQuoteLeg(body, -2 * volume, 0.002, 0.01, 0.004);
        trader.addHedgeLeg(body.getLowerDeltaOption(),
            ComplexStrategyTrader.VolumeStrategy.Vega, volume, 0.01);
        trader.start();
    }

    // check if there are any suitable unoccupied strikes and initiates trading of a butterfly
    void tryFly(IExpiry expiry, IOption.OptionClass oc)
    {
        IStrike atm = expiry.getATMStrike();
        if (atm != null)
        {
            IOption body = getFlyBodyStrike(atm.getOption(oc));
            if (body != null && isFreeOption(body)
                    && isFreeOption(body.getHigherDeltaOption())
                    && isFreeOption(body.getLowerDeltaOption()))
            {
                tradeFly(body);
            }
        }
    }

    // called on every change in the market
    // either trade or quote update for any option in the chain
    @Override
    public void OnTick()
    {
        IExpiry expiry = getExpiry(minInitialDte);
        tryFly(expiry, IOption.OptionClass.CALL);
        tryFly(expiry, IOption.OptionClass.PUT);
    }
}

Configuration

<config>
        <database
                driver='com.mysql.jdbc.Driver'
                url='jdbc:mysql://localhost/algoeye'
                user='root'
                password='**********' />

        <simulator
                start='2012-01-01'
                end='2013-10-10'
                path='/algoeye/K200/data'
                latency='300'/>

        <underlying name='K200' exchange='KSE' currency='KRW'>
                <base>
                        <percentageCommission percent='0.00004' minimum='0'/>
                        <midPrice/>
                        <priceFormat price='0.00' theo='0.000' greeks='0.000'/>
                        <tickTable>
                                <rule price='0.01' size='0.01'/>
                        </tickTable>
                </base>
                <options>
                        <percentageCommission percent='0.002' minimum='1000'/>
                        <midPrice/>
                        <priceFormat price='0.00' theo='0.000' greeks='0.000'/>
                        <tickTable>
                                <rule price='0.01' size='0.01'/>
                                <rule price='3.00' size='0.05'/>
                        </tickTable>
                </options>
                <expiries maxN='2' minT='0' maxT='180'/>
                <portfolioLimits maxTotalPosition='1000' maxPositionPerStrike='10'/>
        </underlying>
        <strategy type='strategy.SimpleFly'
                volume='1'
                strikeDistance='20'
                minInitialDte='5'
                />
</config>

Happy trading!


comments powered by Disqus