BetaGenerator and some code cleanup

This commit is contained in:
2025-02-14 18:56:11 -05:00
parent 2467c30bb3
commit 6f96ba3e22
5 changed files with 75 additions and 62 deletions

View File

@@ -748,7 +748,7 @@ namespace MarketData.Generator.MGSHMomentum
position.Comment = "Closed due to trailing stop.";
HedgeCashBalance+=position.MarketValue;
// This tries to maintain a 10% margin in the hedge cash
// This tries to maintain a hedgeCashMarginPercentDecimal margin in the hedge cash. In other words some % of the hedge gain -> Cash and some -> hedge cash, not allowing hedge cash to grow some margin beyond it's initial setting
double hedgeCashMarginPercentDecimal=.10;
double hedgeCashTreshholdAmount=Configuration.HedgeInitialCash*(1.00+hedgeCashMarginPercentDecimal);
if(HedgeCashBalance > hedgeCashTreshholdAmount)
@@ -764,13 +764,13 @@ namespace MarketData.Generator.MGSHMomentum
HedgeCashBalance=0.00;
}
// Retain this original code for a while until happy with the hedge cash margin approach
//if(HedgeCashBalance>Configuration.HedgeInitialCash)
//{
// CashBalance+=HedgeCashBalance-Configuration.HedgeInitialCash; // if we made gains on the hedge and the hedge cash would grow then put proceeds above the initial hedge cash in regualr cash
// HedgeCashBalance=Configuration.HedgeInitialCash; // For example, let hedge proceeeds help the portfolio invest
//}
AllPositions.Add(position);
closedPositions.Add(position);
}
@@ -1189,11 +1189,8 @@ namespace MarketData.Generator.MGSHMomentum
position.CumReturn252 = momentumCandidate.CumReturn252;
position.IDIndicator = momentumCandidate.IDIndicator;
position.Score=momentumCandidate.Score;
position.MaxDrawdown = momentumCandidate.MaxDrawdown;
position.MaxUpside = momentumCandidate.MaxUpside;
position.PE = momentumCandidate.PE;
position.Beta = momentumCandidate.Beta;
position.ZacksRank = momentumCandidate.ZacksRank;
position.Velocity = momentumCandidate.Velocity;
position.Volume = momentumCandidate.Volume;
position.Return1D = position.Return1D;

View File

@@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using MarketData.Utils;
namespace MarketData.Generator.MGSHMomentum
@@ -52,6 +53,10 @@ namespace MarketData.Generator.MGSHMomentum
public bool UseEBITDAScreen{get;set;}
public bool UseRevenuePerShareScreen{get;set;}
// BETA
public bool UseBetaGenerator{get;set;} // If set then the model will use the internal beta generator. Otherwise it will use Beta from Fundamental data
public int UseBetaGeneratorMonths{get;set;} // If UseBetaGenerator=true then UseBetaGeneratorMonths will prescribe the number of months over which to calculate the monthly beta
// If slope Beta Check is true then we compare the fundamental Beta to the threshhold value. If Beta>Threshhold AND the slope from the Benchmark LowPrice over BetaDays is <0 we reject
public String Benchmark{get;set;}
public bool UseLowSlopeBetaCheck{get;set;}
@@ -105,6 +110,10 @@ namespace MarketData.Generator.MGSHMomentum
MaxPE=40;
StrictMaxPE=false;
// Beta
UseBetaGenerator=true; // The default setting is to calculate our own beta rather than use the beta from the fundamental data
UseBetaGeneratorMonths=24; // The default number of months over which to calculate our monthly return stream for the beta generator
// Stop Limits
UseStopLimits=false; // Flag to control the use of stop limits
StopLimitRiskPercentDecimal=.17; // The per share risk to take when setting the initial stop. This was the best setting after running a suite of tests
@@ -213,6 +222,9 @@ namespace MarketData.Generator.MGSHMomentum
nvpCollection.Add(new NVP("MaxPricingExceptions",MaxPricingExceptions.ToString()));
nvpCollection.Add(new NVP("UseBetaGenerator",UseBetaGenerator.ToString()));
nvpCollection.Add(new NVP("UseBetaGeneratorMonths",UseBetaGeneratorMonths.ToString()));
return nvpCollection;
}
public static MGSHConfiguration FromNVPCollection(NVPCollection nvpCollection)
@@ -290,6 +302,13 @@ namespace MarketData.Generator.MGSHMomentum
{
mgConfiguration.UseHedging=false;
}
if(nvpDictionary.ContainsKey("UseBetaGenerator"))
{
mgConfiguration.UseBetaGenerator = nvpDictionary["UseBetaGenerator"].Get<bool>();
mgConfiguration.UseBetaGeneratorMonths = nvpDictionary["UseBetaGeneratorMonths"].Get<int>();
}
return mgConfiguration;
}
public void DisplayConfiguration()
@@ -355,6 +374,10 @@ namespace MarketData.Generator.MGSHMomentum
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("HedgeCloseAboveSMANDays,{0}", HedgeCloseAboveSMANDays));
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("HedgeBandBreakCheckDays,{0}", HedgeBandBreakCheckDays));
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("HedgeATRMultiplier,{0}", HedgeATRMultiplier));
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("UseBetaGenerator,{0}", UseBetaGenerator));
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("UseBetaGeneratorMonths,{0}", UseBetaGeneratorMonths));
}
}
}

