Overall Statistics
Total Trades
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Net Profit
0%
Sharpe Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
0
Tracking Error
0
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
#region imports
from AlgorithmImports import *
#endregion
from datetime import datetime
import pandas as pd
import numpy as np
from config import *

log_table = {}




def read_log_table(algo, table, log_file_suffix):
    # 从持久化存储中恢复日志
    import pandas as pd
    import pickle
    
    logs = algo.ObjectStore.ReadBytes(f'{table}_logs_{log_file_suffix}.pk')
    logs = pickle.loads(bytes(logs))    
    return pd.DataFrame(logs)


def between_range(range_value, value):
    return range_value[0] < value and value < range_value[1]
    
def exit_price_distance_with_strikes(row):
    strikes = row['strikes']
    price = row['underlying_price_exit']
    low_strike, high_strike = strikes[0], strikes[1]
    return [round((price-low_strike)/price, 3), round((high_strike-price)/price, 3)]
    
def profit_and_loss(row):
    try:
        price_enter = sum(row['price_enter'])
        price_exit = sum(row['price_exit'])
        gain = 1 - price_exit / price_enter
        profit = gain * row['quantity'] * price_enter * 100
        return [gain, profit]
    except:
        return [0, 0]
        
def summary_order(order_df):
    pass


def summary_iron_condor_orders(order_df):
    pass

logged = {}
def log_once(algo, msg):
    if not logged.get(msg, False):
        if algo.Time.hour > 11:
            return
        if algo.Time.minute not in [15, 31, 45]:
            return
        last_msg = f'{algo.Time} {msg}'
        algo.Log(last_msg)
        logged[msg] = True
#region imports
from AlgorithmImports import *
#endregion

log_file_suffix = ''
log_to_disk_open = True

underlyings_stocks = [
    # come go
    # 'QQQ',
    'TSLA',
    # 'NVDA',
    # 'AAPL',
]


option_symbols = []

option_symbol_data_context = {}

max_hold_margin = 25000

default_before_market_liquidate = 5

stop_loss_pct = 0.10

default_risk_reward_ratio = 1

order_expire_time = 15

default_order_quantity = 10
from AlgorithmImports import *

from collections import deque
import statistics
import math

class STDVolatility(PythonIndicator):
    def __init__(self):
        self.roc_window = deque(maxlen=30)

        self.volatility_window = deque(maxlen=252)
        self.last_price = None
        self.Value = 0
        
    def Update(self, bar):
        if self.last_price is None:
            self.last_price = bar.Price
            return False
        
        roc = (bar.Price / self.last_price) - 1
        self.roc_window.append(roc)
        self.last_price = bar.Price
        
        if len(self.roc_window) == self.roc_window.maxlen:
            volatility = statistics.stdev(self.roc_window) * math.sqrt(252)
            self.volatility_window.append(volatility)
            self.Value = volatility
            return True
        else:
            return False
        
    @property
    def roc(self):
        return self.roc_window[-1] if len(self.roc_window) > 0 else 0
    
    @property
    def volatility(self):
        return self.volatility_window[-1] if len(self.volatility_window) > 0 else 0
    
    @property
    def volatility_rank(self):
        volatility = self.volatility
        if len(self.volatility_window) < 2:
            return 0
        min_value = min(self.volatility_window)
        max_value = max(self.volatility_window)
        if max_value == min_value:
            return 1.0
        else:
            return (volatility-min_value) / (max_value-min_value)
        
    @property
    def volatility_percentile(self):
        volatility = self.volatility
        if len(self.volatility_window) < 2:
            return 0
        return len([v for v in self.volatility_window if v < volatility]) / len(self.volatility_window)
        
    
    def to_json(self):
        return {
            'volatility': self.volatility,
            'volatility_rank': self.volatility_rank,
            'volatility_percentile': self.volatility_percentile,
        }
# from QuantConnect.Algorithm.Framework.Execution import ImmediateExecutionModel
import math
from AlgorithmImports import *
from QuantConnect.Orders import *

from common import *
from itertools import *
from option_utils import *
from config import *
from datetime import *
from strategy import *
from symbol_data import *
import json
from risk_manage import *
from share import *
from share.main_utils import *

default_before_market_lidiquate = 10

