michaelchu / optopsy

A nimble options backtesting library for Python

Home Page:https://github.com/michaelchu/optopsy/wiki

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Improvement idea

erv4gen opened this issue · comments

Hi Michael,

I've been testing your module for some time and came up with an improvement idea. Most of my actual strategies are tied to additional external data like VIX, stocks' earnings, etc.

Adding the ability to consider exogenous variables to the strategy will dramatically increase the efficiency and the value of your system.

It could be done by allowing custom columns in the initial dataframe:

 Column Name        Status
 ---------------------------
 option_symbol      Optional
 underlying_symbol  Required
  .....
 vega               Required
 rho                Optional 
 <custom column 1>  Optional 
 <custom column 2>  Optional
  .....
 <custom column n>  Optional

And initial filters to be like for example:

filters = {
        "start_date": datetime(2018, 1, 1),
        "end_date": datetime(2018, 12, 31),
        "entry_dte": (40, 47, 50),
        "leg1_delta": 0.30,
        "contract_size": 1,
        "expr_type": "SPXW",
		"custom column 1" : (20, 30, 40)
		"custom column 2" :  10
		.....
		"custom column n" :  <some conditions>
    }

If it could be implemented, then your module could be used to find patterns not only in historical option prices but also across a variety of financial instruments.

Hi Vladimir,

Thanks for you suggestion! I've actually been thinking about the same problem myself and have decided to implement it as a core feature in the upcoming v2 update that I am working on.

I plan on making v2 event driven, that is, events can be generated from stocks, sentiment and other external data.

  • It will be extensible so that you can implement your own external data intake handlers, filtering logic to generate an actionable event that the strategy can act on. A strategy may look like the following:
class SampleStrategy(op.Strategy):
    """
    This is a sample strategy to simulate a portolio of SPX call spreads
    hedged by VXX calls.
    """

    def on_init(self):
        """
        Define any initialization logic here
        """
        self.set_cash(10000.0)
        self.set_brokerage(op.Brokerage.THINKORSWIM)

        self.start_date = datetime(2018, 1, 1)
        self.end_date = datetime(2018, 12, 31)

        self.calendar.every(op.DayOfWeek.Thursday, self.open_trade)

    def open_trade(self):
        """
        Rules to open a new trade
        """
        self.buy(
            (
                self.get_instrument("SPX")
                .verticals.call_spread()
                .select_by(scale=False)
                .days_to_expiration(min_days=40, max_days=47)
                .abs_deltas(leg1=0.30, leg2=0.50)
            )
        )

        self.buy(
            (
                self.get_instrument("VXX")
                .singles.calls()
                .select_by(scale=False)
                .days_to_expiration(min_days=40, max_days=47)
                .abs_deltas(leg1=0.20)
            )
        )

    def on_next(self, event):
        """
        Define your trading logic here, each new event
        will be attached to the event variable.
        Arguments:
            event: Dict object containing metadata for the event
                       Bar event example: 
                       {
                           type: Event.BAR,
                           data: {
                                     "SPX": [...],
                                     "VXX": [...]
                                    }
                       }
        """
        if event.type == Event.BAR:
            # do something related to price action here
        elif event.type == Event.SENTIMENT:
            # do something related to sentiment signals here
        elif event.type == Event.SIGNAL:
            # do something related to indicator signals here


def run_strategy():

    # create our inport dataframes
    spx = pd.read_csv("spx.csv")
    vxx = pd.read_csv("vxx.csv")
    earnings = pd.read_csv("earnings_signals.csv")
    macd_signals = pd.read_csv("macd_signals.csv")

    # create a new backtest instance
    backtest = op.Backtest()

    # load the data from our dataframes
    backtest.load_data(SPX=spx, VXX=vxx)
    backtest.load_sentiment(earnings=earnings)
    backtest.load_signals(macd=macd_signals)

    backtest.add_strategy(SampleStrategy)
    backtest.run().plot()


if __name__ == "__main__":
    run_strategy()

Feel free to check out the WIP strategy-refactor branch, any feedback/suggestions welcomed!

Yes, that exactly what I was thinking about! Thanks, I'll check out it out!

Since you work on the v2 version, this follow-up could be non-relevant, but still worse to mentions.
I found logical incorrectness in the results calculations in #23 commit:
After exploring the math under the "short put" strategy, it seems that the algorithm is to sell a put (open position) at the bid price, and closing (buying position back) at the ask. This approach is a bit too "conservative" considering that you almost always can establish the position at the (bid+ask)/2 for the major stocks.

In fact, all positions for "short put" and "short call" was terminated by buying the option back from the market, whereas it should be terminated worthless with $0.00 cost.

Summarizing the above, here's two additional suggestions:

  1. Add the input parameter that controls how the position will be open: either through "market" order or "in the spread" ( (bid+ask)/2 )
  2. For call/put short sell terminate the position with $0.00 cost and
    not the market price, if the option expires worthless (assuming exit_dte is set to 0)

Thanks!

You are correct, I will address this issue in the update. Thanks for catching that!