View File

@@ -23,26 +23,32 @@ namespace MarketData.Generator.MGSHMomentum
public int DayCount{get;set;}
public double IDIndicator{get;set;} // This is the IDIndicator methodology used for quality. This is the default methodology
public double Score{get;set;} // This is the Score methodology used for quality. This one is taken from Andreas Clenow Momentum
public double MaxDrawdown{get;set;}
public double MaxUpside{get;set;}
public double PE{get;set;}
public double Beta{get;set;}
public double Velocity{get;set;}
public long Volume{get;set;}
public double Return1D{get;set;}
public double SharpeRatio{get;set;}
public String ZacksRank{get;set;}
public static String Header()
{
StringBuilder sb=new StringBuilder();
sb.Append("Symbol,AnalysisDate,Return,DayCount,IDIndicator,Score,MaxDrawdown,MaxUpside");
sb.Append("Symbol,AnalysisDate,Return,DayCount,IDIndicator,Score");
return sb.ToString();
}
public override String ToString()
{
StringBuilder sb=new StringBuilder();
sb.Append(Symbol).Append(",").Append(AnalysisDate).Append(",").Append(Utility.FormatPercent(CumReturn252)).Append(",").Append(DayCount).Append(",").Append(IDIndicator).Append(",").Append(Score).Append(","). Append(Utility.FormatPercent(MaxDrawdown)).Append(",").Append(Utility.FormatPercent(MaxUpside));
sb.Append(Symbol).Append(",").
Append(AnalysisDate).
Append(",").
Append(Utility.FormatPercent(CumReturn252)).
Append(",").Append(DayCount).
Append(",").
Append(IDIndicator).
Append(",").
Append(Score).
Append(",");
return sb.ToString();
}
}

View File