class CasualFluorescentOrangeKitten(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2023, 5, 2)
        self.SetEndDate(2023, 5, 3)
        # self.SetEndDate(2023, 2, 2)

        # self.SetStartDate(2023, 2, 9)
        # self.SetEndDate(2023, 2, 10)

        # self.SetEndDate(2023, 1, 22)
        # self.SetEndDate(2023, 2, 1)
        
        self.SetCash(3 * 10000)

        self.Portfolio.MarginCallModel = MarginCallModel.Null
        # In Initialize
        self.Portfolio.SetPositions(SecurityPositionGroupModel.Null)

        # self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)

        # self.SetExecution(ImmediateExecutionModel())
        
        self.option_symbols = []
        
        self.init_universal()

        # self.AddRiskManagement(NormalTrailingStopRiskManagementModel(0.05, 0.05))
        self.AddRiskManagement(StopLossRiskManagementModel())
        self.AddRiskManagement(StopProfitRiskManagementModel())
      
        self.Settings.DataSubscriptionLimit = 200
        
        prop = InteractiveBrokersOrderProperties()
        prop.OutsideRegularTradingHours = True
        self.DefaultOrderProperties = prop
        self.DefaultOrderProperties.TimeInForce = TimeInForce.Day

        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(seconds=10)), lambda:cancel_order_if_expire(self, order_expire_time))

        self.Schedule.On(self.DateRules.EveryDay(),self.TimeRules.At(15, 55, 0), lambda: liquidate_combo_mkt_order(self))

        
    def init_universal(self):
        self.UniverseSettings.Resolution = Resolution.Minute
        symbols = []
        for stock in underlyings_stocks:
            equity = self.AddEquity(ticker=stock, resolution=Resolution.Minute)
            equity.SetBuyingPowerModel(BuyingPowerModel.Null)

            option = self.AddOption(stock)
            option.SetBuyingPowerModel(BuyingPowerModel.Null)
            
            option.SetFilter(lambda universe: 
                universe.IncludeWeeklys()\
                    .Strikes(-5, 5)\
                    .Expiration(0, 30)
            )

            self.option_symbols.append(option.Symbol)
            
            symbol_data_context[equity.Symbol] = SymbolData(self, equity.Symbol)
            
            symbols.extend([equity.Symbol, option.Symbol])
        
        self.AddUniverseSelection(ManualUniverseSelectionModel(symbols))
        

    def OnSecuritiesChanged(self, changes):
        for security in changes.AddedSecurities:
            if security.Symbol.ID.SecurityType == SecurityType.Option:
                # self.Securities[security.Symbol.ID].MarginModel = PatternDayTradingMarginModel()
                pass
        for security in changes.RemovedSecurities:
            if security.Symbol.ID.SecurityType == SecurityType.Option and security.Symbol in option_symbol_data_context:
                del option_symbol_data_context[security.Symbol]

    def OnOrderEvent(self, orderEvent):
        if orderEvent.Status == OrderStatus.Filled\
            and orderEvent.Symbol.ID.SecurityType == SecurityType.Option\
            and (orderEvent.IsAssignment or orderEvent.IsInTheMoney):
            order = self.Transactions.GetOrderById(orderEvent.OrderId)
            self.Log(f'ohly shit, {orderEvent.Symbol.Value}. {order.Tag}')
    

    def option_invested(self):
        for symbol, holding in self.Portfolio.items():
            if symbol.ID.SecurityType == SecurityType.Option and holding.Invested:
                return True
        return False


    def OnData(self, slice):
        
        # peng_orders
        peng_orders = self.Transactions.GetOpenOrders()
        if len(peng_orders) > 0:
            return
        
        if self.IsWarmingUp:
            return
                
        if self.option_invested():
            return
        
        now = self.Time
        
        # open_mkrt = now.replace(hour=9, minute=30)
        # just_open_mkrt = now.replace(hour=10, minute=0)
        # open_mkt_soon = open_mkrt < now < just_open_mkrt
        # if not open_mkt_soon:
        #     return
        day = self.Time.strftime("%y%m%d")
        
        for canonical_symbol, chain in slice.OptionChains.items():
            for symbol, contract in chain.Contracts.items():
                exchange = self.Securities[symbol].Exchange
                if exchange.IsClosingSoon(default_before_market_liquidate+1):
                    return
            
            cs = [contract for symbol, contract in chain.Contracts.items()]
            if len(cs) == 0:
                continue
                        
            candidates = try_setup_calendar(self, cs)
            candidates = [c for c in candidates if c.validate()]
            candidates = sorted(candidates, key=lambda c: c.score())
            for c in candidates:
                log_to_disk(self, 'candidates', c.underlying_symbol, c.to_json())
            if len(candidates) > 0:
                import json

                candidate = candidates[0]

                quantity = candidate.order_quantity()

                msg =f'place order {json.dumps(candidate.to_json())} at {quantity}'
                if self.LiveMode:
                    self.Notify.Email('violet_day@live.com', "Notify", msg)
                
                self.Log(msg)
                
                legs, price = candidate.as_combo_limit_orders()
                self.ComboLimitOrder(legs, quantity, price, tag='Enter')
                
    

    def OnEndOfAlgorithm(self):
        dump_to_storage(self, log_table)
