Overall Statistics
Total Trades
994
Average Win
0.96%
Average Loss
-0.39%
Compounding Annual Return
28.910%
Drawdown
5.000%
Expectancy
1.337
Net Profit
1207.297%
Sharpe Ratio
2.84
Probabilistic Sharpe Ratio
100%
Loss Rate
32%
Win Rate
68%
Profit-Loss Ratio
2.46
Alpha
0.207
Beta
-0.113
Annual Standard Deviation
0.068
Annual Variance
0.005
Information Ratio
0.492
Tracking Error
0.164
Treynor Ratio
-1.725
Total Fees
$92939.00
Estimated Strategy Capacity
$44000.00
Lowest Capacity Asset
SPY 31UH85O4JTXEU|SPY R735QTJ8XC9X
from ftplib import FTP
import pandas as pd


host = 'ftpupload.net'
user = 'epiz_31014263'
passw = 'vmHOzdStGxo'


def format_df(df):
    df = pd.read_csv(df)
    df = df.drop('Unnamed: 0', axis=1, errors='ignore')
    df = df.drop_duplicates()
    df['date'] = pd.to_datetime(df['date'])
    df['evening_high_time'] = pd.to_datetime(df['evening_high_time'])
    df['evening_low_time'] = pd.to_datetime(df['evening_low_time'])
    df['morning_high_time'] = pd.to_datetime(df['morning_high_time'])
    df['morning_low_time'] = pd.to_datetime(df['morning_low_time'])
    df = df.set_index('date')
    df = df.sort_values(by=['date'])
    return df


if __name__ == '__main__':
    format_df()

def compare_dates(dt1, dt2, only_days=False, only_hours=False):

    if only_days:
        if dt1.year == dt2.year and dt1.month == dt2.month and dt1.day == dt2.day:
            return True

    if dt1 == dt2:
        return True


def sec_diff(dt1, dt2):

    if dt1 >= dt2:
        return (dt1 - dt2).seconds
    else:
        return -(dt2 - dt1).seconds
from AlgorithmImports import *
from datetime import datetime, timedelta
import pandas as pd
from io import StringIO
import traceback
import requests

from get_csv import format_df
from help_functions import *