@@ -6,6 +6,7 @@ using MarketData.Utils;
using System.Linq;
using MarketData.Numerical;
using MarketData.Cache;
using System.Configuration;
// Filename: MGSHMomentumGenerator.cs
// Author:Sean Kessler
@@ -15,6 +16,8 @@ using MarketData.Cache;
// 2) Incorporates ability to buy a hedge position. This is configurable
// 3) The model does not sell all positions at month end. Instead, it holds positions until they stop out. This is configurable
//BetaGenerator.Beta(symbol,tradeDate,cmtParams.BetaMonths); // beta months is 6
namespace MarketData.Generator.MGSHMomentum
{
/// <summary>Generate momentum selections - </summary>
@@ -44,7 +47,6 @@ namespace MarketData.Generator.MGSHMomentum
bestCandidate=CandidateSelector.SelectBestCandidate(qualityIndicator,Utility.ToList(config.FallbackCandidateBestOf),config.FallbackCandidate,tradeDate);
if(null!=bestCandidate)
{
ZacksRank zacksRank=ZacksRankDA.GetZacksRankOnOrBefore(bestCandidate.Symbol,tradeDate);
MGSHMomentumCandidate momentumCandidate=new MGSHMomentumCandidate();
momentumCandidate.Symbol=bestCandidate.Symbol;
momentumCandidate.AnalysisDate=tradeDate;
@@ -55,7 +57,6 @@ namespace MarketData.Generator.MGSHMomentum
momentumCandidate.PE=bestCandidate.PE;
momentumCandidate.Beta=bestCandidate.Beta;
momentumCandidate.Return1D=bestCandidate.Return1D;
if(null!=zacksRank)momentumCandidate.ZacksRank=zacksRank.Rank;
momentumCandidates=new MGSHMomentumCandidates();
momentumCandidates.Add(momentumCandidate);
}
@@ -181,10 +182,23 @@ namespace MarketData.Generator.MGSHMomentum
double[] pricesArray=null;
LeastSquaresResult leastSquaresResult;
// Beta - first capture the fundamental beta and then determine whether we will calculate our own
double beta = fundamental.Beta;
if(config.UseBetaGenerator)
{
beta = BetaGenerator.Beta(symbol, config.UseBetaGeneratorMonths, false);
if(double.IsNaN(beta))
{
candidateViolations.Add(new CandidateViolation(symbol,"No Beta violation."));
continue;
}
}
// Get the benchmark pricing low pricing data and check the slope of previous lows; only if Beta of candidate is >= LowSlopeBetaThreshhold
// The idea behind this check is that a high beta stock will track to the benchmark. So if the benchmark lows are forming a downward pattern then we
// assume that this is a somewhat bearish condition. The config has the setting at a 15 day check and the threshold beta set to 1.00
if(config.UseLowSlopeBetaCheck && fundamental.Beta>=config.LowSlopeBetaThreshhold)
if(config.UseLowSlopeBetaCheck && beta >= config.LowSlopeBetaThreshhold)
{
Prices benchmarkPrices=GBPriceCache.GetInstance().GetPrices(config.Benchmark,tradeDate,config.LowSlopeBetaDays);
pricesArray=Numerics.ToDouble(benchmarkPrices.GetPricesLow());
@@ -274,9 +288,6 @@ namespace MarketData.Generator.MGSHMomentum
continue;
}
// Zacks Rank. This is for informational purposes for now but may further it's use in the future.
ZacksRank zacksRank=ZacksRankDA.GetZacksRankOnOrBefore(symbol,tradeDate);
// Apply the PEScreening last because there an option to permit the inclusion of the high PE candidates if we have no other available candidates.
// The idea is to try to avoid high PE stocks as they are more likey to introduce drawdowns as backtests have shown.
if(config.UseMaxPEScreen && !double.IsNaN(fundamental.PE) && fundamental.PE>config.MaxPE)
@@ -289,14 +300,11 @@ namespace MarketData.Generator.MGSHMomentum
highPECandidate.DayCount=(int)MomentumGeneratorConstants.DayCount;
highPECandidate.IDIndicator=IDIndicator.Calculate(prices);
highPECandidate.Score=ScoreIndicator.Calculate(prices);
highPECandidate.MaxDrawdown=prices.MaxDrawdown();
highPECandidate.MaxUpside=prices.MaxUpside();
highPECandidate.PE=fundamental.PE;
highPECandidate.Beta=fundamental.Beta;
highPECandidate.Beta=beta;
highPECandidate.Velocity=velocity;
highPECandidate.Volume=price.Volume;
highPECandidate.Return1D=return1D;
if(null!=zacksRank)highPECandidate.ZacksRank=zacksRank.Rank;
highPECandidates.Add(highPECandidate);
continue;
}
@@ -309,14 +317,11 @@ namespace MarketData.Generator.MGSHMomentum
momentumCandidate.DayCount=(int)MomentumGeneratorConstants.DayCount;
momentumCandidate.IDIndicator=IDIndicator.Calculate(prices);
momentumCandidate.Score=ScoreIndicator.Calculate(prices);
momentumCandidate.MaxDrawdown=prices.MaxDrawdown();
momentumCandidate.MaxUpside=prices.MaxUpside();
momentumCandidate.PE=fundamental.PE;
momentumCandidate.Beta=fundamental.Beta;
momentumCandidate.Beta=beta;
momentumCandidate.Velocity=velocity;
momentumCandidate.Volume=price.Volume;
momentumCandidate.Return1D=return1D;
if(null!=zacksRank)momentumCandidate.ZacksRank=zacksRank.Rank;
momentumCandidates.Add(momentumCandidate);
} // for all symbols

View File