from AlgorithmImports import *

from stop_loss_rm import StopLossRiskManagementModel
from stop_profit_rm import StopProfitRiskManagementModel

from trailing_stop_rm import NormalTrailingStopRiskManagementModel
from AlgorithmImports import *
from symbol_data import *
from config import *
from common import *
from option_utils import *
from share import *

class StopLossRiskManagementModel(RiskManagementModel):
    def __init__(self):
        pass

    def ManageRisk(self, algorithm, targets):
        targets = list()
        
        if not algorithm.Portfolio.Invested:
            return targets
        for underlying_symbol, holdings in group_by_underlying(algorithm).items():
            pnl = sum([h.UnrealizedProfit for h in holdings])
            cost = sum([h.HoldingsCost for h in holdings])
            pct = abs(pnl/cost)
            
            if pnl < 0 and pct > stop_loss_pct:
                # FIXME
                for symbol, security in algorithm.Securities.items():
                    if security.Invested:
                        targets.append(PortfolioTarget(symbol, 0))
                log_to_disk(algorithm, 'liquidate', security.Symbol.Value, {
                    'tag': 'stop_loss'
                })
                algorithm.Log(f'stop loss {underlying_symbol.Value}, pnl is {pnl}')            
        return targets
from AlgorithmImports import *
from symbol_data import *
from config import *
from common import *
from option_utils import *

from share import *

class StopProfitRiskManagementModel(RiskManagementModel):
    def __init__(self):
        pass

    def ManageRisk(self, algorithm, targets):
        targets = list()

        if not algorithm.Portfolio.Invested or algorithm.Portfolio.TotalUnrealizedProfit < 0:
            return targets

        for underlying_symbol, holdings in group_by_underlying(algorithm).items():
            pnl = sum([h.UnrealizedProfit for h in holdings])
            cost = sum([h.HoldingsCost for h in holdings])
            pct = abs(pnl/cost)
            
            if pnl> 0 and pct > stop_loss_pct * default_risk_reward_ratio:
                for symbol, security in algorithm.Securities.items():
                    if security.Invested:
                        targets.append(PortfolioTarget(symbol, 0))
                log_to_disk(algorithm, 'liquidate', security.Symbol.Value, {
                    'tag': 'stop_profit'
                })
                algorithm.Log(f'stop profit, pnl is {pnl}')            
        return targets
from AlgorithmImports import *
from common import *
from symbol_data import *

from share import *

class NormalTrailingStopRiskManagementModel(RiskManagementModel):
    
    def __init__(self, min_profit=0.01, maximum_drawdown = 0.2):      
        self.min_profit = min_profit
        self.maximum_drawdown = maximum_drawdown
        
        self.trailing_highs = dict()
        self.reach_threhshold = dict()

    def ManageRisk(self, algorithm, targets):
        targets = list()

        for symbol, security in algorithm.Securities.items():
            
            # Remove if not invested
            if not security.Invested:
                self.trailing_highs.pop(symbol, None)
                self.reach_threhshold.pop(symbol, None)
                continue

            pnl = security.Holdings.UnrealizedProfit
            max_unrealized_profit = security.Holdings.HoldingsCost * self.min_profit
            # 亏损则忽略
            if pnl < 0 and symbol not in self.reach_threhshold:
                continue

            # Add newly invested securities
            if symbol not in self.trailing_highs:
                self.trailing_highs[symbol] = pnl
                continue

            # 收益增大的情况下,覆盖缓存中的symbol最大收益
            if self.trailing_highs[symbol] < pnl:
                self.trailing_highs[symbol] = pnl
            

            if pnl >= max_unrealized_profit and symbol not in self.reach_threhshold:
                # 当前symbol已经触发追踪止损, 首次命中
                self.reach_threhshold[symbol] = True
                algorithm.Log(f'{symbol.Value}@{security.Price} has reach traliling stop')
            
            if not self.reach_threhshold.get(symbol, False):
                continue
            
            if symbol in self.reach_threhshold:
                max_profit = self.trailing_highs[symbol]
                abs_drawdown_percent = (max_profit - pnl) / max_profit

                if abs_drawdown_percent > self.maximum_drawdown:
                    log_to_disk(algorithm, 'liquidate', security.Symbol.Value, {'tag': 'trailing_stop'})
                    targets.append(PortfolioTarget(symbol, 0))
                    
                    self.trailing_highs.pop(symbol, None)
                    self.reach_threhshold.pop(symbol, None)

                    continue
            

        return targets