class FatOrangeSnake(QCAlgorithm):
    """
    Setup:
        0 - buy call at low, sell at high
        1 - buy put at high, sell at low
    OTM:
        How many perc contract out of money
    max_DTE:
        Max days before expiration to buy call/put
    """

    def Initialize(self):
        self.SetStartDate(2012, 1, 1)  # Set Start Date
        #self.SetEndDate(2014, 1, 1)
        self.start_cash = 1000000
        self.setup = 1
        self.OTM = -0.005  # How many perc contract out of money
        self.max_logs = 6  # not used now
        self.max_DTE = 1  # Max days before expiration to buy call/put
        self.perc_buy = 0.01  # how many percent from portfolio to buy with
        self.exponential_gain = 1
        self.logging = 0  # if 1, logs data, be careful, you only have 3MB per day

        self.SetCash(self.start_cash)  # Set Strategy Cash
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
        self.equity = self.AddEquity('SPY', Resolution.Minute)
        self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.symbol = self.equity.Symbol
        self.contract = ''
        self.contracts_subscribed = {}  # option contract
        self.underlying_price = 0
        self.SetBenchmark("SPY")
        self.contracts_added = set()
        self.strikes = ''
        self.all_options = 0
        # if self.logging == 0:
        #     self.Log = lambda x: None

        df = self.Download('https://www.dropbox.com/s/rttn6rq5wmqzt2y/extremes_new.csv?dl=1')
        self.df = format_df(StringIO(df))  # downloaded data of max/min daily prices

    def is_action_necessary(self):
        """
        Find out if we need to prepare or buy or liquidate option.
        If option within 30 minutes of buying, prepare the option
        If time of buying/selling, send info
        """

        if self.Time.weekday() == 4:
            df_row = self.df[(self.df.index.year == self.Time.year) &
                             (self.df.index.month == self.Time.month) &
                             (self.df.index.day == self.Time.day)]

            if not df_row.empty:
                morning_low = 0  # 0 - not, 1 - within 30 mins, 2 - exact
                morning_high = 0
                evening_low = 0
                evening_high = 0

                sec1 = sec_diff(df_row.iloc[0]['morning_low_time'], self.Time)
                sec2 = sec_diff(df_row.iloc[0]['morning_high_time'], self.Time)
                sec3 = sec_diff(df_row.iloc[0]['evening_low_time'], self.Time)
                sec4 = sec_diff(df_row.iloc[0]['evening_high_time'], self.Time)

                num = 60

                if 0 <= sec1 <= num:
                    morning_low = 2 if sec1 == 0 else 1
                if 0 <= sec2 <= num:
                    morning_high = 2 if sec2 == 0 else 1
                if 0 <= sec3 <= num:
                    evening_low = 2 if sec3 == 0 else 1
                if 0 <= sec4 <= num:
                    evening_high = 2 if sec4 == 0 else 1

                return morning_low, morning_high, evening_low, evening_high

    def prepare_option(self, data, is_call):
        """ Finds and stubscribes to option which will later be bought """

        if not self.contracts_subscribed:
            self.contracts_subscribed = self.options_filter(data, is_call)

    def sort_contracts(self, contracts, is_call):
        # sort contracts by expiration date (2nd priority)
        contracts = sorted(contracts, key=lambda x: (x.ID.Date - self.Time).days)
        # sort contract by closest strike price to underlying (1st priority)
        revers = False if is_call else True
        return sorted(contracts, key=lambda x: x.ID.StrikePrice, reverse=revers)

    def buy_option(self, data, is_call):
        """
        Go through all subscribed options (subscribed during option filtering)
        Select first option out of money by percentage given in input
        Buy the option and log
        Remove all options from subscribing that are not the bought option (we still need to close it)
        """

        if not self.contracts_subscribed:
            if self.logging: self.Log('Contract not prepared')
        elif not self.Portfolio.Invested:
            contracts = [self.contracts_subscribed[k] for k in self.contracts_subscribed]
            contracts = self.filter_contracts(contracts, data, is_call)
            self.contract = self.sort_contracts(contracts, is_call)[0]

            if data.ContainsKey(self.contract):
                sec = self.Securities[self.contract.Value]

                if self.logging: self.Log(f'SPY: {self.underlying_price} Strike: {self.contract.ID.StrikePrice} '
                                          f'ASK: {sec.AskPrice} SIZE: {sec.AskSize} '
                                          f'BID: {sec.BidPrice} SIZE: {sec.BidSize} ')

                if sec.AskPrice > 0:
                    cash = self.Portfolio.Cash if self.exponential_gain else self.start_cash
                    quantity = round((cash * self.perc_buy) / (sec.AskPrice * 100))
                    self.all_options += quantity
                    self.Buy(self.contract, quantity)
                    # self.Buy(self.symbol, 1)
            else:
                if self.logging: self.Log('Contract not found')

        else:
            if self.logging: self.Log('Portfolio already invested')

        # after buying option, we no longer need to subscribe to all options data, only track the bought option
        if self.contracts_subscribed:
            for k in self.contracts_subscribed:
                if self.contracts_subscribed[k] in self.contracts_added and self.contract.ID != \
                        self.contracts_subscribed[k].ID:
                    self.RemoveOptionContract(self.contracts_subscribed[k])
            self.contracts_subscribed = {}

    def liquidate_option(self):
        """
        Sell option, log what happened
        Remove option from subscribed data, we're done with it
        """
        if self.contract:
            sec = self.Securities[self.contract.Value]

            if self.logging: self.Log(f'SPY: {self.underlying_price}'
                                      f'ASK: {sec.AskPrice} SIZE: {sec.AskSize} '
                                      f'BID: {sec.AskPrice} SIZE: {sec.AskSize}')
            self.Liquidate(self.symbol)
            self.Liquidate(self.contract)
            if self.logging: self.Log('Contract closed')
            self.RemoveOptionContract(self.contract)
            self.contract = ''

    def filter_contracts(self, contracts, data, is_call):
        contracts = [i for i in contracts if self.max_DTE >= (i.ID.Date - data.Time).days]

        if is_call:
            # filter only calls/puts
            contracts = [i for i in contracts if i.ID.OptionRight == OptionRight.Call]
            # filter out all contracts that are not out of the money by percentage given in input
            contracts = [i for i in contracts if
                         i.ID.StrikePrice >= round(self.underlying_price * (1+self.OTM))]
            # strikes = set([i.ID.StrikePrice for i in contracts])
        else:
            contracts = [i for i in contracts if i.ID.OptionRight == OptionRight.Put]
            # strikes = set([i.ID.StrikePrice for i in contracts])
            contracts = [i for i in contracts if
                         self.underlying_price >= round(i.ID.StrikePrice * (1+self.OTM))]

        return contracts

    def options_filter(self, data, is_call):
        """
        Go through options available, and filter the right one to buy
        Subscribe to all options with strike prices, to later pick correctly during buying
        """

        contracts = self.OptionChainProvider.GetOptionContractList(self.symbol, data.Time)
        # filter contracts that have longer expiration day than given
        contracts = self.filter_contracts(contracts, data, is_call)

        if contracts:
            contracts = self.sort_contracts(contracts, is_call)[:4]

            contracts = {i.ID.StrikePrice: i for i in contracts}
            # self.Log(f'strikes: {[i for i in contracts]}')

            for k in contracts:
                if contracts[k] not in self.contracts_added:
                    self.contracts_added.add(contracts[k])
                    self.AddOptionContract(contracts[k], Resolution.Minute)

            return contracts
        else:
            return ''

    def OnData(self, data):
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
            Arguments:
                data: Slice object keyed by symbol containing the stock data
        '''

        is_necessary = self.is_action_necessary()
        self.underlying_price = self.Securities[self.symbol].Price

        if is_necessary:

            morning_low, morning_high, evening_low, evening_high = is_necessary

            if self.setup == 0:
                if morning_low == 1:
                    self.prepare_option(data, is_call=1)
                if morning_low == 2:
                    self.buy_option(data, is_call=1)
                    # self.Buy('SPY', 1)

                if evening_high == 2:
                    self.liquidate_option()
            if self.setup == 1:
                if morning_high == 1:
                    self.prepare_option(data, is_call=0)
                if morning_high == 2:
                    self.buy_option(data, is_call=0)
                    #self.Buy('SPY', -1)

                if evening_low == 2:
                    self.liquidate_option()
                    # self.Liquidate('SPY')

        #if self.Portfolio['SPY'].Invested:
        #    self.Liquidate('SPY')

    def OnOrderEvent(self, orderEvent):
        if self.logging: self.Log(str(orderEvent))

    def OnEndOfAlgorithm(self):
        if self.logging: self.Log(f'all options: {self.all_options} fees: {self.Portfolio.TotalFees / self.all_options}$ per option')