using System; using System.Collections.Generic; using MarketData.MarketDataModel; using MarketData.Utils; using MarketData.Cache; using MarketData.Numerical; using MarketData.Generator.ModelGenerators; using StopLimit=MarketData.Generator.Model.StopLimit; namespace MarketData.Generator.MGSHMomentum { public class HedgeManager { private static int PRICING_DAYS = 120; public HedgeManager() { } public String HedgeShortSymbol { get; set; } = "SH"; public bool Verbose {get;set;} = false; private void Display(String message) { if(Verbose) { MDTrace.WriteLine(LogLevel.DEBUG,message); } } public bool IsOpenHedgeIndicator(DateTime analysisDate, int hedgeCloseGreaterSMANDays=10, int hedgeBandBreakCheckDays=3) { Prices prices=GBPriceCache.GetInstance().GetPrices(HedgeShortSymbol, analysisDate, PRICING_DAYS); if(null==prices || !prices[0].Date.Date.Equals(analysisDate.Date)) { Display(String.Format("Cannot evaluate IsOpenHedgeIndicator for candidate {0} due to lack of current price on {1}", HedgeShortSymbol,analysisDate.ToShortDateString())); return false; } BollingerBands bollingerBands=BollingerBandGenerator.GenerateBollingerBands(prices); // K is upper band, KL1 below that. L is lower band, LP1 above that BollingerBandElementsByDate bollingerBandElementsByDate = bollingerBands.GetBollingerBandElementsByDate(); if(!bollingerBandElementsByDate.ContainsKey(analysisDate)) { Display(String.Format("Cannot evaluate IsOpenHedgeIndicator for candidate {0} due to lack of current price on {1}",HedgeShortSymbol,analysisDate.ToShortDateString())); return false; } BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[analysisDate]; if(bollingerBandElement.Close < bollingerBandElement.L ) { Display(String.Format($"IsOpenHedgeIndicator AnalysisDate:{analysisDate.ToShortDateString()}")); SlopeManager slopeManager = new SlopeManager(bollingerBandElementsByDate, analysisDate){Verbose=Verbose}; slopeManager.DisplaySlopes(); if(!CloseGreaterSMAN(analysisDate, bollingerBandElementsByDate, hedgeCloseGreaterSMANDays)) { return false; } if(!BandBreakCheck(analysisDate, bollingerBandElementsByDate, hedgeBandBreakCheckDays)) // 3 { return false; } int bucketDays=5; int closeLessL=GetCloseLessL(analysisDate, bollingerBandElementsByDate, bucketDays); int closeBetweenLAndLP1=GetCloseBetweenLAndLP1(analysisDate, bollingerBandElementsByDate, bucketDays); int closeBetweenLP1AndSMAN=GetCloseBetweenLP1AndSMAN(analysisDate, bollingerBandElementsByDate, bucketDays); int closeBetweenSMANAndKL1=GetCloseBetweenSMANAndKL1(analysisDate, bollingerBandElementsByDate, bucketDays); int closeBetweenKL1AndK=GetCloseBetweenKL1AndK(analysisDate, bollingerBandElementsByDate, bucketDays); int closeGreaterK=GetCloseGreaterK(analysisDate, bollingerBandElementsByDate, bucketDays); Display($"Close buckets K:{closeGreaterK} KLSpread:{slopeManager.GetKLSpread()}"); // if the close is spending a lot of time between L and LP1 then make sure the overall volatility spread is widening sufficiently in the 5, 10, 30 day terms if(closeBetweenLAndLP1 >= bucketDays && !slopeManager.IsMatchKLSpread("??+++")) { return false; } Display(String.Format("Lower band break detected for {0} on {1} Price Close:{2} LP1:{3} ", HedgeShortSymbol, analysisDate.ToShortDateString(), Utility.FormatCurrency(bollingerBandElement.Close), Utility.FormatCurrency(bollingerBandElement.L) )); Display($"TRUE"); return true; } return false; } /// /// Mainains and adjusts the stop limits for teh hedge position /// /// /// /// /// public StopLimit EvaluateStopPriceHedge(DateTime analysisDate, MGSHPosition position, MGSHConfiguration configuration) { DateGenerator dateGenerator=new DateGenerator(); Price currentPrice=GBPriceCache.GetInstance().GetPrice(position.Symbol,analysisDate); double trailingStop=position.InitialStopLimit+Math.Floor((currentPrice.Low-position.PurchasePrice)/position.R)*position.R; double trailingStopScaled=trailingStop; double daysHeld=Math.Abs(dateGenerator.DaysBetweenActual(position.PurchaseDate,analysisDate)); if(Utility.IsEpoch(position.LastStopAdjustment)) // we've never adjusted the stop price { BollingerBandElementsByDate bollingerBandElementsByDate = GetBollingerBandElementsByDate(analysisDate); if(null == bollingerBandElementsByDate)return null; trailingStopScaled=GetHedgeStopLimitWithScalingAverageTrueRange(analysisDate,currentPrice,position,trailingStop, configuration.StopLimitScalingVolatilityDays, configuration.HedgeATRMultiplier); trailingStop=Math.Max(trailingStop,trailingStopScaled); if(trailingStop >= currentPrice.Low || trailingStop >= currentPrice.Close) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The calculated trailing stop for {0} of {1} would be higher than the Low/Close of {2}.", position.Symbol, Utility.FormatCurrency(trailingStop), Utility.FormatCurrency(currentPrice.Low))); return null; } if(Numerics.Round(trailingStop) <= (Numerics.Round(position.TrailingStopLimit))) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The calculated trailing stop for {0} of {1} would be less than or equal to the existing stop limit of {2}.", position.Symbol, Utility.FormatCurrency(trailingStop), Utility.FormatCurrency(position.TrailingStopLimit))); return null; } MDTrace.WriteLine(LogLevel.DEBUG,String.Format("****************************** A D J U S T S T O P L I M I T ************************")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Adjusting StopLimit on {0} after {1} days for {2} from {3} to {4}. Purchase Price: {5} Current Price:{6} Spread:{7} Shares:{8}", analysisDate.ToShortDateString(), daysHeld, position.Symbol, Utility.FormatCurrency(position.TrailingStopLimit), Utility.FormatCurrency(trailingStop), Utility.FormatCurrency(position.PurchasePrice), Utility.FormatCurrency(currentPrice.Close), Utility.FormatPercent((currentPrice.Close-trailingStop)/trailingStop), Utility.FormatNumber(position.Shares,2))); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("*****************************************************************************************")); StopLimit newStopLimit=new StopLimit { StopLimitId=position.Symbol + Utility.DateTimeToStringYYYYMMDDMMSSTT(position.PurchaseDate), Symbol=position.Symbol, AnalysisDate=analysisDate, PreviousStop=0.00==position.TrailingStopLimit?position.InitialStopLimit:position.TrailingStopLimit, NewStop=trailingStop, CurrentPriceLow=currentPrice.Low, CurrentPriceClose=currentPrice.Close, PriceTrendIndicatorSlope=0.00 }; position.TrailingStopLimit=trailingStop; position.LastStopAdjustment=analysisDate; return newStopLimit; } else // we have already made prior stop adjustments { int daysSinceLastStopAdjustment=Math.Abs(dateGenerator.DaysBetweenActual(position.LastStopAdjustment,analysisDate)); if(daysSinceLastStopAdjustment>=configuration.HedgeMinDaysBetweenStopAdjustments) { trailingStopScaled=GetHedgeStopLimitWithScalingAverageTrueRange(analysisDate,currentPrice,position,trailingStop,daysSinceLastStopAdjustment,configuration.HedgeATRMultiplier); trailingStop=Math.Max(trailingStop,trailingStopScaled); if(trailingStop>=currentPrice.Low||trailingStop>=currentPrice.Close) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The calculated trailing stop for {0} of {1} would be higher than the Low/Close of {2}.",position.Symbol,Utility.FormatCurrency(trailingStop),Utility.FormatCurrency(currentPrice.Low))); return null; } if(Numerics.Round(position.TrailingStopLimit) < Numerics.Round(trailingStop)) // round the stop limits to fractionals don't look like differences { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("****************************** A D J U S T S T O P L I M I T ************************")); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Adjusting StopLimit on {0} after {1} days for {2} from {3} to {4}. Purchase Price: {5} Current Price:{6} Spread:{7} Shares:{8}", analysisDate.ToShortDateString(), daysHeld, position.Symbol, Utility.FormatCurrency(position.TrailingStopLimit), Utility.FormatCurrency(trailingStop), Utility.FormatCurrency(position.PurchasePrice), Utility.FormatCurrency(currentPrice.Close), Utility.FormatPercent((currentPrice.Close-trailingStop)/trailingStop), Utility.FormatNumber(position.Shares,2))); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("****************************************************************************************")); StopLimit newStopLimit=new StopLimit { StopLimitId=position.Symbol + Utility.DateTimeToStringYYYYMMDDMMSSTT(position.PurchaseDate), Symbol=position.Symbol, AnalysisDate=analysisDate, PreviousStop=0.00==position.TrailingStopLimit?position.InitialStopLimit:position.TrailingStopLimit, NewStop=trailingStop, CurrentPriceLow=currentPrice.Low, CurrentPriceClose=currentPrice.Close, PriceTrendIndicatorSlope=0.00 }; position.TrailingStopLimit=trailingStop; position.LastStopAdjustment=analysisDate; return newStopLimit; } else { double currentTrailingStopLimit=Numerics.Round(position.TrailingStopLimit); double suggestedTrailingStopLimit=Numerics.Round(trailingStop); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Not Adjusting Stop Limit for {0} because the new trailing stop limit would be less than or equal to the current trailing stop limit. Current Trailing Stop Limit:{1}, Calculated Trailing Stop Limit:{2}.", position.Symbol, Utility.FormatCurrency(currentTrailingStopLimit), Utility.FormatCurrency(suggestedTrailingStopLimit))); return null; } } else { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("The trailing stop for {0} was adjusted {1} days ago. HedgeMinDaysBetweenStopAdjustments is {2}", position.Symbol, daysSinceLastStopAdjustment, configuration.HedgeMinDaysBetweenStopAdjustments)); return null; } } } private int GetCloseLessL(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days) { int count=0; DateGenerator dateGenerator = new DateGenerator(); List dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1); dates.RemoveAt(0); foreach(DateTime date in dates) { BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[date]; if(bollingerBandElement.Close < bollingerBandElement.L)count++; } return count; } private int GetCloseBetweenLAndLP1(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days) { int count=0; DateGenerator dateGenerator = new DateGenerator(); List dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1); dates.RemoveAt(0); foreach(DateTime date in dates) { BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[date]; if(bollingerBandElement.Close >= bollingerBandElement.L && bollingerBandElement.Close dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1); dates.RemoveAt(0); foreach(DateTime date in dates) { BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[date]; if(bollingerBandElement.Close >= bollingerBandElement.LP1 && bollingerBandElement.Close dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1); dates.RemoveAt(0); foreach(DateTime date in dates) { BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[date]; if(bollingerBandElement.Close >= bollingerBandElement.SMAN && bollingerBandElement.Close dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1); dates.RemoveAt(0); foreach(DateTime date in dates) { BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[date]; if(bollingerBandElement.Close >= bollingerBandElement.KL1 && bollingerBandElement.Close dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1); dates.RemoveAt(0); foreach(DateTime date in dates) { BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[date]; if(bollingerBandElement.Close > bollingerBandElement.K )count++; } return count; } private bool BandBreakCheck(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days) { int lowLessLP1=0; int closeLessLP1=0; DateGenerator dateGenerator = new DateGenerator(); List dates = dateGenerator.GenerateHistoricalDates(analysisDate, days +1); dates.RemoveAt(0); foreach(DateTime date in dates) { BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[date]; if(bollingerBandElement.Low <= bollingerBandElement.LP1)lowLessLP1++; if(bollingerBandElement.Close <= bollingerBandElement.LP1)closeLessLP1++; } return (lowLessLP1 >= days) && (closeLessLP1 >= days); } private bool CloseGreaterSMAN(DateTime analysisDate, BollingerBandElementsByDate bollingerBandElementsByDate, int days) { DateGenerator dateGenerator = new DateGenerator(); List dates = dateGenerator.GenerateHistoricalDates(analysisDate, days + 1); dates.RemoveAt(0); foreach (DateTime date in dates) { BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[date]; if (bollingerBandElement.Close > bollingerBandElement.SMAN) return true; } return false; } /// /// This is used to trigger an adjustment to the stop price. /// /// /// /// public bool IsLowerBandBreakIndicator(DateTime analysisDate, MGSHPosition position) { BollingerBandElementsByDate bollingerBandElementsByDate = GetBollingerBandElementsByDate(analysisDate); if (null == bollingerBandElementsByDate) return false; BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[analysisDate]; if (bollingerBandElement.High > bollingerBandElement.K) { Display(String.Format("+++ [1] Upper band break detected for {0} on {1} Purchase Date:{2} Purchase Price:{3} Price Close:{4} Price High:{5} Price High(hF):{6} KL1:{7}", HedgeShortSymbol, analysisDate.ToShortDateString(), position.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(position.PurchasePrice), Utility.FormatCurrency(bollingerBandElement.Close), Utility.FormatCurrency(bollingerBandElement.High), Utility.FormatCurrency(bollingerBandElement.High), Utility.FormatCurrency(bollingerBandElement.K) )); return true; } return false; } private double GetHedgeStopLimitWithScalingAverageTrueRange(DateTime analysisDate,Price currentPrice,MGSHPosition position,double stopLimitNonScaled, int stopLimitScalingVolatilityDays, double hedgeATRMultiplier) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("GetHedgeStopLimitWithScalingAverageTrueRange: Symbol:{0}",position.Symbol)); double volatility=double.NaN; volatility=VolatilityGenerator.CalculateVolatility(position.Symbol,analysisDate,stopLimitScalingVolatilityDays, hedgeATRMultiplier); if(double.IsNaN(volatility)) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Unable to calculate AverageTrueRange for {0} on {1}. Using non-scaled stop limit.",position.Symbol,analysisDate.ToShortDateString())); return stopLimitNonScaled; } double stopLimit = ((currentPrice.High + currentPrice.Low)/2.00) - volatility; // We base the stop off of the midpoint of the high and the low return stopLimit; } private BollingerBandElementsByDate GetBollingerBandElementsByDate(DateTime analysisDate) { Prices prices=GBPriceCache.GetInstance().GetPrices(HedgeShortSymbol, analysisDate, PRICING_DAYS); if(null==prices||!prices[0].Date.Date.Equals(analysisDate.Date)) { Display(String.Format("Cannot evaluate IsHedgeCloseIndicatorOn for candidate {0} due to lack of current price on {1}", HedgeShortSymbol,analysisDate.ToShortDateString())); return null; } BollingerBands bollingerBands=BollingerBandGenerator.GenerateBollingerBands(prices); // K is upper band, KL1 below that. L is lower band, LP1 above that BollingerBandElementsByDate bollingerBandElementsByDate = bollingerBands.GetBollingerBandElementsByDate(); if(!bollingerBandElementsByDate.ContainsKey(analysisDate)) { Display(String.Format("Cannot evaluate IsHedgeCloseIndicatorOn for candidate {0} due to lack of current price on {1}", HedgeShortSymbol,analysisDate.ToShortDateString())); return null; } return bollingerBandElementsByDate; } public void DisplayBandSlopes(DateTime analysisDate) { Display($"***** B A N D S L O P E S {analysisDate.ToShortDateString()} *****"); Prices prices=GBPriceCache.GetInstance().GetPrices(HedgeShortSymbol, analysisDate, PRICING_DAYS); if(null==prices||!prices[0].Date.Date.Equals(analysisDate.Date)) { Display(String.Format("Cannot evaluate IsHedgeCloseIndicatorOn for candidate {0} due to lack of current price on {1}", HedgeShortSymbol,analysisDate.ToShortDateString())); return; } BollingerBands bollingerBands=BollingerBandGenerator.GenerateBollingerBands(prices); // K is upper band, KL1 below that. L is lower band, LP1 above that BollingerBandElementsByDate bollingerBandElementsByDate = bollingerBands.GetBollingerBandElementsByDate(); if(!bollingerBandElementsByDate.ContainsKey(analysisDate)) { Display(String.Format("Cannot evaluate IsHedgeCloseIndicatorOn for candidate {0} due to lack of current price on {1}", HedgeShortSymbol,analysisDate.ToShortDateString())); return; } BollingerBandElement bollingerBandElement = bollingerBandElementsByDate[analysisDate]; SlopeManager slopeManager = new SlopeManager(bollingerBandElementsByDate, analysisDate){Verbose=true}; Display("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); slopeManager.DisplaySlopes(); Display("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); slopeManager.DisplaySlopesNumeric(); Display("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); } } }