#region imports
from AlgorithmImports import *
from symbol_data import *
from functools import *
from itertools import *
from config import *
from symbol_data import *
from common import *
from option_utils import *
import json

cache = {}

def gen_all_expiry(day, all_options):
    day = day.strftime("%y%m%d")
    if day in cache:
        return cache[day]

    all_expiries = list(set([c.Expiry for c in all_options]))
    all_expiries_combo = [(a,b) for a,b in permutations(all_expiries, 2) if a<b]
    
    cache[day] = all_expiries_combo
    return all_expiries_combo

def try_setup_calendar(algo, all_options):
    option_map = {(c.Expiry, c.Right, c.Strike) : c for c in all_options}
    desc = []
    
    all_expiries_combo = gen_all_expiry(algo.Time, all_options)
    
    for expiry_a, expiry_b in all_expiries_combo:
        strikes = set([c.Strike for c in all_options if c.Expiry==expiry_a])
        for strike in strikes:
            if (expiry_a, OptionRight.Put, strike) in option_map and (expiry_b, OptionRight.Put, strike) in option_map:
                option_short = option_map[(expiry_a, OptionRight.Put, strike)]
                option_long = option_map[(expiry_b, OptionRight.Put, strike)]
                desc.append(Desc(algo, option_short, option_long))
            
            if (expiry_a, OptionRight.Call, strike) in option_map and (expiry_b, OptionRight.Call, strike) in option_map:
                option_short = option_map[(expiry_a, OptionRight.Call, strike)]
                option_long = option_map[(expiry_b, OptionRight.Call, strike)]
                desc.append(Desc(algo, option_short, option_long))

    return desc


class Desc:
    def __init__(self, algo, option_short, option_long):
        self.algo = algo
        
        self.symbol_data = symbol_data_context[option_short.UnderlyingSymbol]

        self.underlying_symbol =  option_short.UnderlyingSymbol
        self.option_short, self.option_long = option_short, option_long
        self.diff_day = (self.option_long.Expiry-self.option_short.Expiry).days
        
        self.strike = option_short.Strike
        
        self.expiry = (self.option_short.Expiry - self.algo.Time).days
        self.expiry_a = self.option_short.Expiry.strftime("%y%m%d")
        self.expiry_b = self.option_long.Expiry.strftime("%y%m%d")
        self.right = 'call' if self.option_short.Right == OptionRight.Call else 'put'
        
        self.ID =f'{self.option_short.UnderlyingSymbol.Value}/{self.expiry_a}/{self.expiry_b}/{self.right}/{self.strike}'
            
        self.premium = [
            - self.option_short.BidPrice + self.option_long.AskPrice,
            - self.option_short.AskPrice + self.option_long.BidPrice,
            - self.option_short.LastPrice + self.option_long.LastPrice
        ]

        self.max_loss = self.option_long.AskPrice - self.option_short.BidPrice
        self.size = [
            self.option_short.BidSize,
            self.option_long.AskSize
        ]

    def to_json(self):
        return {
            '_id': self.ID,
            'premium': self.premium,
            'max_loss': self.max_loss,
            # 'strike_atr': self.strike_atr,

            'spread': self.spread,

            'underlying_price': self.option_short.UnderlyingLastPrice,

            # 'size': self.size,
            'delta_pct_': abs(self.norm_delta * self.symbol_data.atr.Current.Value /self.max_loss),
            
            'expiry': self.expiry,
            'internal_expiry': (self.option_long.Expiry-self.option_short.Expiry).days,

            'greek': {
                'delta': self.norm_delta,
                'gamma': self.norm_gamma,
                'theta_per_day': self.norm_theta_per_day,
            },

            'sd': self.symbol_data.to_json()
        }

    @property    
    def spread(self):
        bid_and_ask_price =  - self.option_short.BidPrice + self.option_long.AskPrice
        unrealized_price =  - self.option_short.AskPrice + self.option_long.BidPrice
        close_price = self.option_long.LastPrice - self.option_short.LastPrice

        return 1-unrealized_price/bid_and_ask_price if bid_and_ask_price!=0 else 0

    @property
    def norm_delta(self):
        return - self.option_short.Greeks.Delta + self.option_long.Greeks.Delta

    @property
    def norm_gamma(self):
        return - self.option_long.Greeks.Gamma + self.option_short.Greeks.Gamma

    @property
    def norm_theta_per_day(self):
        return self.option_long.Greeks.ThetaPerDay - self.option_short.Greeks.ThetaPerDay

    def as_orders_limit_price(self):
        return -self.option_short.LastPrice + self.option_long.LastPrice

    def as_combo_limit_orders(self):
        return [
            Leg.Create(self.option_short.Symbol, -1),
            Leg.Create(self.option_long.Symbol, +1),
        ], - self.option_short.BidPrice + self.option_long.AskPrice

    @property
    def strike_atr(self):
        if self.option_short.Right == OptionRight.Call:
            return (self.option_short.Strike - self.option_short.UnderlyingLastPrice) / self.symbol_data.atr.Current.Value
        else:
            return (self.option_short.UnderlyingLastPrice - self.option_short.Strike) / self.symbol_data.atr.Current.Value

    def validate(self):
        base_validate_items = {
            # 'premium': all([p>0.01 for p in self.premium]),
            # 'size': default_order_quantity < min(self.size),
            'spread': self.spread < 0.03,
            # 'strike_atr': abs(self.strike_atr) < 0.5
        }

        if not all(base_validate_items.values()):
            return False

        greek_validate_items = {
            '': True,
            
            'delta_pct': abs(self.norm_delta * self.symbol_data.atr.Current.Value /self.max_loss) < 0.010,
            # 'theta_per_day_pct': abs(self.norm_theta_per_day/self.max_loss) > 0.300,

            # 'gamma': self.norm_gamma > 0.0,
            # 'theta': self.norm_theta_per_day > 0,
        }

        if not all(greek_validate_items.values()):
            return False
        return True

    def score(self):
        return abs(self.norm_theta_per_day)
        
    def order_quantity(self):
        legs, price = self.as_combo_limit_orders()
        return math.floor(
            self.algo.Portfolio.Cash * 0.3 / 100 / price
        )
        
        
        # import math
        # max_cash = self.algo.Portfolio.Cash * 3

        # stop_loss = 0.1
        
        # quantity = min([
        #     math.floor(max_cash / self.underlying_price / 100),
        #     math.floor(abs(self.algo.Portfolio.Cash * stop_loss / self.payoff.max_loss)),
        # ])
            
        # return min([
        #     quantity,
        #     min(self.size)
        # ])
