Files
2025-02-13 15:02:19 -05:00

436 lines
22 KiB
C#

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 <L:{closeLessL} L/LP1:{closeBetweenLAndLP1} LP1/SMAN:{closeBetweenLP1AndSMAN} SMAN/KL1:{closeBetweenSMANAndKL1} KL1/K:{closeBetweenKL1AndK} >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;
}
/// <summary>
/// Mainains and adjusts the stop limits for teh hedge position
/// </summary>
/// <param name="analysisDate"></param>
/// <param name="position"></param>
/// <param name="configuration"></param>
/// <returns></returns>
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<DateTime> 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<DateTime> 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<bollingerBandElement.LP1 )count++;
}
return count;
}
private int GetCloseBetweenLP1AndSMAN(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days)
{
int count=0;
DateGenerator dateGenerator = new DateGenerator();
List<DateTime> 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<bollingerBandElement.SMAN )count++;
}
return count;
}
private int GetCloseBetweenSMANAndKL1(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days)
{
int count=0;
DateGenerator dateGenerator = new DateGenerator();
List<DateTime> 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<bollingerBandElement.KL1 )count++;
}
return count;
}
private int GetCloseBetweenKL1AndK(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days)
{
int count=0;
DateGenerator dateGenerator = new DateGenerator();
List<DateTime> 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<bollingerBandElement.K )count++;
}
return count;
}
private int GetCloseGreaterK(DateTime analysisDate,BollingerBandElementsByDate bollingerBandElementsByDate, int days)
{
int count=0;
DateGenerator dateGenerator = new DateGenerator();
List<DateTime> 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<DateTime> 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<DateTime> 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;
}
/// <summary>
/// This is used to trigger an adjustment to the stop price.
/// </summary>
/// <param name="analysisDate"></param>
/// <param name="position"></param>
/// <returns></returns>
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("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}
}
}