@@ -22,12 +22,9 @@ namespace MarketData.Generator.MGSHMomentum
CurrentPrice = position.CurrentPrice;
Volume = position.Volume;
Return1D = position.Return1D;
ZacksRank = position.ZacksRank;
CumReturn252 = position.CumReturn252;
IDIndicator = position.IDIndicator;
Score=position.Score;
MaxDrawdown = position.MaxDrawdown;
MaxUpside = position.MaxUpside;
Velocity = position.Velocity;
PE = position.PE;
Beta = position.Beta;
@@ -50,12 +47,9 @@ namespace MarketData.Generator.MGSHMomentum
position.CurrentPrice=positionToClone.CurrentPrice;
position.Volume=positionToClone.Volume;
position.Return1D=positionToClone.Return1D;
position.ZacksRank=positionToClone.ZacksRank;
position.CumReturn252=positionToClone.CumReturn252;
position.IDIndicator=positionToClone.IDIndicator;
position.Score=positionToClone.Score;
position.MaxDrawdown=positionToClone.MaxDrawdown;
position.MaxUpside=positionToClone.MaxUpside;
position.Velocity=positionToClone.Velocity;
position.PE=positionToClone.PE;
position.Beta=positionToClone.Beta;
@@ -80,12 +74,10 @@ namespace MarketData.Generator.MGSHMomentum
public double Return1D{get;set;}
public double GainLoss{get{return MarketValue-Exposure;}}
public double GainLossPcnt{get{return (MarketValue-Exposure)/Exposure;}}
public String ZacksRank{get;set;}
// public String ZacksRank{get;set;}
public double CumReturn252{get;set;}
public double IDIndicator{get;set;}
public double Score{get;set;}
public double MaxDrawdown{get;set;}
public double MaxUpside{get;set;}
public double Velocity{get;set;}
public double PE{get;set;}
public double Beta{get;set;}
@@ -107,12 +99,9 @@ namespace MarketData.Generator.MGSHMomentum
nvpCollection.Add(new NVP("CurrentPrice",CurrentPrice.ToString()));
nvpCollection.Add(new NVP("Volume",Volume.ToString()));
nvpCollection.Add(new NVP("Return1D",Return1D.ToString()));
nvpCollection.Add(new NVP("ZacksRank",ZacksRank));
nvpCollection.Add(new NVP("CumReturn252",CumReturn252.ToString()));
nvpCollection.Add(new NVP("IDIndicator",IDIndicator.ToString()));
nvpCollection.Add(new NVP("Score",Score.ToString()));
nvpCollection.Add(new NVP("MaxDrawdown",MaxDrawdown.ToString()));
nvpCollection.Add(new NVP("MaxUpside",MaxUpside.ToString()));
nvpCollection.Add(new NVP("Velocity",Velocity.ToString()));
nvpCollection.Add(new NVP("PE",PE.ToString()));
nvpCollection.Add(new NVP("Beta",Beta.ToString()));
@@ -137,11 +126,8 @@ namespace MarketData.Generator.MGSHMomentum
position.CurrentPrice=nvpDictionary["CurrentPrice"].Get<double>();
position.Volume=nvpDictionary["Volume"].Get<long>();
position.Return1D=nvpDictionary["Return1D"].Get<long>();
if(nvpDictionary.ContainsKey("ZacksRank"))position.ZacksRank=nvpDictionary["ZacksRank"].Get<String>();
position.CumReturn252=nvpDictionary["CumReturn252"].Get<long>();
position.IDIndicator=nvpDictionary["IDIndicator"].Get<double>();
if(nvpDictionary.ContainsKey("MaxDrawdown"))position.MaxDrawdown=nvpDictionary["MaxDrawdown"].Get<double>();
if(nvpDictionary.ContainsKey("MaxUpside"))position.MaxUpside=nvpDictionary["MaxUpside"].Get<double>();
position.Velocity=nvpDictionary["Velocity"].Get<double>();
position.PE=nvpDictionary["PE"].Get<double>();
position.Beta=nvpDictionary["Beta"].Get<double>();
@@ -169,7 +155,7 @@ namespace MarketData.Generator.MGSHMomentum
{
if (Utility.IsEpoch(SellDate) && double.IsNaN(CurrentPrice))
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0},{1},{2},{3},{4},{5},{6},N/A,N/A,N/A,N/A,N/A,{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},{18}",
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0},{1},{2},{3},{4},{5},{6},N/A,N/A,N/A,N/A,N/A,{7},{8},{9},{10},{11},{12},{13},{14},{15},{16}",
Symbol,
Utility.DateTimeToStringMMHDDHYYYY(PurchaseDate),
Shares,
@@ -180,8 +166,6 @@ namespace MarketData.Generator.MGSHMomentum
Utility.AddQuotes(Utility.FormatPercent(CumReturn252)),
Utility.AddQuotes(Utility.FormatNumber(IDIndicator)),
Utility.AddQuotes(Utility.FormatNumber(Score)),
Utility.AddQuotes(Utility.FormatPercent(MaxDrawdown)),
Utility.AddQuotes(Utility.FormatPercent(MaxUpside)),
Utility.AddQuotes(Utility.FormatPercent(Velocity)),
Utility.AddQuotes(Utility.FormatNumber(PE)),
Utility.AddQuotes(Utility.FormatNumber(Beta)),
@@ -193,7 +177,7 @@ namespace MarketData.Generator.MGSHMomentum
}
else
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},{18},{19},{20},{21},{22},{23}",
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},{18},{19},{20},{21}",
Symbol,
Utility.DateTimeToStringMMHDDHYYYY(PurchaseDate),
Shares,
@@ -209,8 +193,6 @@ namespace MarketData.Generator.MGSHMomentum
Utility.AddQuotes(Utility.FormatPercent(CumReturn252)),
Utility.AddQuotes(Utility.FormatNumber(IDIndicator)),
Utility.AddQuotes(Utility.FormatNumber(Score)),
Utility.AddQuotes(Utility.FormatPercent(MaxDrawdown)),
Utility.AddQuotes(Utility.FormatPercent(MaxUpside)),
Utility.AddQuotes(Utility.FormatPercent(Velocity)),
Utility.AddQuotes(Utility.FormatNumber(PE)),
Utility.AddQuotes(Utility.FormatNumber(Beta)),