from AlgorithmImports import *
from indicator import *

class DailyIndicator(PythonIndicator):
    def __init__(self):
        self.Value = 0
        self.today = None
        self.today_highest_high = None
        self.today_lowest_low = None
        self.today_volume = 0
        # self.yesterday_close = 0
    
    def Update(self, input):
        day = input.EndTime.strftime('%y-%M-%d')
        if day != self.today:
            self.today_highest_high = input.High
            self.today_lowest_low = input.Low
            self.today = day
            self.today_volume = input.Volume
            self.Value = self.today_highest_high - self.today_lowest_low
        else:
            self.today_highest_high = max(input.High, self.today_highest_high)
            self.today_lowest_low = max(input.Low, self.today_lowest_low)
            self.today_volume = self.today_volume + input.Volume
            
            self.Value = self.today_highest_high - self.today_lowest_low
        return True
    
    def to_json(self):
        return {
            'atr': self.atr.Current.Value,
        }
        
    def is_ready(self):
        return all([
            self.atr.IsReady()
        ])

    
class SymbolData:
    def __init__(self, algorithm, symbol):
        self.algorithm = algorithm
        self.symbol = symbol
        
        self.atr = AverageTrueRange(1)
        self.di = DailyIndicator()

        self.daily_consolidator = TradeBarConsolidator(timedelta(days=1))

        algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.daily_consolidator)
        
        self.daily_consolidator.DataConsolidated += self.consolidation_handler

        self.warmup()


    def consolidation_handler(self, sender, input):
        if input is  None:
            return
        # point = IndicatorDataPoint(index, row.close)
        # self.algorithm.Debug(input.EndTime)
        self.atr.Update(input)
        self.di.Update(input)

    def to_json(self):
        return {
            'tr': self.atr.Current.Value
        }
    
    def warmup(self): 
        history = self.algorithm.History(
            self.symbol, 
            self.algorithm.Time - timedelta(days=5), 
            self.algorithm.Time, 
            Resolution.Daily, 
            dataNormalizationMode=DataNormalizationMode.Raw, 
            ).reset_index(level=0)

        for index, row in history.iterrows():
            if self.algorithm.LiveMode:
                self.algorithm.Log(row.to_dict())
            bar = TradeBar(index, self.symbol, row.open, row.high, row.low, row.close, row.volume)            
            self.consolidation_handler(None, bar)
    
symbol_data_context = {}            

feature_snap = {}