Initial Commit

This commit is contained in:
2026-04-03 16:26:26 -04:00
parent 4726475694
commit e6b3cf6b08
54 changed files with 388264 additions and 0 deletions

2
.gitignore vendored
View File

@@ -174,3 +174,5 @@ cython_debug/
# PyPI configuration file # PyPI configuration file
.pypirc .pypirc
embeddings_cache_meta.json
embeddings_cache.npz

BIN
.python-version Normal file

Binary file not shown.

View File

@@ -0,0 +1,178 @@
using MarketData.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MarketData.Generator.Momentum
{
public class ActivePositions : Dictionary<int,Positions>
{
public ActivePositions()
{
}
public double GetExposure()
{
double exposure=0.00;
List<int> keys = new List<int>(Keys);
for(int index=0;index<keys.Count;index++)
{
List<Position> positions=this[keys[index]];
if(null==positions||0==positions.Count)continue;
exposure+=(from Position position in positions select position.Exposure).Sum();
}
return exposure;
}
public List<String> GetSymbols()
{
return SymbolsHeld();
}
public Position FindPosition(String symbol,DateTime purchaseDate)
{
Position foundPosition=null;
List<int> keys=new List<int>(this.Keys);
for(int index=0;index<keys.Count;index++)
{
Positions positions=this[keys[index]];
foreach(Position position in positions)
{
if(position.Symbol.Equals(symbol)&&position.PurchaseDate.Date.Equals(purchaseDate.Date))
{
foundPosition=position;
break;
}
}
}
return foundPosition;
}
public int FindSlotForPosition(Position searchPosition)
{
List<int> keys=new List<int>(this.Keys);
for(int index=0;index<keys.Count;index++)
{
Positions positions=this[keys[index]];
foreach(Position position in positions)
{
if(position == searchPosition)return index;
}
}
return -1;
}
public bool Remove(Position searchPosition)
{
List<int> keys = new List<int>(this.Keys);
for (int index = 0; index < keys.Count; index++)
{
Positions positions = this[keys[index]];
foreach (Position slotPosition in positions)
{
if (slotPosition == searchPosition)
{
positions.Remove(searchPosition);
return true;
}
}
}
return false;
}
public Positions GetPositions()
{
Positions positionsCollection=new Positions();
List<int> keys=new List<int>(this.Keys);
Dictionary<String,String> symbols=new Dictionary<String,String>();
for(int index=0;index<keys.Count;index++)
{
Positions positions=this[keys[index]];
foreach(Position position in positions)
{
positionsCollection.Add(position);
}
}
return positionsCollection;
}
public List<String> SymbolsHeld()
{
List<int> keys = new List<int>(this.Keys);
Dictionary<String, String> symbols = new Dictionary<String, String>();
for (int index = 0; index < keys.Count; index++)
{
Positions positions = this[keys[index]];
foreach (Position position in positions)
{
if (!symbols.ContainsKey(position.Symbol)) symbols.Add(position.Symbol, position.Symbol);
}
}
return new List<String>(symbols.Keys);
}
public double GetMarketValue()
{
int count=Count;
double marketValue=0.00;
List<int> keys=new List<int>(this.Keys);
for(int index=0;index<keys.Count;index++)
{
List<Position> positions=this[keys[index]];
if(null==positions||0==positions.Count)continue;
marketValue+=(from Position position in positions select position.MarketValue).Sum();
}
return marketValue;
}
public double GetGainLoss()
{
int count=Count;
double marketValue=0.00;
double exposure=0.00;
List<int> keys = new List<int>(this.Keys);
for(int index=0;index<keys.Count;index++)
{
List<Position> positions=this[keys[index]];
if(null==positions||0==positions.Count)continue;
exposure+=(from Position position in positions select position.Exposure).Sum();
marketValue+=(from Position position in positions select position.MarketValue).Sum();
}
return marketValue-exposure;
}
public double GetGainLossPercent()
{
double exposure=GetExposure();
double marketValue=GetMarketValue();
if(0.00==exposure)return exposure;
return (marketValue-exposure)/exposure;
}
public void Display()
{
List<int> keys = new List<int>(this.Keys);
for(int index=0;index<keys.Count;index++)
{
Positions positions=this[keys[index]];
DateTime purchaseDate=(from Position position in positions select position.PurchaseDate).Distinct().FirstOrDefault();
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("******************* B U Y S F O R {0} *****************",Utility.DateTimeToStringMMHDDHYYYY(purchaseDate)));
positions.Display();
}
}
public void AddFromNVPCollection(NVPCollection nvpCollection)
{
SlotPosition slotPosition=SlotPosition.FromNVPCollection(nvpCollection);
if(!ContainsKey(slotPosition.Slot))
{
Add(slotPosition.Slot,new Positions());
}
this[slotPosition.Slot].Add(slotPosition.ToPosition());
}
public List<NVPCollections> ToNVPCollections()
{
List<int> keys=new List<int>(Keys);
List<NVPCollections> nvpCollectionsList=new List<NVPCollections>();
for(int index=0;index<keys.Count;index++)
{
Positions positions=this[keys[index]];
SlotPositions slotPositions=new SlotPositions(keys[index],positions);
NVPCollections nvpCollections=slotPositions.ToNVPCollections();
nvpCollectionsList.Add(nvpCollections);
}
return nvpCollectionsList;
}
}
}

View File

@@ -0,0 +1,758 @@
using System;
using System.Collections.Generic;
using MarketData.MarketDataModel;
using MarketData.DataAccess;
using MarketData.Utils;
using System.Linq;
using MarketData.Cache;
using MarketData.Generator.Model;
namespace MarketData.Generator.Momentum
{
public class MomentumBacktest
{
private double NonTradeableCash{get;set;}
private double CashBalance{get;set;}
private MGConfiguration Configuration{get;set;}
private int HoldingPeriod{get{return Configuration.HoldingPeriod;}}
private int MaxPositions{get{return Configuration.MaxPositions;}}
private ActivePositions ActivePositions{get;set;}
private Positions AllPositions{get;set;}
private int Cycle{get;set;}
private DateTime TradeDate{get;set;}
private DateTime StartDate{get;set;}
private DateTime AnalysisDate{get;set;}
private String PathSessionFileName{get;set;}
// ******************************************************************************************************************************************************
//************************************************************** D I S P L A Y G A I N L O S S *****************************************************
// ******************************************************************************************************************************************************
public static void DisplayGainLoss(String paramPathSessionFileName)
{
Profiler profiler=new Profiler();
ModelPerformanceSeries performanceSeries=GetModelPerformance(paramPathSessionFileName);
if(null==performanceSeries)return;
MDTrace.WriteLine("Date,Exposure,MarketValue,GainLossDOD,GainLoss,CumulativeGainLoss,R,(1+R),CumProd,CumProd-1,ClosedPositions");
foreach(ModelPerformanceItem modelPerformanceItem in performanceSeries)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\",\"{9}\",\"{10}\"",
modelPerformanceItem.Date.ToShortDateString(),
Utility.FormatCurrency(modelPerformanceItem.Exposure),
Utility.FormatCurrency(modelPerformanceItem.MarketValue),
Utility.FormatCurrency(modelPerformanceItem.GainLossDOD),
Utility.FormatCurrency(modelPerformanceItem.GainLoss),
Utility.FormatCurrency(modelPerformanceItem.CumulativeGainLoss),
Utility.FormatNumber(modelPerformanceItem.R,4),
Utility.FormatNumber(modelPerformanceItem.OnePlusR,4),
Utility.FormatNumber(modelPerformanceItem.CumProd,4),
Utility.FormatNumber(modelPerformanceItem.CumProdMinusOne,4),
modelPerformanceItem.ClosedPositions));
}
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Done, took {0}(ms)",profiler.End()));
}
public static ModelPerformanceSeries GetModelPerformance(String paramPathSessionFileName)
{
try
{
MGSessionParams sessionParams=MGSessionManager.RestoreSession(paramPathSessionFileName);
return GetModelPerformance(sessionParams);
}
catch(Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
return null;
}
}
// Calculates the expectation for the model ( Percent of Winning Trades * Average Gain)/(Percent Losing Trades * Average Loss)
// The expectation should be above zero
public static ModelStatistics GetModelStatistics(MGSessionParams sessionParams)
{
ModelStatistics modelStatistics=new ModelStatistics();
try
{
if(null==sessionParams||null==sessionParams.AllPositions||0==sessionParams.AllPositions.Count) return modelStatistics;
double totalTrades=sessionParams.AllPositions.Count;
double winningTrades=sessionParams.AllPositions.Where(x => x.GainLoss>=0.00).Count();
double losingTrades=sessionParams.AllPositions.Where(x => x.GainLoss<0.00).Count();
double averageWinningTrade=sessionParams.AllPositions.Where(x => x.GainLoss>=0.00).Average(x => x.GainLossPcnt)*100.00;
double averageLosingTrade=sessionParams.AllPositions.Where(x => x.GainLoss<0.00).Average(x => x.GainLossPcnt)*100.00;
double percentWinningTrades=(winningTrades/(double)sessionParams.AllPositions.Count)*100.00;
double percentLosingTrades=(losingTrades/(double)sessionParams.AllPositions.Count)*100.00;
double expectation=(percentWinningTrades*averageWinningTrade)/(percentLosingTrades*Math.Abs(averageLosingTrade));
modelStatistics.TotalTrades=(long)totalTrades;
modelStatistics.WinningTrades=(long)winningTrades;
modelStatistics.LosingTrades=(long)losingTrades;
modelStatistics.AverageWinningTradePercentGain=averageWinningTrade;
modelStatistics.AverageLosingTradePercentLoss=averageLosingTrade;
modelStatistics.WinningTradesPercent=percentWinningTrades;
modelStatistics.LosingTradesPercent=percentLosingTrades;
modelStatistics.Expectancy=expectation;
return modelStatistics;
}
catch(Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
return modelStatistics;
}
}
public static ModelPerformanceSeries GetModelPerformance(MGSessionParams sessionParams)
{
Profiler profiler=new Profiler();
ModelPerformanceSeries performanceSeries=new ModelPerformanceSeries();
DateGenerator dateGenerator=new DateGenerator();
try
{
if(null==sessionParams)return null;
MarketData.Generator.Momentum.Positions combinedPositions=sessionParams.GetCombinedPositions();
DateTime minDate=combinedPositions.Min(x => x.PurchaseDate);
DateTime maxDate=PricingDA.GetLatestDate();
double prevGainLoss=double.NaN;
LocalPriceCache.GetInstance().RemoveDate(maxDate);
List<DateTime> historicalDates=dateGenerator.GenerateHistoricalDates(minDate,maxDate);
foreach(DateTime currentDate in historicalDates)
{
MarketData.Generator.Momentum.Positions openPositions=new MarketData.Generator.Momentum.Positions(combinedPositions.Where(x => (x.PurchaseDate<=currentDate&&(!Utility.IsEpoch(x.SellDate)&&x.SellDate>currentDate))||(x.PurchaseDate<=currentDate&&Utility.IsEpoch(x.SellDate))).ToList());
MarketData.Generator.Momentum.Positions closedPositions=new MarketData.Generator.Momentum.Positions(combinedPositions.Where(x => (!Utility.IsEpoch(x.SellDate)&&x.SellDate.Equals(currentDate))).ToList());
if(0==openPositions.Count&&0==closedPositions.Count) continue;
double gainLoss=0.00;
double gainLossClosedPositions=0.00;
double exposure=0.00;
double marketValue=0.00;
ModelPerformanceItem performanceItem=new ModelPerformanceItem();
foreach(MarketData.Generator.Momentum.Position openPosition in openPositions)
{
exposure+=openPosition.Shares*openPosition.PurchasePrice;
if(!LocalPriceCache.GetInstance().ContainsPrice(openPosition.Symbol,currentDate))
{
Prices prices=PricingDA.GetPricesForward(openPosition.Symbol,currentDate,PricingDA.ForwardLookingDays);
LocalPriceCache.GetInstance().Add(prices);
}
Price price=LocalPriceCache.GetInstance().GetPrice(openPosition.Symbol,currentDate);
if(null==price)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("No price for {0} on {1}",openPosition.Symbol,currentDate.ToShortDateString()));
}
else
{
gainLoss+=((price.Close*openPosition.Shares)-(openPosition.PurchasePrice*openPosition.Shares));
marketValue+=(price.Close*openPosition.Shares);
}
}
foreach(MarketData.Generator.Momentum.Position closedPosition in closedPositions)
{
double gainLossPosition=(closedPosition.CurrentPrice*closedPosition.Shares)-(closedPosition.PurchasePrice*closedPosition.Shares);
gainLossClosedPositions+=gainLossPosition;
}
performanceItem.Date=currentDate;
performanceItem.Exposure=exposure;
performanceItem.MarketValue=marketValue;
performanceItem.GainLossDOD=double.IsNaN(prevGainLoss)?gainLoss:(gainLoss-prevGainLoss)+gainLossClosedPositions;
performanceItem.GainLoss=gainLoss+gainLossClosedPositions;
performanceItem.ClosedPositions=closedPositions.Count>0?true:false;
performanceSeries.Add(performanceItem);
prevGainLoss=gainLoss;
}
performanceSeries.CalculatePerformance();
return performanceSeries;
}
catch(Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
return null;
}
finally
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Done, total took {0}(ms)",profiler.End()));
}
}
// ******************************************************************************************************************************************************
//************************************************************** D I S P L A Y S E S S I O N *****************************************************
// ******************************************************************************************************************************************************
public void DisplaySession(String paramPathSessionFileName)
{
if(null==paramPathSessionFileName)return;
PathSessionFileName=paramPathSessionFileName;
MGSessionParams sessionParams=null;
if(null==(sessionParams=RestoreSession()))
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Error loading session file {0}",paramPathSessionFileName));
return;
}
Console.WriteLine(String.Format("SessionFile:{0} Last Updated:{1}",paramPathSessionFileName,sessionParams.LastUpdated));
Configuration.DisplayConfiguration();
MDTrace.WriteLine(LogLevel.DEBUG,"************** A L L P O S I T I O N S *************");
AllPositions=new Positions((from Position position in AllPositions orderby position.PurchaseDate ascending select position).ToList());
AllPositions.Display();
MDTrace.WriteLine(LogLevel.DEBUG,"************** A C T I V E P O S I T I O N S *************");
ActivePositions.Display();
MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P G A I N E R S *************");
AllPositions.DisplayTopFive();
MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P L O S S E R S *************");
AllPositions.DisplayBottomFive();
// DisplayBalanceFromPositions();
DisplayLatestModelPerformance(paramPathSessionFileName);
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("StartDate:{0}",Utility.DateTimeToStringMMHDDHYYYY(StartDate)));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TradeDate:{0}",Utility.DateTimeToStringMMHDDHYYYY(TradeDate)));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("AnalysisDate:{0}",Utility.DateTimeToStringMMHDDHYYYY(AnalysisDate)));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Next Slot:{0}",Cycle));
}
// ******************************************************************************************************************************************************
//******************************************************* L I Q U I D A T E A L L P O S I T I O N S ***********************************************
// ******************************************************************************************************************************************************
public void MGLiquididate(String pathSessionFile,DateTime? tradeDate)
{
if (null == pathSessionFile) return;
MGSessionParams sessionParams=null;
PathSessionFileName = pathSessionFile;
if (null == (sessionParams = RestoreSession()))
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Error loading session file {0}", pathSessionFile));
return;
}
MDTrace.WriteLine(LogLevel.DEBUG, "************** L I Q U I D A T E P O S I T I O N S *************");
if (null == ActivePositions || 0 == ActivePositions.Count)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("No active positions in file {0}", pathSessionFile));
return;
}
if(null==tradeDate)tradeDate = PricingDA.GetLatestDate(ActivePositions.GetSymbols());
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Trade date:{0}", Utility.DateTimeToStringMMHDDHYYYY(tradeDate.Value)));
for (int slotIndex = 0; slotIndex < ActivePositions.Count; slotIndex++)
{
Positions slotPositions = ActivePositions[slotIndex];
SellPositions(slotPositions, tradeDate.Value);
MDTrace.WriteLine(LogLevel.DEBUG, "********************* S E L L *********************");
slotPositions.Display();
AllPositions.Add(slotPositions);
DisplaySales(slotPositions, TradeDate);
CashBalance += slotPositions.MarketValue;
ActivePositions[slotIndex].Clear();
}
GBPriceCache.GetInstance().Dispose();
SaveSession();
}
// ******************************************************************************************************************************************************
// ****************************************************************** C L O S E **********************************************************************
// ******************************************************************************************************************************************************
public bool ClosePosition(String symbol,DateTime purchaseDate,DateTime sellDate,double sellPrice,String pathSessionFile)
{
if(null==pathSessionFile) return false;
MGSessionParams sessionParams=null;
PathSessionFileName=pathSessionFile;
if(null==(sessionParams=RestoreSession()))
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Error loading session file {0}",pathSessionFile));
return false;
}
if(!BackupSession()) return false;
Positions activePositions = ActivePositions.GetPositions();
Position position=activePositions.Where(x => x.Symbol.Equals(symbol) && x.PurchaseDate.Equals(purchaseDate)).FirstOrDefault();
if(null==position) // if it is not in the active positions then the position is already closed and we are modifying either the sell date or the sell price
{
position=AllPositions.Where(x => x.Symbol.Equals(symbol) && x.PurchaseDate.Equals(purchaseDate)).FirstOrDefault();
if(null==position)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot locate position for symbol '{0}' purchased on {1}.",symbol,purchaseDate.ToShortDateString()));
return false;
}
position.SellDate = sellDate;
CashBalance -= position.MarketValue;
position.CurrentPrice = sellPrice;
CashBalance += position.MarketValue;
SaveSession();
return true;
}
position.SellDate = sellDate;
position.CurrentPrice = sellPrice;
CashBalance += position.MarketValue;
ActivePositions.Remove(position);
AllPositions.Add(position);
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Position for symbol '{0}' purchased on {1} is now closed.",symbol,purchaseDate.ToShortDateString()));
SaveSession();
return true;
}
// ******************************************************************************************************************************************************
// *************************************************************************** E D I T ******************************************************************
// ******************************************************************************************************************************************************
public bool EditPosition(String symbol,DateTime purchaseDate,double purchasePrice,String pathSessionFile)
{
if(null==pathSessionFile) return false;
PathSessionFileName=pathSessionFile;
MGSessionParams sessionParams=null;
if(null==(sessionParams=RestoreSession()))
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Error loading session file {0}",pathSessionFile));
return false;
}
if(!BackupSession()) return false;
Positions activePositions = ActivePositions.GetPositions();
Position position=activePositions.Where(x => x.Symbol.Equals(symbol) && x.PurchaseDate.Equals(purchaseDate)).FirstOrDefault();
if(null==position)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot locate position for symbol '{0}' purchased on {1}.",symbol,purchaseDate.ToShortDateString()));
return false;
}
if(!position.PurchaseDate.Equals(purchaseDate)) position.PurchaseDate=purchaseDate;
if(!position.PurchasePrice.Equals(purchasePrice))
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Adjusting Cash for Position for symbol '{0}' purchased on {1}. Original Price: {2} New Price: {3} Change in Cash: {4}",
symbol,purchaseDate.ToShortDateString(),
Utility.FormatCurrency(position.PurchasePrice),
Utility.FormatCurrency(purchasePrice),
Utility.FormatCurrency((position.PurchasePrice-purchasePrice)*position.Shares)));
CashBalance+=(position.PurchasePrice-purchasePrice)*position.Shares;
position.PurchasePrice=purchasePrice;
}
SaveSession();
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Position for symbol '{0}' purchased on {1} has been modified and saved.",symbol,purchaseDate.ToShortDateString()));
return true;
}
// ******************************************************************************************************************************************************
// ****************************************************************** B A C K T E S T *****************************************************************
// ******************************************************************************************************************************************************
// Ideally, startDate should be November,February,May,August
// paramStartDate is startDate {ORIGINAL START DATE}
// paramAnalysisDate is endDate {TODAY}
public BacktestResult PerformBacktest(DateTime paramStartDate,DateTime paramAnalysisDate,String paramPathSessionFileName,MGConfiguration configuration)
{
BacktestResult backTestResult=new BacktestResult();
DateGenerator dateGenerator=new DateGenerator();
Configuration=configuration;
CashBalance=Configuration.InitialCash;
ActivePositions=new ActivePositions();
AllPositions=new Positions();
StartDate=paramStartDate;
TradeDate=paramStartDate;
AnalysisDate=paramAnalysisDate;
PathSessionFileName=paramPathSessionFileName;
MGSessionParams sessionParams=null;
Cycle=0;
if(AnalysisDate.Date>Today().Date)return backTestResult;
if(Utility.IsEpoch(AnalysisDate))AnalysisDate=dateGenerator.GetPrevBusinessDay(Today()); // Ensure AnalysisDate is not a weekend or holiday
TradeDate =dateGenerator.GetCurrentMonthEnd(StartDate);
if(TradeDate>AnalysisDate)
{
int startMonth=StartDate.Month;
TimeSpan timeSpan=new TimeSpan();
if((new int[]{12,3,6,9}).Any(x=>x.Equals(startMonth)))timeSpan=new TimeSpan(30,0,0,0);
else if((new int[]{1,4,7,10}).Any(x=>x.Equals(startMonth)))timeSpan=new TimeSpan(60,0,0,0);
else if((new int[]{2,5,8,11}).Any(x=>x.Equals(startMonth)))timeSpan=new TimeSpan(90,0,0,0);
StartDate=StartDate-timeSpan;
TradeDate=dateGenerator.GetCurrentMonthEnd(StartDate);
}
if(null!=PathSessionFileName)sessionParams=RestoreSession();
if(null!=sessionParams)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Using session file {0}, Last updated {1}",paramPathSessionFileName,sessionParams.LastUpdated));
}
Configuration.DisplayConfiguration();
DisplayBalance();
while(true)
{
if(TradeDate>AnalysisDate)break;
int slotIndex=(int)(((double)Cycle)%((double)(HoldingPeriod)));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TRADE DATE {0} , ANALYSIS DATE {1}",Utility.DateTimeToStringMMHDDHYYYY(TradeDate),Utility.DateTimeToStringMMHDDHYYYY(AnalysisDate)));
if(!ActivePositions.ContainsKey(slotIndex))
{
Positions positions = null;
positions=BuyPositions(TradeDate,AnalysisDate,CashBalance/((double)HoldingPeriod-(double)ActivePositions.Count),SymbolsHeld(TradeDate));
MDTrace.WriteLine(LogLevel.DEBUG, "******************** B U Y ********************");
if(CashBalance-positions.Exposure<0.00)
{
positions.Clear();
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));
}
positions.Display();
ActivePositions.Add(slotIndex,positions);
DisplayPurchases(positions,TradeDate);
CashBalance-=positions.Exposure;
DisplayBalance();
}
else
{
Positions slotPositions=ActivePositions[slotIndex];
List<String> closedSymbols = slotPositions.ConvertAll(x => x.Symbol); // capture the closed symbols so we don't re-enter the position (avoid wash trades)
SellPositions(slotPositions,TradeDate);
MDTrace.WriteLine(LogLevel.DEBUG,"********************* S E L L *********************");
slotPositions.Display();
AllPositions.Add(slotPositions);
DisplaySales(slotPositions, TradeDate);
CashBalance+=slotPositions.MarketValue;
ActivePositions[slotIndex].Clear();
DisplayBalance();
double cashAllocation=CashBalance;
cashAllocation = Math.Min(CashBalance, (ActivePositions.GetExposure() + CashBalance) / (double)HoldingPeriod);
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("CASH ALLOCATION:{0}",Utility.FormatCurrency(cashAllocation)));
Positions positions = null;
positions=BuyPositions(TradeDate,AnalysisDate,cashAllocation,new List<String>(SymbolsHeld(TradeDate).Concat(closedSymbols)));
MDTrace.WriteLine(LogLevel.DEBUG,"********************** B U Y ********************");
positions.Display();
if(CashBalance-positions.Exposure<=0.00)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));
break;
}
ActivePositions[slotIndex]=positions;
DisplayPurchases(positions, TradeDate);
CashBalance-=positions.Exposure;
DisplayBalance();
}
Cycle++;
TradeDate=dateGenerator.GetNextMonthEnd(TradeDate);
if(TradeDate>AnalysisDate)break;
} // WHILE TRUE
MDTrace.WriteLine(LogLevel.DEBUG,"RUN COMPLETE.");
if(null!=PathSessionFileName)SaveSession();
for(int slotIndex=0;slotIndex<HoldingPeriod;slotIndex++)
{
if(!ActivePositions.ContainsKey(slotIndex)||0==ActivePositions[slotIndex].Count())continue;
Positions slotPositions=ActivePositions[slotIndex];
SellPositions(slotPositions,AnalysisDate);
MDTrace.WriteLine(LogLevel.DEBUG,"********************* S E L L ********************");
slotPositions.Display();
AllPositions.Add(slotPositions);
DisplaySales(slotPositions, TradeDate);
CashBalance+=slotPositions.MarketValue;
ActivePositions[slotIndex].Clear();
}
MDTrace.WriteLine(LogLevel.DEBUG,"************** A L L P O S I T I O N S *************");
AllPositions=new Positions((from Position position in AllPositions orderby position.PurchaseDate ascending select position).ToList());
AllPositions.Display();
MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P G A I N E R S *************");
AllPositions.DisplayTopFive();
MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P L O S E R S *************");
AllPositions.DisplayBottomFive();
DisplayBalance();
backTestResult.Success=true;
backTestResult.CashBalance=CashBalance;
GBPriceCache.GetInstance().Dispose();
return backTestResult;
}
public List<String> SymbolsHeld(DateTime tradeDate)
{
List<String> symbolsHeld=ActivePositions.SymbolsHeld();
if(!Configuration.IncludeTradeMasterForSymbolsHeld)return symbolsHeld;
if(null == symbolsHeld)symbolsHeld=new List<String>();
PortfolioTrades portfolioTrades=PortfolioDA.GetOpenTradesAsOf(tradeDate);
if(null == portfolioTrades || 0==portfolioTrades.Count)return symbolsHeld;
symbolsHeld.AddRange(portfolioTrades.Symbols.Distinct());
return symbolsHeld;
}
// **********************************************************************************************************************************************************
// **************************************************************** G E T E X P O S U R E / M A R K E T V A L U E*****************************************
// **********************************************************************************************************************************************************
public RealtimeGainLoss GetRealtimeGainLoss(DateTime tradeDate)
{
int count=ActivePositions.Count;
double marketValue=0.00;
double exposure=0.00;
RealtimeGainLoss gainLoss=new RealtimeGainLoss();
for(int slotIndex=0;slotIndex<count;slotIndex++)
{
List<Position> positions=ActivePositions[slotIndex];
if(null==positions||0==positions.Count)continue;
foreach(Position position in positions)
{
Price price=GBPriceCache.GetInstance().GetRealtimePrice(position.Symbol);
if(null==price){MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot price {0} on {1}",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));continue;}
position.CurrentPrice=price.Close;
}
}
for(int slotIndex=0;slotIndex<count;slotIndex++)
{
List<Position> positions=ActivePositions[slotIndex];
if(null==positions||0==positions.Count)continue;
exposure+=(from Position position in positions select position.Exposure).Sum();
marketValue+=(from Position position in positions select position.MarketValue).Sum();
}
gainLoss.Exposure=exposure;
gainLoss.MarketValue=marketValue;
return gainLoss;
}
// **************************************************************************************************************************************************
// **************************************************************** S E L L P O S I T I O N S *****************************************************
// ***************************************************************************************************************************************************
private void SellPositions(Positions positions,DateTime sellDate)
{
foreach(Position position in positions)
{
Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,sellDate);
position.SellDate=sellDate;
if(null==price)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("**********Cannot locate a price for {0} on {1}**********",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(sellDate)));
position.CurrentPrice=position.PurchasePrice;
}
else position.CurrentPrice=price.Close;
}
}
private void SellPosition(Position position,DateTime sellDate)
{
Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,sellDate);
position.SellDate=sellDate;
if(null==price)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("**********Cannot locate a price for {0} on {1}**********",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(sellDate)));
position.CurrentPrice=position.PurchasePrice;
}
else position.CurrentPrice=price.Close;
}
// ***************************************************************************************************************************************************
// ******************************************************** B U Y P O S I T I O N S *************************************************************
// ***************************************************************************************************************************************************
private Positions BuyPositions(DateTime tradeDate, DateTime analysisDate, double cash, List<String> symbolsHeld)
{
MDTrace.WriteLine(LogLevel.DEBUG,"**BUYPOSITIONS**");
Positions positions = new Positions();
int positionCount = 0;
if (Configuration.BenchmarkMode) return BuyBenchmarkPositions(tradeDate, cash);
MomentumCandidates momentumCandidates = MomentumGenerator.GenerateMomentum(tradeDate, symbolsHeld, Configuration);
for (int index = 0; index < momentumCandidates.Count; index++)
{
MomentumCandidate momentumCandidate = momentumCandidates[index];
Price price = GBPriceCache.GetInstance().GetPrice(momentumCandidate.Symbol, tradeDate);
if (null == price)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", momentumCandidate.Symbol, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
continue;
}
Position position = new Position();
position.Symbol = momentumCandidate.Symbol;
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;
position.PurchaseDate = tradeDate;
position.PurchasePrice = price.Close;
position.CurrentPrice = price.Close;
position.Shares = (int)Math.Floor((cash / (double)MaxPositions) / position.PurchasePrice);
if (0 == position.Shares) continue; // if not able to purchase any shares, for example, if the share price exceeds our purchasing power then move on to the next candidate
positions.Add(position);
positionCount++;
if (positionCount >= MaxPositions) break;
}
if (0 == positions.Count && Configuration.UseFallbackCandidate) // if we don't get any signals then consider the fallback candidate options as per the configuration file
{
String fallbackCandidate = Configuration.FallbackCandidate;
if (null != Configuration.FallbackCandidateBestOf && !"".Equals(Configuration.FallbackCandidateBestOf))
{
QualityIndicator qualityIndicator=new QualityIndicator(Configuration.QualityIndicatorType);
fallbackCandidate=CandidateSelector.SelectBestCandidateSymbol(qualityIndicator,Utility.ToList(Configuration.FallbackCandidateBestOf),Configuration.FallbackCandidate,tradeDate);
}
Price price = GBPriceCache.GetInstance().GetPrice(fallbackCandidate, tradeDate);
if (null == price)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", fallbackCandidate, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
return positions;
}
Position position = new Position();
position.Symbol = fallbackCandidate;
position.PurchaseDate = tradeDate;
position.PurchasePrice = price.Close;
position.CurrentPrice = price.Close;
position.Shares = (int)Math.Floor(cash / position.PurchasePrice);
if(0 == position.Shares)return positions;
positions.Add(position);
}
return positions;
}
private Positions BuyBenchmarkPositions(DateTime tradeDate, double cash)
{
Positions positions = new Positions();
Price benchmarkPrice = GBPriceCache.GetInstance().GetPrice(Configuration.BenchmarkModeSymbol, tradeDate);
if (null == benchmarkPrice)
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", Configuration.BenchmarkModeSymbol, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
return positions;
}
Position position = new Position();
position.Symbol = Configuration.BenchmarkModeSymbol;
position.PurchaseDate = tradeDate;
position.PurchasePrice = benchmarkPrice.Close;
position.Shares = (int)Math.Floor(cash / position.PurchasePrice);
if (0 == position.Shares) return positions;
positions.Add(position);
return positions;
}
// *********************************************************************************************************************************************************************
// ************************************************************ E N D B U Y P O S I T I O N S ***********************************************
// *********************************************************************************************************************************************************************
private void DisplayLatestModelPerformance(String pathSessionFileName)
{
ModelPerformanceSeries performanceSeries=GetModelPerformance(pathSessionFileName);
if(null==performanceSeries || 0==performanceSeries.Count)return;
MDTrace.WriteLine("Date,Exposure,MarketValue,GainLossDoD,GainLoss,CumulativeGainLoss,R,(1+R),CumProd,CumProd-1,ClosedPositions");
ModelPerformanceItem modelPerformanceItem = performanceSeries[performanceSeries.Count-1]; // get the last record
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\",\"{9}\",\"{10}\"",
modelPerformanceItem.Date.ToShortDateString(),
Utility.FormatCurrency(modelPerformanceItem.Exposure),
Utility.FormatCurrency(modelPerformanceItem.MarketValue),
Utility.FormatCurrency(modelPerformanceItem.GainLossDOD),
Utility.FormatCurrency(modelPerformanceItem.GainLoss),
Utility.FormatCurrency(modelPerformanceItem.CumulativeGainLoss),
Utility.FormatNumber(modelPerformanceItem.R,4),
Utility.FormatNumber(modelPerformanceItem.OnePlusR,4),
Utility.FormatNumber(modelPerformanceItem.CumProd,4),
Utility.FormatNumber(modelPerformanceItem.CumProdMinusOne,4),
modelPerformanceItem.ClosedPositions));
}
private void DisplayBalance()
{
MDTrace.WriteLine(LogLevel.DEBUG,"*******************************************");
MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,AVAILABLE CASH,TOTAL ACCOUNT");
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2}",Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue()+CashBalance))));
for (int slotIndex = 0; slotIndex < ActivePositions.Count; slotIndex++)
{
Positions slotPositions = ActivePositions[slotIndex];
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("SLOT:{0} EXPOSURE:{1}",slotIndex,Utility.FormatCurrency(slotPositions.Exposure)));
}
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TOTAL EXPOSURE: {0}",Utility.FormatCurrency(ActivePositions.GetExposure())));
MDTrace.WriteLine(LogLevel.DEBUG,"*******************************************");
}
private void DisplayBalanceFromPositions()
{
MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,GAIN/LOSS,GAIN/LOSS(%),AVAILABLE CASH,TOTAL ACCOUNT");
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2},{3},{4}",
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetGainLoss())),
Utility.AddQuotes(Utility.FormatPercent(ActivePositions.GetGainLossPercent())),
Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue()+CashBalance))));
}
private void DisplayBalance(RealtimeGainLoss gainLoss)
{
MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,GAIN/LOSS,GAIN/LOSS(%),AVAILABLE CASH,TOTAL ACCOUNT");
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2},{3},{4}",
Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),
Utility.AddQuotes(Utility.FormatCurrency(gainLoss.GainLoss)),
Utility.AddQuotes(Utility.FormatPercent(gainLoss.GainLossPercent)),
Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
Utility.AddQuotes(Utility.FormatCurrency(gainLoss.MarketValue+CashBalance))));
}
// ****************************************************************************************************************************************
// ************************************************************* C O N T R O L T O D A Y ***********************************************
// ****************************************************************************************************************************************
public DateTime Today()
{
return DateTime.Now;
}
// ****************************************************************************************************************************************
// **************************************************************** S E S S I O N M A N A G E M E N T ***********************************
// ****************************************************************************************************************************************
public MGSessionParams RestoreSession()
{
try
{
MGSessionManager sessionManager=new MGSessionManager();
if(!MGSessionManager.SessionAvailable(PathSessionFileName))return null;
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Restoring session from '{0}'",PathSessionFileName));
MGSessionParams sessionParams=MGSessionManager.RestoreSession(PathSessionFileName);
TradeDate=sessionParams.TradeDate;
if(TradeDate.Date<AnalysisDate.Date)TradeDate=AnalysisDate; // AnalysisDate will not fall on a weekend or holiday
StartDate=sessionParams.StartDate;
Configuration=sessionParams.Configuration;
ActivePositions=sessionParams.ActivePositions;
AllPositions=sessionParams.AllPositions;
Cycle=sessionParams.Cycle;
CashBalance=sessionParams.CashBalance;
NonTradeableCash=sessionParams.NonTradeableCash;
return sessionParams;
}
catch(Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
return null;
}
}
public void SaveSession()
{
DateGenerator dateGenerator = new DateGenerator();
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Saving session to '{0}'", PathSessionFileName));
MGSessionParams sessionParams = new MGSessionParams();
if (Utility.IsEpoch(AnalysisDate)) AnalysisDate = dateGenerator.GetPrevBusinessDay(Today());
sessionParams.LastUpdated = Today();
sessionParams.TradeDate = TradeDate;
sessionParams.StartDate = StartDate;
sessionParams.AnalysisDate = AnalysisDate;
sessionParams.Configuration = Configuration;
sessionParams.ActivePositions = ActivePositions;
sessionParams.AllPositions = AllPositions;
sessionParams.Cycle = Cycle;
sessionParams.CashBalance = CashBalance;
sessionParams.NonTradeableCash = NonTradeableCash;
MGSessionManager.SaveSession(sessionParams,PathSessionFileName);
}
public bool BackupSession()
{
DateGenerator dateGenerator = new DateGenerator();
String[] parts = PathSessionFileName.Split('.');
String backupFileName = parts[0] + "_" + Utility.DateTimeToStringYYYYMMDDMMSSTT(DateTime.Now) + ".bak";
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Saving session to '{0}'", backupFileName));
MGSessionParams sessionParams = new MGSessionParams();
if (Utility.IsEpoch(AnalysisDate)) AnalysisDate = dateGenerator.GetPrevBusinessDay(Today());
sessionParams.LastUpdated = Today();
sessionParams.TradeDate = TradeDate;
sessionParams.StartDate = StartDate;
sessionParams.AnalysisDate = AnalysisDate;
sessionParams.Configuration = Configuration;
sessionParams.ActivePositions = ActivePositions;
sessionParams.AllPositions = AllPositions;
sessionParams.Cycle = Cycle;
sessionParams.CashBalance = CashBalance;
sessionParams.NonTradeableCash = NonTradeableCash;
return MGSessionManager.SaveSession(sessionParams,backupFileName);
}
/// <summary>
/// This makes for easier reading of the sales
/// </summary>
/// <param name="positions"></param>
/// <param name="tradeDate"></param>
private static void DisplaySales(Positions positions,DateTime tradeDate)
{
MDTrace.WriteLine(LogLevel.DEBUG,"********* S E L L S *********");
foreach (Position position in positions)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Sell {0} {1} @ {2} on {3}",position.Symbol,Utility.FormatNumber(position.Shares,3),Utility.FormatCurrency(position.CurrentPrice,2),tradeDate.ToShortDateString()));
}
}
/// <summary>
/// This makes for easier reading of the purchases
/// </summary>
/// <param name="positions"></param>
/// <param name="tradeDate"></param>
private static void DisplayPurchases(Positions positions, DateTime tradeDate)
{
MDTrace.WriteLine(LogLevel.DEBUG,"********* B U Y S *********");
foreach (Position position in positions)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Buy {0} {1} @ {2} on {3}",position.Symbol,Utility.FormatNumber(position.Shares,3),Utility.FormatCurrency(position.PurchasePrice,2),tradeDate.ToShortDateString()));
}
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MarketData.Generator.Momentum
{
public class BacktestResult
{
public BacktestResult()
{
}
public double CashBalance{get;set;}
public bool Success{get;set;}
}
}

View File

@@ -0,0 +1,98 @@
using MarketData.DataAccess;
using MarketData.MarketDataModel;
using MarketData.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace MarketData.Generator.Momentum
{
public class CandidateSelector
{
private static int DAY_COUNT=252;
private CandidateSelector()
{
}
public static String SelectBestCandidateSymbol(QualityIndicator qualityIndicator,List<String> candidates,String defaultCandidate,DateTime analysisDate)
{
String bestCandidate=defaultCandidate;
try
{
if(null==candidates||0==candidates.Count||Utility.IsEpoch(analysisDate)||null==defaultCandidate)return bestCandidate;
List<QualityIndicatorCandidate> qualityIndicatorCandidates=new List<QualityIndicatorCandidate>();
foreach(String candidate in candidates)
{
Prices prices=PricingDA.GetPrices(candidate,analysisDate,DAY_COUNT);
if(null==prices||0==prices.Count)continue;
QualityIndicatorCandidate qualityIndicatorCandidate=new QualityIndicatorCandidate();
qualityIndicatorCandidate.Symbol=candidate;
qualityIndicatorCandidate.IDIndicator=IDIndicator.Calculate(prices);
qualityIndicatorCandidate.Score=ScoreIndicator.Calculate(prices);
qualityIndicatorCandidates.Add(qualityIndicatorCandidate);
}
if(0==qualityIndicatorCandidates.Count)return bestCandidate;
if(qualityIndicator.Quality.Equals(QualityIndicator.QualityType.IDIndicator))
{
qualityIndicatorCandidates=(from QualityIndicatorCandidate qualityIndicatorCandidate in qualityIndicatorCandidates orderby qualityIndicatorCandidate.IDIndicator ascending select qualityIndicatorCandidate).ToList();
}
else
{
qualityIndicatorCandidates=(from QualityIndicatorCandidate qualityIndicatorCandidate in qualityIndicatorCandidates orderby qualityIndicatorCandidate.Score descending select qualityIndicatorCandidate).ToList();
}
return qualityIndicatorCandidates.Take(1).FirstOrDefault().Symbol;
}
catch(Exception exception)
{
MDTrace.WriteLine(String.Format("{0}",exception.ToString()));
return bestCandidate;
}
}
public static QualityIndicatorCandidate SelectBestCandidate(QualityIndicator qualityIndicator,List<String> candidates,String defaultCandidate,DateTime analysisDate)
{
QualityIndicatorCandidate bestCandidate=new QualityIndicatorCandidate();
try
{
if(null==candidates||0==candidates.Count||Utility.IsEpoch(analysisDate)||null==defaultCandidate)return null;
List<QualityIndicatorCandidate> qualityIndicatorCandidates=new List<QualityIndicatorCandidate>();
foreach(String candidate in candidates)
{
Prices prices=PricingDA.GetPrices(candidate,analysisDate,DAY_COUNT);
Fundamental fundamental=FundamentalDA.GetFundamentalMaxDate(candidate,analysisDate);
if(null==prices||0==prices.Count)continue;
QualityIndicatorCandidate qualityIndicatorCandidate=new QualityIndicatorCandidate();
qualityIndicatorCandidate.Symbol=candidate;
qualityIndicatorCandidate.IDIndicator=IDIndicator.Calculate(prices);
qualityIndicatorCandidate.Score=ScoreIndicator.Calculate(prices);
qualityIndicatorCandidate.CumReturn252=prices.GetCumulativeReturn();
qualityIndicatorCandidate.DayCount=DAY_COUNT;
qualityIndicatorCandidate.Return1D=prices.GetReturn1D();
if(null!=fundamental)
{
qualityIndicatorCandidate.PE=fundamental.PE;
qualityIndicatorCandidate.Beta=fundamental.Beta;
}
qualityIndicatorCandidates.Add(qualityIndicatorCandidate);
}
if(0==qualityIndicatorCandidates.Count)return bestCandidate;
if(qualityIndicator.Quality.Equals(QualityIndicator.QualityType.IDIndicator))
{
qualityIndicatorCandidates=(from QualityIndicatorCandidate qualityIndicatorCandidate in qualityIndicatorCandidates orderby qualityIndicatorCandidate.IDIndicator ascending select qualityIndicatorCandidate).ToList();
}
else
{
qualityIndicatorCandidates=(from QualityIndicatorCandidate qualityIndicatorCandidate in qualityIndicatorCandidates orderby qualityIndicatorCandidate.Score descending select qualityIndicatorCandidate).ToList();
}
return qualityIndicatorCandidates.Take(1).FirstOrDefault();
}
catch(Exception exception)
{
MDTrace.WriteLine(String.Format("{0}",exception.ToString()));
return bestCandidate;
}
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
namespace MarketData.Generator.Momentum
{
public class CandidateViolation
{
public CandidateViolation(String symbol, String reasonCategory)
{
Symbol = symbol;
ReasonCategory = reasonCategory;
}
public String Symbol {get; set;}
public String ReasonCategory {get; set;}
}
public class CandidateViolations : List<CandidateViolation>
{
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Text;
using MarketData.MarketDataModel;
using MarketData.DataAccess;
using MarketData.Utils;
using System.Linq;
using MarketData.Helper;
using MarketData.Numerical;
namespace MarketData.Generator.Momentum
{
public class MomentumCandidates : List<MomentumCandidate>
{
public MomentumCandidates()
{
}
public MomentumCandidates(List<MomentumCandidate> momentumCandidates)
{
foreach(MomentumCandidate momentumCandidate in momentumCandidates)Add(momentumCandidate);
}
}
public class MomentumCandidate
{
public String Symbol{get;set;}
public DateTime AnalysisDate{get;set;}
public double CumReturn252{get;set;}
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 String ZacksRank{get;set;}
public static String Header()
{
StringBuilder sb=new StringBuilder();
sb.Append("Symbol,AnalysisDate,Return,DayCount,IDIndicator,Score,MaxDrawdown,MaxUpside");
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));
return sb.ToString();
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Text;
using MarketData.MarketDataModel;
using MarketData.DataAccess;
using MarketData.Utils;
using System.Linq;
using MarketData.Helper;
using MarketData.Numerical;
namespace MarketData.Generator.Momentum
{
public class QualityIndicatorCandidate
{
public QualityIndicatorCandidate()
{
}
//public QualityIndicatorCandidate(String symbol,double idIndicator,double score)
//{
// Symbol=symbol;
// IDIndicator=idIndicator;
// Score=score;
//}
public String Symbol{get;set;}
public double CumReturn252{get;set;}
public double IDIndicator{get;set;}
public double Score{get;set;}
public int DayCount{get;set;}
public double PE{get;set;}
public double Beta{get;set;}
public double Return1D{get;set;}
}
// *********************************************************************************************************
public class IDIndicator
{
private IDIndicator()
{
}
//CountOf CountOf
//Negative Positive Return Sign IDIndicator
// 50 202 0.2 1 -60.31746032
// 100 152 0.2 1 -20.63492063
// 0 252 0.1 1 -100
// 0 252 0.2 1 -100
// The lower the IDIndicator the better
// This calculator deviates from the original by using an exponential decay on the stream of returns such that weigh distant returns a bit less than current returns
// and thereby attempt to give a more accurate picture of the quality of current returns.
public static double Calculate(Prices prices)
{
double idIndicator=0.00;
double cumulativeReturn=0.00;
float[] returns=prices.GetReturns();
ExponentialDecay exponentialDecay=new ExponentialDecay();
exponentialDecay.Prime(returns,2.00);
for(int index=0;index<returns.Length;index++)returns[index]=returns[index]*(float)exponentialDecay[index];
double positiveCount=(from float value in returns where value>0.00 select value).Count();
double negativeCount=(from float value in returns where value<0.00 select value).Count();
for(int index=0;index<returns.Length;index++)cumulativeReturn+=(double)returns[index];
double sign=cumulativeReturn>0.00?1.00:-1.00;
idIndicator=sign*((negativeCount/returns.Length)*100.00-(positiveCount/returns.Length)*100.00);
return idIndicator;
}
}
}

View File

@@ -0,0 +1,242 @@
using System;
using MarketData.Utils;
namespace MarketData.Generator.Momentum
{
public class MGConfiguration
{
// Operational Settings
public bool Verbose{get;set;}
// Basic settings
public int HoldingPeriod{get;set;}
public int MaxPositions{get;set;}
public String NoTradeSymbols{get;set;}
public String NoTradeFinancialSymbols{get;set;}
public double InitialCash{get;set;}
// Fundamental screenings
public double MarketCapLowerLimit{get;set;}
public bool UsePEScreen{get;set;} // If set this filter will ignore any security that is either missing a PE or if the PE is present but less than 0
public bool UseMaxPEScreen{get;set;} // control Max PE range check
public double MaxPE{get;set;} // if UseMaxPECheck is set this setting will ignore any security who's PE is greater. If PE is missing candidate will be accepted
public bool StrictMaxPE{get;set;} // if TRUE then UseMaxPEScreen is STRICT otherwise UseMaxPEScreen is SOFT RULE where >MaxPE is AVOIDED but used to fill the quota. The quota is filled by ranking highPECandidates by PE and taking the lower PE's up to the quota. Soft rule yields best result
// EBITDA screen
public bool UseEBITDAScreen{get;set;}
public bool UseRevenuePerShareScreen{get;set;}
// 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;}
public int LowSlopeBetaDays{get;set;}
public double LowSlopeBetaThreshhold{get;set;}
public bool UseCalcBeta{get;set;} // if this is set then use the betaCalc36 values from the beta generator that have been added to fundamentals, otherwise use the Beta from fundamentals (Yahoo/FinViz)
// MACD Settings : If MACD is being used then the process configures the MACD as per setup and eliminates candidates with a weak sell/strong sell signal in the signal days setting.
public bool UseMACD{get;set;}
public String MACDSetup="(12,26,9)"; // (8,17,9) is another alternative
public int MACDSignalDays{get;set;}
public bool MACDRejectWeakSellSignals{get;set;}
public bool MACDRejectStrongSellSignals{get;set;}
// Stochastics Settings : If Stochastics is being used then the process eliminates candidates with a weak sell/strong sell in the signal days setting.
public bool UseStochastics{get;set;}
public int StochasticsSignalDays{get;set;}
public bool StochasticsRejectWeakSells{get;set;}
public bool StochasticsRejectStrongSells{get;set;}
// FallbackCandidate : If this setting is true then the FallbackCandidate is purchased if there are no candidates that qualify in a given cycle
public bool UseFallbackCandidate{get;set;}
public String FallbackCandidate{get;set;}
public String FallbackCandidateBestOf{get;set;} // if this is set then the fallback candidate is selected as the best 252 day return
// Benchmark mode : If this is set to true then purchases the benchmark symbol instead of the momentum candidates
public bool BenchmarkMode{get;set;}
public String BenchmarkModeSymbol{get;set;}
// QualityIndicator
public String QualityIndicatorType{get;set;} // this can be either IDINDICATOR or SCOREINDICATOR. The SCOREINDICATOR was adopted from CMMomentum model
// IncludeTradeMasterForSymbolsHeld :
public bool IncludeTradeMasterForSymbolsHeld{get;set;} // if this is set to true then use both ActivePositions within the model as well as the master trades table to determine held positions.
public MGConfiguration()
{
Verbose=true; // user verbose output
BenchmarkMode=false; // set this to true if you want to run the model using just the benchmark symbol to buy.
BenchmarkModeSymbol="SPY"; // SPY is the default symbol to buy when testing
HoldingPeriod=3; // 3 is the default
MaxPositions=3; // 3 is the default
NoTradeSymbols="GBTC,YOKU,PNY,RFMD,ASAZY"; // ASAZY came up as candidate during 3/30 run but not available on Robinhood
NoTradeFinancialSymbols="U.S. Private Equity,U.S. Financials,U.S. Financial Services,U.S. Banking and Investment Services,Trading-Miscellaneous,Trading--Miscellaneous,Trading--Leveraged Equity,Trading--Leveraged Debt,Trading--Leveraged Commodities,Trading--Inverse Equity,Trading--Inverse Commodities,Tactical Allocation,Specialty Finance,Japan Financials,Savings & Cooperative Banks,Option Writing,Insurance Brokers,Insurance - Specialty,Insurance - Reinsurance,Insurance - Property & Casualty,Insurance - Life,Insurance - Diversified,Global Private Equity,Global Financials,Financial Services,Financial Exchanges,Financial,China Financials,Banks - Regional - US,Banks - Regional - Latin America,Banks - Global,Asset Management,Credit Services";
Benchmark="SPY"; // SPY is the default
// Candidate selection settings
MarketCapLowerLimit=1000000000; // 1B is the default
// PEScreen is off by default
UsePEScreen=false; // false is the default. This setting yields the most optimal performance in backtests. Checks for existance of PE and if exists ensures >0
// UseMaxPEScreen, MaxPE, and StrictPEExclusion
UseMaxPEScreen=true;
MaxPE=40;
StrictMaxPE=false;
UseEBITDAScreen=true; // true is the default
UseRevenuePerShareScreen=true; // true is the default
UseLowSlopeBetaCheck=true; // true is the default. this yields the most optimal performance in backtests
LowSlopeBetaDays=15; // 15 is the default. This yields the most optimal performance in backtests
LowSlopeBetaThreshhold=1.00; // (1.00) is the default This yields the most optimal performance in backtests
UseCalcBeta=true; // This is set to true by default
UseMACD=true; // true is the default
MACDSetup="(12,26,9)"; // (12,26,9)
MACDSignalDays=12; // 12 is the default
MACDRejectStrongSellSignals=false; // false is the default
MACDRejectWeakSellSignals=true; // true is the default
UseStochastics=true; // true is the default
StochasticsSignalDays=3; // 3 is the default
StochasticsRejectStrongSells=true; // true is the default
StochasticsRejectWeakSells=true; // true is the default
// Fallback candidate settings
UseFallbackCandidate=true; // True is the default
FallbackCandidate="SHV"; // "SHV" ICE U.S. Treasury Short Bond Index, "AGG" Barclays U.S. Aggregate Bond Index - AGG can have a slighty better return but is more volatile
FallbackCandidateBestOf="SHV,AGG,ACWX"; // if set then the fallback candidate is selected as the best 252 day return in this comma seperated list (i.e.) "SHV,ACWX,AGG"
// Set the QualityIndicator type to the IDIndicator
QualityIndicatorType=QualityIndicator.ToString(QualityIndicator.QualityType.IDIndicator);
IncludeTradeMasterForSymbolsHeld=false;
}
public void DisplayHeader()
{
MDTrace.WriteLine(LogLevel.DEBUG,"Setting,Value");
}
public NVPCollection ToNVPCollection()
{
NVPCollection nvpCollection=new NVPCollection();
nvpCollection.Add(new NVP("Verbose",Verbose.ToString()));
nvpCollection.Add(new NVP("BenchmarkMode",BenchmarkMode.ToString()));
nvpCollection.Add(new NVP("BenchmarkModeSymbol",BenchmarkModeSymbol.ToString()));
nvpCollection.Add(new NVP("HoldingPeriod",HoldingPeriod.ToString()));
nvpCollection.Add(new NVP("MaxPositions",MaxPositions.ToString()));
nvpCollection.Add(new NVP("NoTradeSymbols",NoTradeSymbols.ToString()));
nvpCollection.Add(new NVP("NoTradeFinancialSymbols",NoTradeFinancialSymbols.ToString()));
nvpCollection.Add(new NVP("Benchmark",Benchmark.ToString()));
nvpCollection.Add(new NVP("MarketCapLowerLimit",MarketCapLowerLimit.ToString()));
nvpCollection.Add(new NVP("UsePEScreen",UsePEScreen.ToString()));
nvpCollection.Add(new NVP("UseEBITDAScreen",UseEBITDAScreen.ToString()));
nvpCollection.Add(new NVP("UseRevenuePerShareScreen",UseRevenuePerShareScreen.ToString()));
nvpCollection.Add(new NVP("UseLowSlopeBetaCheck",UseLowSlopeBetaCheck.ToString()));
nvpCollection.Add(new NVP("LowSlopeBetaDays",LowSlopeBetaDays.ToString()));
nvpCollection.Add(new NVP("LowSlopeBetaThreshhold",LowSlopeBetaThreshhold.ToString()));
nvpCollection.Add(new NVP("UseCalcBeta",UseCalcBeta.ToString()));
nvpCollection.Add(new NVP("UseMACD",UseMACD.ToString()));
nvpCollection.Add(new NVP("MACDSetup",MACDSetup.ToString()));
nvpCollection.Add(new NVP("MACDSignalDays",MACDSignalDays.ToString()));
nvpCollection.Add(new NVP("MACDRejectStrongSellSignals",MACDRejectStrongSellSignals.ToString()));
nvpCollection.Add(new NVP("MACDRejectWeakSellSignals",MACDRejectWeakSellSignals.ToString()));
nvpCollection.Add(new NVP("UseStochastics",UseStochastics.ToString()));
nvpCollection.Add(new NVP("StochasticsSignalDays",StochasticsSignalDays.ToString()));
nvpCollection.Add(new NVP("StochasticsRejectStrongSells",StochasticsRejectStrongSells.ToString()));
nvpCollection.Add(new NVP("StochasticsRejectWeakSells",StochasticsRejectWeakSells.ToString()));
nvpCollection.Add(new NVP("UseFallbackCandidate",UseFallbackCandidate.ToString()));
nvpCollection.Add(new NVP("FallbackCandidate",FallbackCandidate.ToString()));
nvpCollection.Add(new NVP("FallbackCandidateBestOf",FallbackCandidateBestOf.ToString()));
nvpCollection.Add(new NVP("UseMaxPEScreen",UseMaxPEScreen.ToString()));
nvpCollection.Add(new NVP("MaxPE",MaxPE.ToString()));
nvpCollection.Add(new NVP("StrictMaxPE",StrictMaxPE.ToString()));
nvpCollection.Add(new NVP("QualityIndicatorType",QualityIndicatorType.ToString()));
nvpCollection.Add(new NVP("IncludeTradeMasterForSymbolsHeld",IncludeTradeMasterForSymbolsHeld.ToString()));
return nvpCollection;
}
public static MGConfiguration FromNVPCollection(NVPCollection nvpCollection)
{
MGConfiguration mgConfiguration=new MGConfiguration();
NVPDictionary nvpDictionary=nvpCollection.ToDictionary();
mgConfiguration.Verbose=nvpDictionary["Verbose"].Get<Boolean>();
mgConfiguration.BenchmarkMode=nvpDictionary["BenchmarkMode"].Get<Boolean>();
mgConfiguration.BenchmarkModeSymbol=nvpDictionary["BenchmarkModeSymbol"].Get<String>();
mgConfiguration.HoldingPeriod=nvpDictionary["HoldingPeriod"].Get<int>();
mgConfiguration.MaxPositions=nvpDictionary["MaxPositions"].Get<int>();
mgConfiguration.NoTradeSymbols=nvpDictionary["NoTradeSymbols"].Get<String>();
mgConfiguration.NoTradeFinancialSymbols=nvpDictionary["NoTradeFinancialSymbols"].Get<String>();
mgConfiguration.Benchmark=nvpDictionary["Benchmark"].Get<String>();
mgConfiguration.MarketCapLowerLimit=nvpDictionary["MarketCapLowerLimit"].Get<double>();
mgConfiguration.UsePEScreen=nvpDictionary["UsePEScreen"].Get<Boolean>();
mgConfiguration.UseMaxPEScreen=nvpDictionary["UseMaxPEScreen"].Get<Boolean>();
mgConfiguration.MaxPE=nvpDictionary["MaxPE"].Get<double>();
mgConfiguration.StrictMaxPE=nvpDictionary["StrictMaxPE"].Get<Boolean>();
mgConfiguration.UseEBITDAScreen=nvpDictionary["UseEBITDAScreen"].Get<Boolean>();
mgConfiguration.UseRevenuePerShareScreen=nvpDictionary["UseRevenuePerShareScreen"].Get<Boolean>();
mgConfiguration.UseLowSlopeBetaCheck=nvpDictionary["UseLowSlopeBetaCheck"].Get<Boolean>();
mgConfiguration.LowSlopeBetaDays=nvpDictionary["LowSlopeBetaDays"].Get<int>();
mgConfiguration.LowSlopeBetaThreshhold=nvpDictionary["LowSlopeBetaThreshhold"].Get<double>();
if(nvpDictionary.ContainsKey("UseCalcBeta"))mgConfiguration.UseCalcBeta=nvpDictionary["UseCalcBeta"].Get<bool>();
else mgConfiguration.UseCalcBeta=true;
mgConfiguration.UseMACD=nvpDictionary["UseMACD"].Get<Boolean>();
mgConfiguration.MACDSetup=nvpDictionary["MACDSetup"].Get<String>();
mgConfiguration.MACDSignalDays=nvpDictionary["MACDSignalDays"].Get<int>();
mgConfiguration.MACDRejectStrongSellSignals=nvpDictionary["MACDRejectStrongSellSignals"].Get<Boolean>();
mgConfiguration.MACDRejectWeakSellSignals=nvpDictionary["MACDRejectWeakSellSignals"].Get<Boolean>();
mgConfiguration.UseStochastics=nvpDictionary["UseStochastics"].Get<Boolean>();
mgConfiguration.StochasticsSignalDays=nvpDictionary["StochasticsSignalDays"].Get<int>();
mgConfiguration.StochasticsRejectStrongSells=nvpDictionary["StochasticsRejectStrongSells"].Get<Boolean>();
mgConfiguration.StochasticsRejectWeakSells=nvpDictionary["StochasticsRejectWeakSells"].Get<Boolean>();
mgConfiguration.UseFallbackCandidate=nvpDictionary["UseFallbackCandidate"].Get<Boolean>();
mgConfiguration.FallbackCandidate=nvpDictionary["FallbackCandidate"].Get<String>();
mgConfiguration.FallbackCandidateBestOf=nvpDictionary["FallbackCandidateBestOf"].Get<String>();
if(nvpDictionary.ContainsKey("QualityIndicatorType")) mgConfiguration.QualityIndicatorType=nvpDictionary["QualityIndicatorType"].Get<String>();
else mgConfiguration.QualityIndicatorType=QualityIndicator.ToString(QualityIndicator.QualityType.IDIndicator);
if(nvpDictionary.ContainsKey("IncludeTradeMasterForSymbolsHeld")) mgConfiguration.IncludeTradeMasterForSymbolsHeld=nvpDictionary["IncludeTradeMasterForSymbolsHeld"].Get<bool>();
else mgConfiguration.IncludeTradeMasterForSymbolsHeld=false;
return mgConfiguration;
}
public void DisplayConfiguration()
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Verbose,{0}",Verbose));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Holding Period,{0}",HoldingPeriod));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MaxPositions,{0}",MaxPositions));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("NoTradeSymbols,{0}",NoTradeSymbols));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("NoTradeFinancialSymbols,{0}",NoTradeFinancialSymbols));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Benchmark,{0}",Benchmark));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MarketCapLowerLimit,{0}",MarketCapLowerLimit));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UsePEScreen,{0}",UsePEScreen));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseMaxPEScreen,{0}",UseMaxPEScreen));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MaxPE,{0}",MaxPE));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("StrictMaxPE,{0}",StrictMaxPE));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseEBITDAScreen,{0}",UseEBITDAScreen));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseRevenuePerShareScreen,{0}",UseRevenuePerShareScreen));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseLowSlopeBetaCheck,{0}",UseLowSlopeBetaCheck));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("LowSlopeBetaDays,{0}",LowSlopeBetaDays));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("LowSlopeBetaThreshhold,{0}",LowSlopeBetaThreshhold));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseCalcBeta,{0}",UseCalcBeta));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseMACD,{0}",UseMACD));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MACDSetup,{0}",MACDSetup));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MACDSignalDays,{0}",MACDSignalDays));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MACDRejectStrongSellSignals,{0}",MACDRejectStrongSellSignals));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MACDRejectWeakSellSignals,{0}",MACDRejectWeakSellSignals));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseStochastics,{0}",UseStochastics));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("StochasticsSignalDays,{0}",StochasticsSignalDays));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("StochasticsRejectStrongSells,{0}",StochasticsRejectStrongSells));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("StochasticsRejectWeakSells,{0}",StochasticsRejectWeakSells));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("UseFallbackCandidate,{0}",UseFallbackCandidate));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("FallbackCandidate,{0}",FallbackCandidate));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("FallbackCandidateBestOf,{0}",FallbackCandidateBestOf));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("BenchmarkMode,{0}",BenchmarkMode));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("BenchmarkSymbol,{0}",BenchmarkModeSymbol));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("QualityIndicatorType,{0}",QualityIndicatorType));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("IncludeTradeMasterForSymbolsHeld,{0}",IncludeTradeMasterForSymbolsHeld));
}
}
}

View File

@@ -0,0 +1,164 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Runtime.Serialization.Formatters.Binary;
using MarketData.Utils;
namespace MarketData.Generator.Momentum
{
// *****************************************************************************
public class MGSessionManager
{
public static bool SaveSession(MGSessionParams sessionParams,String pathSessionFile)
{
try
{
if(null==pathSessionFile)return false;
pathSessionFile=GetSessionFileName(pathSessionFile);
FileStream outStream=new FileStream(pathSessionFile,FileMode.Create);
StreamWriter streamWriter=new StreamWriter(outStream);
streamWriter.WriteLine("SESSIONv1.00");
streamWriter.WriteLine((new NVP("LastUpdated",sessionParams.LastUpdated.ToString())).ToString());
streamWriter.WriteLine((new NVP("TradeDate",sessionParams.TradeDate.ToShortDateString())).ToString());
streamWriter.WriteLine((new NVP("StartDate",sessionParams.StartDate.ToShortDateString())).ToString());
streamWriter.WriteLine((new NVP("AnalysisDate",sessionParams.AnalysisDate.ToShortDateString())).ToString());
streamWriter.WriteLine((new NVP("Cycle",sessionParams.Cycle.ToString())).ToString());
streamWriter.WriteLine((new NVP("CashBalance",sessionParams.CashBalance.ToString())).ToString());
streamWriter.WriteLine((new NVP("NonTradeableCash",sessionParams.NonTradeableCash.ToString())).ToString());
NVPCollection configurationCollection=sessionParams.Configuration.ToNVPCollection();
streamWriter.WriteLine(configurationCollection.ToString());
List<NVPCollections> nvpCollectionsList=sessionParams.ActivePositions.ToNVPCollections();
int totalPositions=0;
foreach(NVPCollections nvpCollections in nvpCollectionsList)
{
List<String> nvpCollectionsStringList=nvpCollections.ToList();
totalPositions+=nvpCollectionsStringList.Count;
}
streamWriter.WriteLine((new NVP("TotalActivePositions",totalPositions.ToString())).ToString());
foreach(NVPCollections nvpCollections in nvpCollectionsList)
{
List<String> nvpCollectionsStringList=nvpCollections.ToList();
foreach(String str in nvpCollectionsStringList)
{
streamWriter.WriteLine(str);
}
}
NVPCollections allPositionsCollections=sessionParams.AllPositions.ToNVPCollections();
List<String> nvpAllCollectionsStringList=allPositionsCollections.ToList();
streamWriter.WriteLine((new NVP("TotalPositions",nvpAllCollectionsStringList.Count.ToString())).ToString());
foreach(String str in nvpAllCollectionsStringList)streamWriter.WriteLine(str);
streamWriter.Flush();
outStream.Flush();
streamWriter.Close();
outStream.Close();
outStream.Dispose();
return true;
}
catch(Exception exception)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0}",exception.ToString()));
return false;
}
}
public static MGSessionParams RestoreSession(String pathSessionFile)
{
FileStream inStream =null;
StreamReader streamReader=null;
try
{
if(!SessionAvailable(pathSessionFile))return null;
MGSessionParams sessionParams=new MGSessionParams();
inStream =new FileStream(pathSessionFile,FileMode.Open);
streamReader=new StreamReader(inStream);
String versionInfo=streamReader.ReadLine();
double version=double.Parse(Utility.BetweenString(versionInfo,"v",null));
if(1.00!=version)return null;
NVP lastUpdated=new NVP(streamReader.ReadLine());
NVP tradeDate=new NVP(streamReader.ReadLine());
NVP startDate=new NVP(streamReader.ReadLine());
NVP analysisDate=new NVP(streamReader.ReadLine());
NVP cycle=new NVP(streamReader.ReadLine());
NVP cashBalance=new NVP(streamReader.ReadLine());
NVP nonTradeableCash=new NVP(streamReader.ReadLine());
sessionParams.LastUpdated=lastUpdated.Get<DateTime>();
sessionParams.TradeDate=tradeDate.Get<DateTime>();
sessionParams.StartDate=startDate.Get<DateTime>();
sessionParams.AnalysisDate=analysisDate.Get<DateTime>();
sessionParams.Cycle=cycle.Get<int>();
sessionParams.CashBalance=cashBalance.Get<double>();
sessionParams.NonTradeableCash=nonTradeableCash.Get<double>();
NVPCollection configurationCollection=new NVPCollection(streamReader.ReadLine());
sessionParams.Configuration=MGConfiguration.FromNVPCollection(configurationCollection);
int totalActivePositions=(new NVP(streamReader.ReadLine())).Get<int>();
sessionParams.ActivePositions=new ActivePositions();
for(int positionIndex=0;positionIndex<totalActivePositions;positionIndex++)
{
NVPCollection nvpCollection=new NVPCollection(streamReader.ReadLine());
sessionParams.ActivePositions.AddFromNVPCollection(nvpCollection);
}
int totalPositions=(new NVP(streamReader.ReadLine())).Get<int>();
NVPCollections nvpCollections=new NVPCollections();
for(int positionIndex=0;positionIndex<totalPositions;positionIndex++)
{
NVPCollection nvpCollection=new NVPCollection(streamReader.ReadLine());
nvpCollections.Add(nvpCollection);
}
sessionParams.AllPositions=Positions.FromNVPCollections(nvpCollections);
inStream.Close();
inStream.Dispose();
inStream = null;
streamReader.Close();
streamReader.Dispose();
streamReader = null;
return sessionParams;
}
finally
{
if(null!=streamReader)streamReader.Close();
if(null!=inStream)inStream.Close();
}
}
public static String GetSessionFileName(String pathSessionFile)
{
if (null == pathSessionFile) return null;
String directory = Path.GetDirectoryName(pathSessionFile);
if ("".Equals(directory)) directory = Directory.GetCurrentDirectory();
return directory + "\\" + Path.GetFileNameWithoutExtension(pathSessionFile) + ".txt";
}
public static bool SessionAvailable(String pathSessionFile)
{
return IsValidSessionFile(pathSessionFile);
}
public static bool IsValidSessionFile(String pathSessionFile)
{
FileStream inStream =null;
StreamReader streamReader=null;
try
{
if(null==pathSessionFile)return false;
pathSessionFile=pathSessionFile.ToLower();
if(!pathSessionFile.EndsWith(".txt"))pathSessionFile+=".txt";
if(!File.Exists(pathSessionFile))return false;
inStream =new FileStream(pathSessionFile,FileMode.Open);
streamReader=new StreamReader(inStream);
String versionInfo=streamReader.ReadLine();
double version=double.Parse(Utility.BetweenString(versionInfo,"v",null));
if(1.00!=version)return false;
return true;
}
catch(Exception)
{
return false;
}
finally
{
if(null!=streamReader)streamReader.Close();
if(null!=inStream)inStream.Close();
}
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Runtime.Serialization.Formatters.Binary;
using MarketData.Utils;
namespace MarketData.Generator.Momentum
{
public class MGSessionParams
{
public DateTime LastUpdated { get; set; }
public DateTime TradeDate { get; set; }
public DateTime StartDate { get; set; }
public DateTime AnalysisDate { get; set; }
public MGConfiguration Configuration { get; set; }
public ActivePositions ActivePositions { get; set; }
public Positions AllPositions { get; set; }
public double CashBalance { get; set; }
public double NonTradeableCash { get; set; }
public int Cycle { get; set; }
// This gets AllPositions+Positions
public Positions GetCombinedPositions()
{
Positions positions=new Positions();
foreach(Position position in AllPositions) positions.Add(position);
Positions activePositions=ActivePositions.GetPositions();
foreach(Position position in activePositions) positions.Add(position);
return positions;
}
}
}

View File

@@ -0,0 +1,699 @@
using System;
using System.Collections.Generic;
using MarketData.MarketDataModel;
using MarketData.DataAccess;
using MarketData.Utils;
using System.Linq;
using MarketData.Numerical;
using MarketData.Cache;
// Filename: MomentumGenerator.cs
// Author:Sean Kessler
// Date:01/2018
namespace MarketData.Generator.Momentum
{
/// <summary>Generate momentum selections - </summary>
public class MomentumGenerator
{
public enum MomentumGeneratorConstants{DayCount=252}; // Trading days in one year
private MomentumGenerator()
{
}
// These two interfaces are used by the UI so that it can capture the fallback candidates
public static MomentumCandidates GenerateMomentum(DateTime tradeDate,MGConfiguration config)
{
List<String> symbolsHeld=new List<String>();
return new MomentumCandidates(GenerateMomentum(tradeDate,symbolsHeld,config).Take(config.MaxPositions).ToList());
}
public static MomentumCandidates GenerateMomentumWithFallback(DateTime tradeDate,MGConfiguration config)
{
List<String> symbolsHeld=new List<String>();
MomentumCandidates momentumCandidates=GenerateMomentum(tradeDate,symbolsHeld,config);
QualityIndicator qualityIndicator=new QualityIndicator(config.QualityIndicatorType);
if((null==momentumCandidates||0==momentumCandidates.Count)&&config.UseFallbackCandidate)
{
QualityIndicatorCandidate bestCandidate=null;
if(null!=config.FallbackCandidateBestOf && !"".Equals(config.FallbackCandidateBestOf))
{
bestCandidate=CandidateSelector.SelectBestCandidate(qualityIndicator,Utility.ToList(config.FallbackCandidateBestOf),config.FallbackCandidate,tradeDate);
if(null!=bestCandidate)
{
ZacksRank zacksRank=ZacksRankDA.GetZacksRankOnOrBefore(bestCandidate.Symbol,tradeDate);
MomentumCandidate momentumCandidate=new MomentumCandidate();
momentumCandidate.Symbol=bestCandidate.Symbol;
momentumCandidate.AnalysisDate=tradeDate;
momentumCandidate.CumReturn252=bestCandidate.CumReturn252;
momentumCandidate.IDIndicator=bestCandidate.IDIndicator;
momentumCandidate.Score=bestCandidate.Score;
momentumCandidate.DayCount=bestCandidate.DayCount;
momentumCandidate.PE=bestCandidate.PE;
momentumCandidate.Beta=bestCandidate.Beta;
momentumCandidate.Return1D=bestCandidate.Return1D;
if(null!=zacksRank)momentumCandidate.ZacksRank=zacksRank.Rank;
momentumCandidates=new MomentumCandidates();
momentumCandidates.Add(momentumCandidate);
}
}
}
return momentumCandidates;
}
// This interface is called by the Backtest
public static MomentumCandidates GenerateMomentum(DateTime tradeDate,List<String> symbolsHeld,MGConfiguration config)
{
DateGenerator dateGenerator=new DateGenerator();
List<String> symbols=PricingDA.GetSymbols();
MomentumCandidates momentumCandidates=new MomentumCandidates();
MomentumCandidates highPECandidates=new MomentumCandidates();
DateTime startDateOfReturns=dateGenerator.GetPrevMonthEnd(tradeDate,2);
List<String> noTradeSymbols=Utility.ToList(config.NoTradeSymbols);
List<String> noTradeFinancialSymbols=Utility.ToList(config.NoTradeFinancialSymbols);
CandidateViolations candidateViolations = new CandidateViolations();
MDTrace.WriteLine(LogLevel.DEBUG,$"Fetching data...");
// Filter out symbols where we do not have a price on trade date
Profiler profiler = new Profiler();
Dictionary<String,DateTime> latestDates = PricingDA.GetLatestDates(symbols);
symbols=symbols.Where(x => latestDates.ContainsKey(x) && latestDates[x].Date>=tradeDate.Date).ToList();
MDTrace.WriteLine(LogLevel.DEBUG,$"Loaded Pricing Dates in {Utility.FormatNumber(profiler.End(),0,true)} (ms)");
// Prefetch a subset of fundamentals where each fundamental.asof is no greater than tradeDate
profiler.Reset();
FundamentalsV2 fundamentals = FundamentalDA.GetFundamentalsMaxDateV2(tradeDate);
MDTrace.WriteLine(LogLevel.DEBUG,$"Loaded Fundamentals in {Utility.FormatNumber(profiler.End(),0,true)} (ms)");
// Prefetch the Company Profiles
profiler.Reset();
Dictionary<String,CompanyProfile> companyProfiles = CompanyProfileDA.GetCompanyProfiles(symbols);
MDTrace.WriteLine(LogLevel.DEBUG,$"Loaded Company Profiles in {Utility.FormatNumber(profiler.End(),0,true)} (ms)");
// Prefetch the Analyst Ratings
profiler.Reset();
Dictionary<String,AnalystRatings> analystRatingsDictionary = AnalystRatingsDA.GetAnalystRatingsDowngradesMaxDateNoZacks(symbols, tradeDate);
MDTrace.WriteLine(LogLevel.DEBUG,$"Loaded Analyst Ratings in {Utility.FormatNumber(profiler.End(),0,true)} (ms)");
// Prefetch Zacks Ranks
profiler.Reset();
Dictionary<String,ZacksRank> zacksRanksDictionary = ZacksRankDA.GetZacksRankOnOrBefore(symbols, tradeDate);
MDTrace.WriteLine(LogLevel.DEBUG,$"Loaded Zacks Ranks in {Utility.FormatNumber(profiler.End(),0,true)} (ms)");
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Generate momentum.. examining candidates"));
// Go through the universe of stocks
for(int index=0;index<symbols.Count;index++)
{
String symbol=symbols[index];
if(0==(index%500))Console.WriteLine("Processing item {0} of {1}",index+1,symbols.Count);
// Check if the symbol is held in any open positions
if(symbolsHeld.Any(x=>x.Equals(symbol, StringComparison.CurrentCultureIgnoreCase)))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate already held."));
continue;
}
// Check if the symbol is in the no trade list (i.e.) Bitcoin etc.,
if(noTradeSymbols.Any(x=>x.Equals(symbol, StringComparison.CurrentCultureIgnoreCase)))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate in NoTradeSymbol."));
continue;
}
// Check MarketCap, EBITDA, PE, and Revenue Per Share
FundamentalV2 fundamental = default;
if(fundamentals.ContainsKey(symbol))fundamental = fundamentals[symbol];
if(null==fundamental)
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate no fundamental."));
continue;
}
if(!(fundamental.MarketCap>=config.MarketCapLowerLimit))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate MarketCapLimit."));
continue;
}
if(config.UseEBITDAScreen && (double.IsNaN(fundamental.EBITDA)||fundamental.EBITDA<=0))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate EBITDA violation."));
continue;
}
if(config.UseRevenuePerShareScreen && (double.IsNaN(fundamental.RevenuePerShare)||fundamental.RevenuePerShare<0.00))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate RevenuePerShare violation."));
continue;
}
// Initial PE screening. This screen checks for existance of PE and if it is availabe it must be >0.00 . There is another PE based on limits further below
if(config.UsePEScreen && (double.IsNaN(fundamental.PE)||fundamental.PE<=0.00))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate PE violation."));
continue;
}
// Exclude any company in the "Financial" sector
CompanyProfile companyProfile = default;
if(companyProfiles.ContainsKey(symbol))companyProfile = companyProfiles[symbol];
if(null!=companyProfile&&null!=companyProfile.Sector&&noTradeFinancialSymbols.Any(x=>x.Equals(companyProfile.Sector)))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate Financial Sector violation."));
continue;
}
// Retrieve prices
Prices prices=null;
prices=GBPriceCache.GetInstance().GetPrices(symbol,tradeDate,(int)MomentumGeneratorConstants.DayCount+20);
if(null==prices || prices.Count!=(int)MomentumGeneratorConstants.DayCount+20)
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate missing price history."));
continue;
}
// Fetch single day price
Price price=prices[0]; // GBPriceCache.GetInstance().GetPrice(symbol,tradeDate);
if(null==price)
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate missing price on trade date."));
continue;
}
// Filter penny stocks - don't trade anything less than $1.00
if(price.Close<1.00||price.Open<1.00)
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate penny stock violation."));
continue;
}
// calculate the one day return
double return1D=prices.GetReturn1D();
// Liquidity check - if any day has volume < 10,000 then we reject it
if(((from Price xPrice in prices where xPrice.Volume<10000 select xPrice).Count())>1)
{
candidateViolations.Add(new CandidateViolation(symbol,"Liquidity violation."));
continue;
}
// Calculate velocity as a percentage range of the open price within the 252+20 day range of prices - This is used for display purposes
double velocity;
Prices velocityPrices=GBPriceCache.GetInstance().GetPrices(symbol,tradeDate,(int)MomentumGenerator.MomentumGeneratorConstants.DayCount+20);
double priceHigh=(from Price selectPrice in velocityPrices select selectPrice.Open).Max();
double priceLow=(from Price selectPrice in velocityPrices select selectPrice.Open).Min();
if(0.00==priceHigh-priceLow)velocity=0.00;
else velocity=((price.Open-priceLow)*(100/(priceHigh-priceLow)))/100.00;
// Price slopes - These are used for display purposes
double[] pricesArray=null;
LeastSquaresResult leastSquaresResult;
// 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
// The BetaCalc36 is calculated as part of the monthly fundamental run.
double beta = fundamental.Beta;
if(config.UseCalcBeta)beta=fundamental.BetaCalc36;
if(config.UseLowSlopeBetaCheck && beta >= config.LowSlopeBetaThreshhold)
{
Prices benchmarkPrices=GBPriceCache.GetInstance().GetPrices(config.Benchmark,tradeDate,config.LowSlopeBetaDays);
pricesArray=Numerics.ToDouble(benchmarkPrices.GetPricesLow());
leastSquaresResult=Numerics.LeastSquares(pricesArray);
double slopeBmk=leastSquaresResult.Slope;
if(slopeBmk<0)
{
candidateViolations.Add(new CandidateViolation(symbol,"Beta threshhold violation."));
continue;
}
}
// *** MACDSignal detection
if(config.UseMACD)
{
MACDSetup macdSetup=new MACDSetup(config.MACDSetup);
MACDSignals macdSignals=MACDGenerator.GenerateMACD(prices,macdSetup);
Signals signalsMACD = SignalGenerator.GenerateSignals(macdSignals);
signalsMACD=new Signals(signalsMACD.Take(config.MACDSignalDays).ToList());
int weakSellSignals=(from Signal signal in signalsMACD where signal.IsWeakSell() select signal).Count();
int strongSellSignals=(from Signal signal in signalsMACD where signal.IsStrongSell() select signal).Count();
if(config.MACDRejectWeakSellSignals && weakSellSignals>0)
{
candidateViolations.Add(new CandidateViolation(symbol,"MACD Reject Weak Sell violation."));
continue;
}
if(config.MACDRejectStrongSellSignals && strongSellSignals>0)
{
candidateViolations.Add(new CandidateViolation(symbol,"MACD Reject Strong Sell violation."));
continue;
}
}
// *** Stochastics oscillator
if(config.UseStochastics)
{
Stochastics stochastics=StochasticsGenerator.GenerateStochastics(prices);
Signals signalsStochastics=new Signals(SignalGenerator.GenerateSignals(stochastics).OrderByDescending(x => x.SignalDate).ToList());
signalsStochastics=new Signals(signalsStochastics.Take(config.StochasticsSignalDays).ToList());
int weakSellCount=(from Signal signal in signalsStochastics where signal.IsWeakSell() select signal).Count();
int strongSellCount=(from Signal signal in signalsStochastics where signal.IsStrongSell() select signal).Count();
if(config.StochasticsRejectStrongSells&&strongSellCount>0)
{
candidateViolations.Add(new CandidateViolation(symbol,"Stochastics Oscillator Reject Strong Sell violation."));
continue;
}
if(config.StochasticsRejectWeakSells&&weakSellCount>0)
{
candidateViolations.Add(new CandidateViolation(symbol,"Stochastics Oscillator Reject Weak Sell violation."));
continue;
}
}
// Analyst Ratings - "Downgrades" that are more than a year old (252 days) are not considered. Mean reversion.... bad companies improve, good companies decline.
DateTime minRatingDate=dateGenerator.GenerateHistoricalDate(startDateOfReturns,(int)MomentumGeneratorConstants.DayCount);
AnalystRatings analystRatings= default;
if(analystRatingsDictionary.ContainsKey(symbol))analystRatings=analystRatingsDictionary[symbol];
if(default!=analystRatings)
{
analystRatings.RemoveAll(x => x.Date<minRatingDate);
AnalystRating rating=null;
if(null!=analystRatings)rating=analystRatings.FirstOrDefault();
if(null!=rating)
{
candidateViolations.Add(new CandidateViolation(symbol,"AnalystRating Downgrade violation within set period."));
continue;
}
}
// The cumulative returns for the ranking skip to the previous month to eliminate short term reversal anomaly (Wesley Gray : Quantum Momentum)
prices=GBPriceCache.GetInstance().GetPrices(symbol,startDateOfReturns,(int)MomentumGeneratorConstants.DayCount);
if(null==prices||(int)MomentumGeneratorConstants.DayCount!=prices.Count)
{
candidateViolations.Add(new CandidateViolation(symbol,"Insufficient pricing, cannot determine rank."));
continue;
}
// check for outliers in the return stream
float[] returns = default;
returns=prices.GetReturns();
if((from float value in returns where Math.Abs(value)>.50 select value).Count()>0)
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate pricing contains outliers in the returns."));
continue;
}
// Cumulative return
double cumulativeReturn=prices.GetCumulativeReturn();
if(cumulativeReturn<.10)
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate cumulative returns below threshhold."));
continue;
}
// Zacks Rank. This is for informational purposes for now but may further it's use in the future.
ZacksRank zacksRank = default;
if(zacksRanksDictionary.ContainsKey(symbol))
{
zacksRank = zacksRanksDictionary[symbol];
}
// 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)
{
candidateViolations.Add(new CandidateViolation(symbol,"PE violation."));
MomentumCandidate highPECandidate=new MomentumCandidate();
highPECandidate.AnalysisDate=tradeDate;
highPECandidate.Symbol=symbol;
highPECandidate.CumReturn252=prices.GetCumulativeReturn();
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=beta;
highPECandidate.Velocity=velocity;
highPECandidate.Volume=price.Volume;
highPECandidate.Return1D=return1D;
if(null!=zacksRank)highPECandidate.ZacksRank=zacksRank.Rank;
highPECandidates.Add(highPECandidate);
continue;
}
// *********************************************************************** C A N D I D A T E A C C E P T A N C E *******************************************************
// At this point whatever remains is taken so initialize the candidate and add to list
MomentumCandidate momentumCandidate=new MomentumCandidate();
momentumCandidate.AnalysisDate=tradeDate;
momentumCandidate.Symbol=symbol;
momentumCandidate.CumReturn252=prices.GetCumulativeReturn();
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=beta;
momentumCandidate.Velocity=velocity;
momentumCandidate.Volume=price.Volume;
momentumCandidate.Return1D=return1D;
if(null!=zacksRank)momentumCandidate.ZacksRank=zacksRank.Rank;
momentumCandidates.Add(momentumCandidate);
} // for all symbols
if(0!=candidateViolations.Count)
{
MDTrace.WriteLine(LogLevel.DEBUG,"**************** C A N D I D A T E S U M M A R Y ************************");
IEnumerable<Tuple<string, int>> groups = candidateViolations.GroupBy(x => x.ReasonCategory).OrderByDescending(group => group.Count()).Select(group => Tuple.Create(group.Key, group.Count()));
foreach(Tuple<string, int> group in groups)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Group: {0} Count:{1}",group.Item1, group.Item2));
}
}
MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Total Considered : {momentumCandidates.Count+candidateViolations.Count}"));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Total Disqualified : {candidateViolations.Count}"));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Total Eligible : {momentumCandidates.Count}"));
MDTrace.WriteLine(LogLevel.DEBUG,"******************************************************************************************************");
// ********************************************************* E N D C A N D I D A T E S E L E C T I O N C R I T E R I A ****************************************
// If we wind up with less than the number of required candidates then check the StrictMaxPE
// flag and, if allowed, add the highPECandidate (that we've accumulated but skipped) to the momentumCandidates ordering them by the Lowest PE
if(!config.StrictMaxPE && momentumCandidates.Count<config.MaxPositions && highPECandidates.Count>0)
{
int takeCandidates=config.MaxPositions-momentumCandidates.Count;
highPECandidates=new MomentumCandidates(highPECandidates.OrderBy(x=>x.PE).Take(takeCandidates).ToList());
momentumCandidates.AddRange(highPECandidates);
if(config.Verbose)MDTrace.WriteLine(LogLevel.DEBUG,String.Format("High PE Candidates,{0}",Utility.FromList((from MomentumCandidate momentumCandidate in highPECandidates select momentumCandidate.Symbol).ToList())));
}
QualityIndicator qualityIndicator=new QualityIndicator(config.QualityIndicatorType);
if(qualityIndicator.Quality.Equals(QualityIndicator.QualityType.IDIndicator))
{
momentumCandidates=new MomentumCandidates((from MomentumCandidate momentumCandidate in momentumCandidates orderby momentumCandidate.IDIndicator ascending, momentumCandidate.CumReturn252 descending, momentumCandidate.Return1D descending, momentumCandidate.Volume descending select momentumCandidate).ToList());
}
else
{
momentumCandidates=new MomentumCandidates((from MomentumCandidate momentumCandidate in momentumCandidates orderby momentumCandidate.Score descending,momentumCandidate.CumReturn252 descending,momentumCandidate.Return1D descending,momentumCandidate.Volume descending select momentumCandidate).ToList());
}
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MomentumGenertor.GenerateMomentum:{0} candidates",momentumCandidates.Count()));
return momentumCandidates;
}
/*
// This interface is called by the Backtest
public static MomentumCandidates GenerateMomentum(DateTime tradeDate,List<String> symbolsHeld,MGConfiguration config)
{
DateGenerator dateGenerator=new DateGenerator();
List<String> symbols=PricingDA.GetSymbols();
MomentumCandidates momentumCandidates=new MomentumCandidates();
MomentumCandidates highPECandidates=new MomentumCandidates();
DateTime startDateOfReturns=dateGenerator.GetPrevMonthEnd(tradeDate,2);
List<String> noTradeSymbols=Utility.ToList(config.NoTradeSymbols);
List<String> noTradeFinancialSymbols=Utility.ToList(config.NoTradeFinancialSymbols);
CandidateViolations candidateViolations = new CandidateViolations();
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Generate momentum.. examining candidates"));
// Go through the universe of stocks
for(int index=0;index<symbols.Count;index++)
{
String symbol=symbols[index];
if(0==(index%500))Console.WriteLine("Processing item {0} of {1}",index+1,symbols.Count);
// Check if the symbol is held in any open positions
if(symbolsHeld.Any(x=>x.Equals(symbol, StringComparison.CurrentCultureIgnoreCase)))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate already held."));
continue;
}
// Check if the symbol is in the no trade list (i.e.) Bitcoin etc.,
if(noTradeSymbols.Any(x=>x.Equals(symbol, StringComparison.CurrentCultureIgnoreCase)))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate in NoTradeSymbol."));
continue;
}
// Check MarketCap, EBITDA, PE, and Revenue Per Share
Fundamental fundamental=FundamentalDA.GetFundamentalMaxDate(symbol,tradeDate);
if(null==fundamental)
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate no fundamental."));
continue;
}
if(!(fundamental.MarketCap>=config.MarketCapLowerLimit))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate MarketCapLimit."));
continue;
}
if(config.UseEBITDAScreen && (double.IsNaN(fundamental.EBITDA)||fundamental.EBITDA<=0))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate EBITDA violation."));
continue;
}
if(config.UseRevenuePerShareScreen && (double.IsNaN(fundamental.RevenuePerShare)||fundamental.RevenuePerShare<0.00))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate RevenuePerShare violation."));
continue;
}
// Initial PE screening. This screen checks for existance of PE and if it is availabe it must be >0.00 . There is another PE based on limits further below
if(config.UsePEScreen && (double.IsNaN(fundamental.PE)||fundamental.PE<=0.00))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate PE violation."));
continue;
}
// Exclude any company in the "Financial" sector
CompanyProfile companyProfile=CompanyProfileDA.GetCompanyProfile(symbol);
if(null!=companyProfile&&null!=companyProfile.Sector&&noTradeFinancialSymbols.Any(x=>x.Equals(companyProfile.Sector)))
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate Financial Sector violation."));
continue;
}
// Fetch single day price
Price price=GBPriceCache.GetInstance().GetPrice(symbol,tradeDate);
if(null==price)
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate missing price on trade date."));
continue;
}
// Filter penny stocks - don't trade anything less than $1.00
if(price.Close<1.00||price.Open<1.00)
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate penny stock violation."));
continue;
}
// Retrieve prices
Prices prices=null;
prices=GBPriceCache.GetInstance().GetPrices(symbol,tradeDate,(int)MomentumGeneratorConstants.DayCount);
if(null==prices||prices.Count!=(int)MomentumGeneratorConstants.DayCount)
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate missing price history."));
continue;
}
// calculate the one day return
double return1D=prices.GetReturn1D();
// Liquidity check - if any day has volume < 10,000 then we reject it
if(((from Price xPrice in prices where xPrice.Volume<10000 select xPrice).Count())>1)
{
candidateViolations.Add(new CandidateViolation(symbol,"Liquidity violation."));
continue;
}
// Calculate velocity as a percentage range of the open price within the 252+20 day range of prices - This is used for display purposes
double velocity;
Prices velocityPrices=GBPriceCache.GetInstance().GetPrices(symbol,tradeDate,(int)MomentumGenerator.MomentumGeneratorConstants.DayCount+20);
double priceHigh=(from Price selectPrice in velocityPrices select selectPrice.Open).Max();
double priceLow=(from Price selectPrice in velocityPrices select selectPrice.Open).Min();
if(0.00==priceHigh-priceLow)velocity=0.00;
else velocity=((price.Open-priceLow)*(100/(priceHigh-priceLow)))/100.00;
// Price slopes - These are used for display purposes
double[] pricesArray=null;
LeastSquaresResult leastSquaresResult;
// 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)
{
Prices benchmarkPrices=GBPriceCache.GetInstance().GetPrices(config.Benchmark,tradeDate,config.LowSlopeBetaDays);
pricesArray=Numerics.ToDouble(benchmarkPrices.GetPricesLow());
leastSquaresResult=Numerics.LeastSquares(pricesArray);
double slopeBmk=leastSquaresResult.Slope;
if(slopeBmk<0)
{
candidateViolations.Add(new CandidateViolation(symbol,"Beta threshhold violation."));
continue;
}
}
// *** MACDSignal detection
if(config.UseMACD)
{
MACDSetup macdSetup=new MACDSetup(config.MACDSetup);
MACDSignals macdSignals=MACDGenerator.GenerateMACD(prices,macdSetup);
Signals signalsMACD = SignalGenerator.GenerateSignals(macdSignals);
signalsMACD=new Signals(signalsMACD.Take(config.MACDSignalDays).ToList());
int weakSellSignals=(from Signal signal in signalsMACD where signal.IsWeakSell() select signal).Count();
int strongSellSignals=(from Signal signal in signalsMACD where signal.IsStrongSell() select signal).Count();
if(config.MACDRejectWeakSellSignals && weakSellSignals>0)
{
candidateViolations.Add(new CandidateViolation(symbol,"MACD Reject Weak Sell violation."));
continue;
}
if(config.MACDRejectStrongSellSignals && strongSellSignals>0)
{
candidateViolations.Add(new CandidateViolation(symbol,"MACD Reject Strong Sell violation."));
continue;
}
}
// *** Stochastics oscillator
if(config.UseStochastics)
{
Stochastics stochastics=StochasticsGenerator.GenerateStochastics(prices);
Signals signalsStochastics=new Signals(SignalGenerator.GenerateSignals(stochastics).OrderByDescending(x => x.SignalDate).ToList());
signalsStochastics=new Signals(signalsStochastics.Take(config.StochasticsSignalDays).ToList());
int weakSellCount=(from Signal signal in signalsStochastics where signal.IsWeakSell() select signal).Count();
int strongSellCount=(from Signal signal in signalsStochastics where signal.IsStrongSell() select signal).Count();
if(config.StochasticsRejectStrongSells&&strongSellCount>0)
{
candidateViolations.Add(new CandidateViolation(symbol,"Stochastics Oscillator Reject Strong Sell violation."));
continue;
}
if(config.StochasticsRejectWeakSells&&weakSellCount>0)
{
candidateViolations.Add(new CandidateViolation(symbol,"Stochastics Oscillator Reject Weak Sell violation."));
continue;
}
}
// Analyst Ratings - "Downgrades" that are more than a year old (252 days) are not considered. Mean reversion.... bad companies improve, good companies decline.
DateTime minRatingDate=dateGenerator.GenerateHistoricalDate(startDateOfReturns,(int)MomentumGeneratorConstants.DayCount);
AnalystRatings analystRatings=AnalystRatingsDA.GetAnalystRatingsMaxDateNoZacks(symbol,tradeDate);
analystRatings.RemoveAll(x => x.Date<minRatingDate);
AnalystRating rating=null;
if(null!=analystRatings)rating=(from AnalystRating analystRating in analystRatings where analystRating.Type.Equals("Downgrades") select analystRating).FirstOrDefault();
if(null!=rating)
{
candidateViolations.Add(new CandidateViolation(symbol,"AnalystRating Downgrade violation within set period."));
continue;
}
// The cumulative returns for the ranking skip to the previous month to eliminate short term reversal anomaly (Wesley Gray : Quantum Momentum)
prices=GBPriceCache.GetInstance().GetPrices(symbol,startDateOfReturns,(int)MomentumGeneratorConstants.DayCount);
if(null==prices||(int)MomentumGeneratorConstants.DayCount!=prices.Count)
{
candidateViolations.Add(new CandidateViolation(symbol,"Insufficient pricing, cannot determine rank."));
continue;
}
// check for outliers in the return stream
float[] returns = default;
returns=prices.GetReturns();
if((from float value in returns where Math.Abs(value)>.50 select value).Count()>0)
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate pricing contains outliers in the returns."));
continue;
}
// Cumulative return
double cumulativeReturn=prices.GetCumulativeReturn();
if(cumulativeReturn<.10)
{
candidateViolations.Add(new CandidateViolation(symbol,"Candidate cumulative returns below threshhold."));
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)
{
candidateViolations.Add(new CandidateViolation(symbol,"PE violation."));
MomentumCandidate highPECandidate=new MomentumCandidate();
highPECandidate.AnalysisDate=tradeDate;
highPECandidate.Symbol=symbol;
highPECandidate.CumReturn252=prices.GetCumulativeReturn();
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.Velocity=velocity;
highPECandidate.Volume=price.Volume;
highPECandidate.Return1D=return1D;
if(null!=zacksRank)highPECandidate.ZacksRank=zacksRank.Rank;
highPECandidates.Add(highPECandidate);
continue;
}
// *********************************************************************** C A N D I D A T E A C C E P T A N C E *******************************************************
// At this point whatever remains is taken so initialize the candidate and add to list
MomentumCandidate momentumCandidate=new MomentumCandidate();
momentumCandidate.AnalysisDate=tradeDate;
momentumCandidate.Symbol=symbol;
momentumCandidate.CumReturn252=prices.GetCumulativeReturn();
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.Velocity=velocity;
momentumCandidate.Volume=price.Volume;
momentumCandidate.Return1D=return1D;
if(null!=zacksRank)momentumCandidate.ZacksRank=zacksRank.Rank;
momentumCandidates.Add(momentumCandidate);
} // for all symbols
if(0!=candidateViolations.Count)
{
MDTrace.WriteLine(LogLevel.DEBUG,"**************** C A N D I D A T E S U M M A R Y ************************");
IEnumerable<Tuple<string, int>> groups = candidateViolations.GroupBy(x => x.ReasonCategory).OrderByDescending(group => group.Count()).Select(group => Tuple.Create(group.Key, group.Count()));
foreach(Tuple<string, int> group in groups)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Group: {0} Count:{1}",group.Item1, group.Item2));
}
}
MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Total Considered : {momentumCandidates.Count+candidateViolations.Count}"));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Total Disqualified : {candidateViolations.Count}"));
MDTrace.WriteLine(LogLevel.DEBUG,String.Format($"Total Eligible : {momentumCandidates.Count}"));
MDTrace.WriteLine(LogLevel.DEBUG,"******************************************************************************************************");
// ********************************************************* E N D C A N D I D A T E S E L E C T I O N C R I T E R I A ****************************************
// If we wind up with less than the number of required candidates then check the StrictMaxPE
// flag and, if allowed, add the highPECandidate (that we've accumulated but skipped) to the momentumCandidates ordering them by the Lowest PE
if(!config.StrictMaxPE && momentumCandidates.Count<config.MaxPositions && highPECandidates.Count>0)
{
int takeCandidates=config.MaxPositions-momentumCandidates.Count;
highPECandidates=new MomentumCandidates(highPECandidates.OrderBy(x=>x.PE).Take(takeCandidates).ToList());
momentumCandidates.AddRange(highPECandidates);
if(config.Verbose)MDTrace.WriteLine(LogLevel.DEBUG,String.Format("High PE Candidates,{0}",Utility.FromList((from MomentumCandidate momentumCandidate in highPECandidates select momentumCandidate.Symbol).ToList())));
}
QualityIndicator qualityIndicator=new QualityIndicator(config.QualityIndicatorType);
if(qualityIndicator.Quality.Equals(QualityIndicator.QualityType.IDIndicator))
{
momentumCandidates=new MomentumCandidates((from MomentumCandidate momentumCandidate in momentumCandidates orderby momentumCandidate.IDIndicator ascending, momentumCandidate.CumReturn252 descending, momentumCandidate.Return1D descending, momentumCandidate.Volume descending select momentumCandidate).ToList());
}
else
{
momentumCandidates=new MomentumCandidates((from MomentumCandidate momentumCandidate in momentumCandidates orderby momentumCandidate.Score descending,momentumCandidate.CumReturn252 descending,momentumCandidate.Return1D descending,momentumCandidate.Volume descending select momentumCandidate).ToList());
}
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("MomentumGenertor.GenerateMomentum:{0} candidates",momentumCandidates.Count()));
return momentumCandidates;
}
*/
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MarketData.Generator.Momentum
{
public class SMSNotifications : Dictionary<String,SMSNotification>
{
}
public class SMSNotification
{
public SMSNotification()
{
}
public DateTime NotificationTime{get;set;}
public int ElapsedTimeMinutes(DateTime currentTime)
{
TimeSpan elapsedTime=currentTime-NotificationTime;
return elapsedTime.Minutes;
}
}
}

View File

@@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using MarketData.Utils;
using System.Linq;
using MarketData.Generator.Interface;
namespace MarketData.Generator.Momentum
{
public class Position : IPurePosition
{
public Position()
{
CurrentPrice=double.NaN;
}
public Position(Position position)
{
Symbol = position.Symbol;
PurchaseDate = position.PurchaseDate;
SellDate = position.SellDate;
Shares = position.Shares;
PurchasePrice = position.PurchasePrice;
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;
SharpeRatio = position.SharpeRatio;
}
public static Position Clone(Position position)
{
Position clonedPosition = new Position();
clonedPosition.Symbol = position.Symbol;
clonedPosition.PurchaseDate = position.PurchaseDate;
clonedPosition.SellDate = position.SellDate;
clonedPosition.Shares = position.Shares;
clonedPosition.PurchasePrice = position.PurchasePrice;
clonedPosition.CurrentPrice = position.CurrentPrice;
clonedPosition.Volume = position.Volume;
clonedPosition.Return1D = position.Return1D;
clonedPosition.ZacksRank = position.ZacksRank;
clonedPosition.CumReturn252 = position.CumReturn252;
clonedPosition.IDIndicator = position.IDIndicator;
clonedPosition.Score = position.Score;
clonedPosition.MaxDrawdown = position.MaxDrawdown;
clonedPosition.MaxUpside = position.MaxUpside;
clonedPosition.Velocity = position.Velocity;
clonedPosition.PE = position.PE;
clonedPosition.Beta = position.Beta;
clonedPosition.SharpeRatio = position.SharpeRatio;
return clonedPosition;
}
public String Symbol{get;set;}
public DateTime PurchaseDate{get;set;}
public DateTime SellDate{get;set;}
public double Shares{get;set;}
public double PurchasePrice{get;set;}
public double CurrentPrice{get;set;}
public double Exposure{get{return Shares*PurchasePrice;}}
public double MarketValue{get{return Shares*CurrentPrice;}}
public long Volume{get;set;}
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 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;}
public double SharpeRatio { get; set; }
public virtual NVPCollection ToNVPCollection()
{
NVPCollection nvpCollection=new NVPCollection();
nvpCollection.Add(new NVP("Symbol",Symbol.ToString()));
nvpCollection.Add(new NVP("PurchaseDate",PurchaseDate.ToString()));
nvpCollection.Add(new NVP("SellDate",SellDate.ToString()));
nvpCollection.Add(new NVP("Shares",Shares.ToString()));
nvpCollection.Add(new NVP("PurchasePrice",PurchasePrice.ToString()));
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()));
nvpCollection.Add(new NVP("SharpeRatio", SharpeRatio.ToString()));
return nvpCollection;
}
public static Position FromNVPCollection(NVPCollection nvpCollection)
{
Position position=new Position();
NVPDictionary nvpDictionary=nvpCollection.ToDictionary();
position.Symbol=nvpDictionary["Symbol"].Get<String>();
position.PurchaseDate=nvpDictionary["PurchaseDate"].Get<DateTime>();
position.SellDate=nvpDictionary["SellDate"].Get<DateTime>();
position.Shares=nvpDictionary["Shares"].Get<double>();
position.PurchasePrice=nvpDictionary["PurchasePrice"].Get<double>();
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>();
if (nvpDictionary.ContainsKey("SharpeRatio")) position.SharpeRatio = nvpDictionary["SharpeRatio"].Get<double>();
else position.SharpeRatio = double.NaN;
if(nvpDictionary.ContainsKey("Score")) position.Score=nvpDictionary["Score"].Get<double>();
else position.Score=double.NaN;
return position;
}
public static void DisplayHeader()
{
MDTrace.WriteLine(LogLevel.DEBUG, "Symbol,Purchase Date,Shares,Purchase Price,Exposure,Volume,Return1D,Sell Date,Sell Price,Market Value,Gain Loss,Gain Loss(%),CumReturn252,IDIndicator,Score,MaxDrawdown,MaxUpside,Velocity,PE,Beta,SharpeRatio");
}
public void Display()
{
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}",
Symbol,
Utility.DateTimeToStringMMHDDHYYYY(PurchaseDate),
Shares,
Utility.AddQuotes(Utility.FormatCurrency(PurchasePrice)),
Utility.AddQuotes(Utility.FormatCurrency(Exposure)),
Volume,
Utility.AddQuotes(Utility.FormatPercent(Return1D)),
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)),
Utility.AddQuotes(Utility.FormatNumber(SharpeRatio))
));
}
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}",
Symbol,
Utility.DateTimeToStringMMHDDHYYYY(PurchaseDate),
Shares,
Utility.AddQuotes(Utility.FormatCurrency(PurchasePrice)),
Utility.AddQuotes(Utility.FormatCurrency(Exposure)),
Volume,
Utility.AddQuotes(Utility.FormatPercent(Return1D)),
Utility.DateTimeToStringMMHDDHYYYY(SellDate),
Utility.AddQuotes(Utility.FormatCurrency(CurrentPrice)),
Utility.AddQuotes(Utility.FormatCurrency(MarketValue)),
Utility.AddQuotes(Utility.FormatCurrency(GainLoss)),
Utility.FormatPercent(GainLossPcnt),
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)),
Utility.AddQuotes(Utility.FormatNumber(SharpeRatio))
));
}
}
}
// ****************************************************************************************************************************************************************
public class Positions : List<Position>
{
public Positions()
{
}
public Positions(Positions positions)
{
foreach(Position position in positions)Add(position);
}
public Positions(List<Position> positions)
{
foreach(Position position in positions)Add(position);
}
public Positions(Position position)
{
Add(position);
}
public void Add(Positions positions)
{
foreach(Position position in positions)Add(position);
}
public double Exposure
{
get
{
return (from Position position in this select position.PurchasePrice*position.Shares).Sum();
}
}
public double MarketValue
{
get
{
return (from Position position in this select position.CurrentPrice*position.Shares).Sum();
}
}
public NVPCollections ToNVPCollections()
{
NVPCollections nvpCollections=new NVPCollections();
foreach(Position position in this)
{
nvpCollections.Add(position.ToNVPCollection());
}
return nvpCollections;
}
public static Positions FromNVPCollections(NVPCollections nvpCollections)
{
Positions positions=new Positions();
foreach(NVPCollection nvpCollection in nvpCollections)
{
positions.Add(Position.FromNVPCollection(nvpCollection));
}
return positions;
}
public void DisplayTopFive()
{
Positions positions = new Positions(this.OrderByDescending(x => x.GainLossPcnt).Take(5).ToList());
Position.DisplayHeader();
for (int index = 0; index < positions.Count; index++)
{
Position position = positions[index];
position.Display();
}
}
public void DisplayBottomFive()
{
Positions positions = new Positions(this.OrderBy(x => x.GainLossPcnt).Take(5).ToList());
Position.DisplayHeader();
for (int index = 0; index < positions.Count; index++)
{
Position position = positions[index];
position.Display();
}
}
public void Display()
{
Position.DisplayHeader();
for(int index=0;index<Count;index++)
{
Position position=this[index];
position.Display();
}
if(this.Any(x=>Utility.IsEpoch(x.SellDate)||double.IsNaN(x.CurrentPrice)))
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("EXPOSURE:{0}", Utility.FormatCurrency(this.Exposure)));
}
else
{
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("GAIN/LOSS {0}", Utility.FormatCurrency(this.Sum(x => x.GainLoss))));
}
MDTrace.WriteLine(LogLevel.DEBUG,"****************************************************************************************************************************");
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MarketData.Generator.Momentum
{
public class QualityIndicator
{
public enum QualityType{IDIndicator=0,ScoreIndicator=1};
private QualityType qualityType;
public QualityIndicator()
{
qualityType=QualityType.IDIndicator;
}
public QualityIndicator(QualityType qualityType)
{
this.qualityType=qualityType;
}
public QualityIndicator(String strQualityType)
{
if(null==strQualityType)
{
qualityType=QualityType.IDIndicator;
return;
}
strQualityType=strQualityType.ToUpper();
if("SCOREINDICATOR".Equals(strQualityType))qualityType=QualityType.ScoreIndicator;
else qualityType=QualityType.IDIndicator;
}
public override String ToString()
{
if(qualityType.Equals(QualityType.ScoreIndicator))return "SCOREINDICATOR";
return "IDINDICATOR";
}
public static String ToString(QualityIndicator.QualityType qualityType)
{
if(qualityType.Equals(QualityType.ScoreIndicator))return "SCOREINDICATOR";
return "IDINDICATOR";
}
public static QualityType ToQuality(String strQualityType)
{
if(strQualityType.Equals("SCOREINDICATOR")) return QualityIndicator.QualityType.ScoreIndicator;
return QualityIndicator.QualityType.IDIndicator;
}
public QualityType Quality
{
get{return qualityType;}
set{qualityType=value;}
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Text;
using MarketData.MarketDataModel;
using MarketData.DataAccess;
using MarketData.Numerical;
using MarketData.Utils;
using System.Linq;
using MarketData.Helper;
namespace MarketData.Generator.Momentum
{
public class ScoreIndicator
{
private ScoreIndicator()
{
}
// This uses the same scoring mechanism as is used in Clenow Momentum which is based upon the log of returns.
// The higher the score the better the rank. The logPrices array gets filled with the most distant price in the lower indices. Hence the backward walk through the prices.
public static double Calculate(Prices prices)
{
double score=double.NaN;
if(null==prices || 0==prices.Count)return double.NaN;
double[] logPrices=new double[prices.Count];
for(int index=prices.Count-1;index>=0;index--)
{
Price price=prices[index];
logPrices[(prices.Count-index)-1]=Math.Log(price.Close);
}
LeastSquaresResultWithR2 leastSquaresResult=LeastSquaresHelper.CalculateLeastSquaresWithR2(logPrices);
double slope=leastSquaresResult.Slope;
double annualizedReturn=Math.Pow(Math.Exp(slope),252);
if(slope<0.00)annualizedReturn*=-1.00;
score=leastSquaresResult.RSquared*annualizedReturn;
return score;
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Text;
using MarketData.MarketDataModel;
using MarketData.DataAccess;
using MarketData.Utils;
using System.Linq;
using MarketData.Helper;
using MarketData.Numerical;
namespace MarketData.Generator.Momentum
{
public class SlotPositions : List<SlotPosition>
{
public SlotPositions()
{
}
public SlotPositions(int slot,Positions positions)
{
for(int index=0;index<positions.Count;index++)
{
Add(new SlotPosition(slot,positions[index]));
}
}
public NVPCollections ToNVPCollections()
{
NVPCollections nvpCollections=new NVPCollections();
foreach(SlotPosition position in this)
{
nvpCollections.Add(position.ToNVPCollection());
}
return nvpCollections;
}
public static SlotPositions FromNVPCollections(NVPCollections nvpCollections)
{
SlotPositions positions=new SlotPositions();
foreach(NVPCollection nvpCollection in nvpCollections)
{
positions.Add(SlotPosition.FromNVPCollection(nvpCollection));
}
return positions;
}
}
public class SlotPosition : Position
{
public SlotPosition()
{
}
public SlotPosition(int slot,Position position) : base(position)
{
Slot=slot;
}
public SlotPosition(Position position) : base(position)
{
}
public int Slot{get;set;}
public Position ToPosition()
{
Position position = new Position(this);
return position;
}
public override NVPCollection ToNVPCollection()
{
NVPCollection nvpCollection=base.ToNVPCollection();
nvpCollection.Insert(0,new NVP("Slot",Slot.ToString()));
return nvpCollection;
}
public static new SlotPosition FromNVPCollection(NVPCollection nvpCollection)
{
NVPDictionary nvpDictionary=nvpCollection.ToDictionary();
Position position=Position.FromNVPCollection(nvpCollection);
SlotPosition slotPosition=new SlotPosition(position);
slotPosition.Slot=nvpDictionary["Slot"].Get<int>();
return slotPosition;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

148
Books/History/Squadron.txt Normal file
View File

@@ -0,0 +1,148 @@
Squadron (aviation)
Article
Talk
Read
Edit
View history
Tools
Appearance
Birthday mode (Baby Globe)
Disabled
Enabled
Learn more about Birthday mode
Text
Small
Standard
Large
Width
Standard
Wide
Color (beta)
Automatic
Light
Dark
From Wikipedia, the free encyclopedia
For other uses, see Squadron.
"Fighter squadron" redirects here. For the film, see Fighter Squadron. For the video game, see Fighter Squadron: The Screamin' Demons Over Europe.
A United States Air Force F-86 Sabre squadron during the Korean War, 1951
vte
Air force units and formations
Section/element
Flight Escadrille (France) Schwarm (Germany)
Squadron Escadron (France) Staffel (Germany)
Wing (RAF) Group (USAAF/USAF) Geschwader (Germany) R<>giment/Escadre (France) Aviation regiment (USSR/Russia)
Group (RAF) Wing (USAAF/USAF) Brigade a<>rienne (France)
Air division Air Division (USAAF/USAF) Aviation division (USSR)
Tactical air force (RAF) Command (USAAF/USAF)
Command (RAF) Numbered air force (USAAF/USAF) Air army (USSR)
Air Force
A squadron in an air force, or naval or army aviation service, is a unit comprising a number of military aircraft and their aircrews, usually of the same type, typically with 12 to 24 aircraft, sometimes divided into three or four flights, depending on aircraft type and air force.[1]
In most armed forces, two or more squadrons will form a group or a wing. Some military forces (including the United States Air Force, United States Space Force, French Air and Space Force, Royal Air Force, German Air Force, Royal Netherlands Air Force, Belgian Air Force and Republic of Singapore Air Force) also use the term "squadron" for non-flying ground units (e.g. radar squadrons, missile squadrons, air defense squadrons, aircraft maintenance squadrons, security forces squadrons, civil engineering squadrons, range operations squadrons, range management squadrons, weather squadrons, medical squadrons, etc.).
Comparative organization
Organizational structure of flying units in selected NATO countries, by relative size Size
group[2] British and
USN USAF and
USMC USSF Canadian[3] French AAE German Air Force Italian Air Force NATO rank level[2]
of general or
commanding officer
8 Air division
(no longer used) Air division
Division a<>rienne Luftwaffendivision
(no longer used) Divisione aerea OF-7
7 Group Wing Delta (OF-5) Group[4]
Groupe a<>rien
(no longer used) Brigade A<>rienne Brigata aerea OF-5, or OF-6
6 Wing Group Wing
Escadre Escadre Geschwader[5] (OF-5) Stormo OF-4, OF-5, or OF-6
5 Squadron Squadron Squadron (OF-4) Squadron
Escadron Escadron Gruppe (OF-4) Gruppo OF-3 or OF-4
4 Flight Flight Flight
Escadrille Escadrille Staffel[5] (OF-3) Squadriglia OF-2 or OF-3
3 Flight Element/Section Section Section Schwarm[5] / Kette sezione OF-1 or OF-2
Germany
See also: Organization of the Luftwaffe (1933<33>1945) <20> Staffel
In World War I, the Imperial German Army used the term Squadron (staffel), whereas the Austro-Hungarian armed forces and the Swiss Army used the term company. In the modern German Air Force, a flying staffel is a battalion-equivalent, while a ground based support staffel is a company-equivalent. One such example are the air base defence units, which are squadrons (German, plural: Staffeln) formed into battalions. The ground based missile air defence units are also company- (in this case battery-)equivalent squadrons (staffeln).
Sweden
The Swedish Air Force adopted naval-like traditions in its formative years and for that historical reason calls its squadrons divisions (plural: divisioner). They are grouped into air flotillas (plural: flygflottiljer). During the Cold War the Swedish Army, Navy and Air Force each had their own integral helicopter arms. After the end of it in line with the mid-90s force reduction and reforms they were fused into the Swedish Armed Forces Helicopter Wing as a service, independent from the three main armed forces branches.[6] The Helicopter Wing adopted the term skvadron from the former Swedish Army Aviation for its units, which is squadron in its army company-equivalent meaning. In the early 2000s, the Swedish Air Force absorbed the Helicopter Wing as its fourth combat air wing. Unlike the US Air Force, where the name of the base and the units stationed at that base are not related to each other, the name of the wing (flotilla) is in general considered synonymous with the air base where the unit is stationed. For example, the air base where the F 10 wing is stationed (in <20>ngelholm) is commonly referred to as F 10 even though it is the name of the tactical unit. In general, this only applies as long as a wing is stationed at the base. Case in point is Uppsala-<2D>rna air base, an active military airport but since the tactical unit located there has been disbanded it is no longer referred to as F 16. These naming conventions have been inherited from the navy where Swedish military aviation has its roots.
United Kingdom and Commonwealth
During the infant years of combat aviation in World War I and specifically with the trench stalemate at the front military aircraft partially took over the reconnaissance role from the cavalry. With that in mind the British Royal Flying Corps adopted the squadron nomenclature. After the fusion of the Royal Flying Corps and the Royal Naval Air Service into an independent Royal Air Force, the new armed forces branch introduced its own system of ranks, with the commanders of squadrons becoming squadron leaders.
The rapid sophistication in technology and combat tactics has led to increased requirements and qualifications of the officers in command positions and the commanders of RAF flying squadrons were upgraded in the post-World War II period from squadron leaders to wing commanders. Today RAF flying squadrons are battalion-equivalents, while combat and combat service support ground squadrons such as communications or administrative squadrons are company-equivalents and still usually commanded by squadron leaders.
During the Second World War, a British fighter squadron was a fighting strength of 12 aircraft; this was achieved with about 16 aircraft and 20 pilots assigned to each squadron so the full force could be flown at any time. The squadron was subdivided into two flights - A and B, and then into sections of two or three aircraft - Red and Yellow, Blue and Green - for operations.[7]
Flying units in the Fleet Air Arm and Army Air Corps are also called squadrons. In the latter they are company-equivalent units, divided into flights and grouped into regiments.
In the Air Training Corps of the United Kingdom and many Commonwealth nations, a squadron is a group of cadets who parade regularly.
United States
In the United States Air Force, the squadron is the principal organizational unit.[8] An aggregation of two or more USAF squadrons will be designated as a group and two or more groups will be designated as a wing.[9]
USAF squadrons may be flying units composed of pilots and flight crews, with designations such as fighter squadron, bomb squadron, or airlift squadron. Fighter squadrons may support between 18 and 24 aircraft, while squadrons flying larger aircraft (e.g., bomber, cargo, reconnaissance) may support fewer aircraft. However, non-flying units also exist at the squadron level, such as missile squadrons, aircraft maintenance squadrons, intelligence squadrons, aerospace medicine squadrons, security forces squadrons, civil engineering squadrons and force support squadrons, as well as numerous other examples.[9]
USAF flying squadrons are typically commanded by an aeronautically rated officer in the rank of lieutenant colonel, although some particularly large squadrons, such as the 414th Combat Training Squadron that manages Red Flag training at Nellis AFB, Nevada will be commanded by an aeronautically rated officer in the rank of full colonel.[10] Non-flying squadrons are also usually commanded by an officer in the rank of lieutenant colonel, but some may also be commanded by officers in the rank of major.
Further information on U.S. Navy squadrons: Dictionary of American Naval Aviation Squadrons
In contrast to the organizational structure of United States Air Force units, where flying squadrons are separate from non-flying squadrons tasked with administrative, aircraft maintenance, or other support functions, flying squadrons in naval aviation in the United States (e.g., United States Navy and United States Marine Corps) typically contain both embedded administrative support functions and organizational level aircraft maintenance functions, plus all their associated personnel, as part of the total squadron manning.[11] With few exceptions, oversight of the majority of these non-flying functions is assigned to the squadron's naval aviators and naval flight officers as their "ground job" in addition to their regular flying duties.[12]
With few exceptions, most U.S. Navy flying squadrons are commanded by aeronautically designated officers in the rank of commander. Exceptions are primarily the Fleet Replacement Squadrons (FRS), which are often, though not always, commanded by aeronautically designated captains. Commanding officers (COs) of U.S. Navy flying squadrons other than FRS units will be assisted by an executive officer (XO) of the same rank who functions as a second-in-command and who will eventually "fleet up" and relieve the CO as the next CO.[11]
In United States Marine Corps Aviation, in addition to flying units that are patterned in similar fashion to their U.S. Navy counterparts, the nomenclature "squadron" in the Marine Corps is also used to designate all battalion-equivalent, aviation support organizations. These squadrons include: wing headquarters, tactical air command, air control, air support, aviation logistics, wing support, and wing communications squadrons. In contrast to their USN counterparts, USMC flying squadrons and aviation support squadrons, while having a commanding officer (CO) at the lieutenant colonel level, may not have an equivalent rank executive officer (XO), but are moving more toward the USN model. USMC aviation (Flying) squadron XO's are aeronautically designated officers in the rank of Lt.Col or Major.
Also in contrast to USAF flying squadrons, most tactical sea-based and land-based U.S. Naval Aviation squadrons (USN and USMC), vice training squadrons and test and evaluation squadrons, usually do not have more than 12 aircraft authorized/assigned at any one time. Exceptions are USN helicopter mine countermeasures squadrons (17 MH-53), USMC "composite" medium tilt-rotor squadrons assigned afloat as the Aviation Combat Element (ACE) of a Marine Expeditionary Unit (MEU), (12 MV-22s, 6 AH-1s, 4 CH-53s, 3 UH-1s, and 6 AV-8s). Other squadrons with a large number of Primary Aircraft Assigned (PAA) include Marine heavy helicopter squadrons (16 CH-53s), Marine light/attack helicopter squadrons (18 AH-1s and 9 UH-1s), and Marine attack squadrons (16 AV-8s).
Although part of U.S. naval aviation, United States Coast Guard aviation units are centered on an air station or air facility versus a squadron or group/wing organizational structure. The one exception to this is the Coast Guard's Helicopter Interdiction Squadron (HITRON), which is engaged primarily in counter-narcotics (CN) interdiction operations.[13]
In the United States Army Aviation Branch, flying units may be organized in battalions or squadrons (the latter for air cavalry only) reporting to an aviation brigade. Aircraft maintenance activities are typically assigned to an aviation maintenance company or element in the battalion or brigade.[14]
In the U.S. Civil Air Patrol (CAP), a squadron is the basic administrative unit. As the official civilian auxiliary of the U.S. Air Force, CAP follows the USAF organizational model.
Other countries
An escadron is the equivalent unit in France's French Air and Space Force (Arm<72>e de l'air et de l'espace). It is normally subdivided into escadrilles of eight aircraft. The Spanish Air and Space Force and some air forces of other Spanish-speaking countries follow that tradition (with a squadron called an escuadron and a flight called an escuadrilla), as does the Brazilian Air Force with esquadr<64>o and esquadrilha respectively.
The Royal Canadian Air Force and the Belgian Air Component on the other hand use escadrille as the equivalent of a squadron. The Italian Air Force uses gruppo (group) to denominate its squadrons, as does the Chilean Air Force (grupo de aviaci<63>n). The Portuguese Air Force (esquadra) and the Polish Air Force (eskadra taktyczna, 'tactical squadron') use the term squadron with its etymology originating from the naval and not the army meaning. The Czech Air Force and the Slovak Air Force use the generic term Letka as the squadron equivalent. The Turkish Air Force (filo) and the Hellenic Air Force (<28>???a ae??p??????, mira aeroporikis, 'aviation squadron') use the squadron denomination originating from the army term. The Royal Norwegian Air Force use the skvadron term also originating from the army term. So does the Hungarian Air Force with rep<65>losz<73>zad (Hungarian for 'aircraft squadron' or 'flying squadron'; the cavalry company-equivalent term is sz<73>zad).
Many Eastern European countries use the term originating from the French word escadrille:
The Royal Danish Air Force uses eskadrille, also originating from the French escadrille.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

13591
Books/Music/Jazz Theory.txt Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

10948
Books/War/Manstein.txt Normal file

File diff suppressed because it is too large Load Diff

593
Chartwell.py Normal file
View File

@@ -0,0 +1,593 @@
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
from gpt4all import GPT4All
import os
import re
import numpy as np
import json
from pathlib import Path
# Retrieval — find the most relevant chunks from your documents using embeddings and cosine similarity
# Augmented — add that retrieved context to the prompt
# Generation — use the language model to generate an answer based on that context
# IMPORTANT SETUP STEPS FOR RE-CREATING THIS ENVIORNMENT
# 1) Install python
# 3.10.11
# 2) Create venv
# python -m venv .venv
# .venv/Scripts/activate
# 3) Install Dependencies
# pip install -r requirements.txt
# 4) Meta-Llama-3-8B-Instruct.Q4_0.gguf
# \Users\skess\.cache\gpt4all\Meta-Llama-3-8B-Instruct.Q4_0.gguf
# The model will auto-download on the first run and then switch to allow_download=False (see below)
# The model is about 4.5G. The download is quick.
# lm_model = GPT4All("Meta-Llama-3-8B-Instruct.Q4_0.gguf",model_path=r"C:\Users\skess\.cache\gpt4all",device="gpu",allow_download=False)
# 5) huggingface This is for the sentence transformer (sentence-transformers/all-MiniLM-L6-v2)
# \Users\skess\.cache\huggingface There is a fodler structure under here.
# embed_model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2") This will automatically load the model if it is not already loaded
# so an internet connection would be required if running this from scratch
# IMPORTANT PYTHON NOTES - KEEP
# Python
# .venv/Scripts/Activate
# pip freeze > requirements.txt
# pip install -r requirements.txt
# Still on the to-do list:
# Fix the enrichment length cap
# Semantic chunking
# Better table handling
# -------------------
# Embedding Cleaning
# -------------------
# del embeddings_cache.npz
# del embeddings_cache_meta.json
# -------------------------
# Knowledge base selection
# -------------------------
BOOK_DIR = 'Books/History' # just a string
book_files = []
for f in Path(BOOK_DIR).rglob('*'):
if not f.is_file():
continue
try:
with open(f, 'r', encoding='utf-8'):
pass
book_files.append(str(f)) # store as string, not Path
except (UnicodeDecodeError, PermissionError):
continue
print(f"Found {len(book_files)} files")
# Overlap should be 10-20% of chunk size
CHUNK_SIZE = 700
CHUNK_OVERLAP = 100
DEBUG = False
CACHE_FILE = "embeddings_cache.npz"
CACHE_META = "embeddings_cache_meta.json"
MAX_HISTORY = 5
CURRENT_LEVEL = 10
SEARCH_FILTER = None # None = search all books
# -------------------------
# CONVERSATIONAL HISTORY
# -------------------------
conversation_history = []
# -------------------------
# LEVEL CONFIG
# -------------------------
LEVELS = {
1: {"expand": False, "top_k": 1, "max_tokens": 75, "context_len": 500},
2: {"expand": False, "top_k": 1, "max_tokens": 75, "context_len": 600},
3: {"expand": False, "top_k": 2, "max_tokens": 100, "context_len": 700},
4: {"expand": False, "top_k": 2, "max_tokens": 100, "context_len": 800},
5: {"expand": False, "top_k": 3, "max_tokens": 125, "context_len": 1000},
6: {"expand": False, "top_k": 3, "max_tokens": 150, "context_len": 1200},
7: {"expand": True, "top_k": 3, "max_tokens": 150, "context_len": 1400},
8: {"expand": True, "top_k": 4, "max_tokens": 175, "context_len": 1600},
9: {"expand": True, "top_k": 5, "max_tokens": 175, "context_len": 1800},
10: {"expand": True, "top_k": 5, "max_tokens": 200, "context_len": 2000},
}
# -------------------------
# Load models
# -------------------------
# -----------------------------------
# Load the sentence tranformer model
# -----------------------------------
print("Loading embedding model...")
embed_model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
# -----------------------------------
# Load the language model - If it does not exist in the download area then download it otherwise us it.
# -----------------------------------
print("Loading language model...")
model_file = "Meta-Llama-3-8B-Instruct.Q4_0.gguf"
model_path = r"C:\Users\skess\.cache\gpt4all"
full_path = os.path.join(model_path, model_file)
if not os.path.exists(full_path):
print("Model not found locally. Downloading...")
allow_download = True
else:
allow_download = False
lm_model = GPT4All(
model_file,
model_path=model_path,
device="gpu",
allow_download=allow_download
)
# -------------------------
# Clean text
# -------------------------
def clean_text(text):
text = re.sub(r'(\w+)-\n(\w+)', r'\1\2', text)
text = re.sub(r'\n+', ' ', text)
text = re.sub(r'(?<=[a-z])(\d{1,3})(?=\s[A-Z])', '', text)
text = re.sub(r'\s\d{1,4}\s', ' ', text)
text = re.sub(r'[■•◆▪→]', '', text)
text = re.sub(r' +', ' ', text)
return text.strip()
# -------------------------
# Chunk text with overlap
# -------------------------
def chunk_text(text, chunk_size=CHUNK_SIZE, overlap=CHUNK_OVERLAP):
# Step 1 — Split into paragraphs first
paragraphs = [p.strip() for p in re.split(r'\n\s*\n', text) if p.strip()]
# Step 2 — Split any overly long paragraphs into sentences
split_units = []
for para in paragraphs:
if len(para) <= chunk_size * 2:
split_units.append(para)
else:
# Break long paragraph into sentences
sentences = re.split(r'(?<=[.!?])\s+', para)
current = ""
for sentence in sentences:
if len(current) + len(sentence) <= chunk_size:
current += " " + sentence
else:
if current:
split_units.append(current.strip())
current = sentence
if current:
split_units.append(current.strip())
# Step 3 — Combine units into chunks up to chunk_size
# with overlap by re-including the previous unit
chunks = []
current_chunk = ""
prev_unit = ""
for unit in split_units:
if len(current_chunk) + len(unit) + 1 <= chunk_size:
current_chunk += " " + unit
else:
if current_chunk:
chunks.append(current_chunk.strip())
# Overlap — start new chunk with previous unit for context
if prev_unit and len(prev_unit) + len(unit) + 1 <= chunk_size:
current_chunk = prev_unit + " " + unit
else:
current_chunk = unit
prev_unit = unit
if current_chunk:
chunks.append(current_chunk.strip())
return chunks
# -------------------------
# Check if cache is valid
# -------------------------
def cache_is_valid():
if not os.path.exists(CACHE_FILE) or not os.path.exists(CACHE_META):
return False
with open(CACHE_META, "r") as f:
meta = json.load(f)
if meta.get("book_files") != book_files:
return False
for book_name in book_files:
if not os.path.exists(book_name):
continue
stored_size = meta.get("file_sizes", {}).get(book_name)
actual_size = os.path.getsize(book_name)
if stored_size != actual_size:
return False
return True
# -------------------------
# Load or build embeddings
# -------------------------
all_chunks = []
all_sources = []
if cache_is_valid():
print("Loading embeddings from cache...")
data = np.load(CACHE_FILE, allow_pickle=True)
chunk_embeddings = data["embeddings"]
all_chunks = list(data["chunks"])
all_sources = list(data["sources"])
print(f"Total chunks loaded from cache: {len(all_chunks)}")
else:
print("Building embeddings from scratch...")
for book_name in book_files:
if not os.path.exists(book_name):
print(f"Warning: {book_name} not found, skipping...")
continue
print(f"Loading {book_name}...")
with open(book_name, "r", encoding="utf-8") as f:
book_text = clean_text(f.read())
book_chunks = chunk_text(book_text)
all_chunks.extend(book_chunks)
all_sources.extend([book_name] * len(book_chunks))
print(f" -> {len(book_chunks)} chunks")
print(f"Total chunks: {len(all_chunks)}")
print("Embedding chunks (this may take a minute)...")
chunk_embeddings = embed_model.encode(all_chunks, convert_to_tensor=False)
print("Saving embeddings cache...")
np.savez(
CACHE_FILE,
embeddings=chunk_embeddings,
chunks=np.array(all_chunks, dtype=object),
sources=np.array(all_sources, dtype=object)
)
file_sizes = {b: os.path.getsize(b) for b in book_files if os.path.exists(b)}
with open(CACHE_META, "w") as f:
json.dump({"book_files": book_files, "file_sizes": file_sizes}, f)
print("Cache saved.")
# -------------------------
# Book filter helper
# -------------------------
def get_filtered_indices(filter_term):
"""Return indices of chunks whose source filename contains filter_term."""
if not filter_term:
return list(range(len(all_chunks)))
filter_lower = filter_term.lower()
return [i for i, src in enumerate(all_sources)
if filter_lower in os.path.basename(src).lower()]
def show_available_books():
"""Print a short list of available books with keywords."""
print("\n--- Available books ---")
for f in book_files:
base = os.path.basename(f).replace('.txt', '')
print(f" {base}")
print("--- Use 'search <keyword>: your question' to filter ---\n")
# -------------------------
# Query expansion
# -------------------------
def expand_query(question):
book_titles = ', '.join([os.path.basename(b).replace('.txt', '') for b in book_files])
prompt = (
f"You are helping search a library containing these documents:\n"
f"{book_titles}\n\n"
f"Generate 3 alternative ways to ask the following question using "
f"vocabulary, concepts, and terminology that would likely appear in "
f"these specific documents. Do not reference authors or books not in this list. "
f"The alternative questions must ask about the SAME specific fact as the original. "
f"Do not broaden or change the subject of the question. "
f"Return ONLY the 3 questions, one per line, no numbering, no explanation.\n\n"
f"Question: {question}"
)
with lm_model.chat_session():
response = lm_model.generate(prompt, max_tokens=150)
lines = [line.strip() for line in response.strip().split('\n') if line.strip()]
alternatives = [
l for l in lines
if len(l) > 15
and len(l) < 200
and '?' in l
and l != question
and ':' not in l[:20]
][:3]
all_queries = [question] + alternatives
print(f" [Expanded queries: {len(all_queries)}]")
for q in all_queries:
print(f" - {q}")
return all_queries
# ----------------------
# Topic Detection
# ----------------------
# Stopwords for topic detection
# -------------------------
STOPWORDS = {
"the","is","a","an","and","or","of","to","in","on","for","with",
"what","which","who","how","when","where","can","i","you","it",
"did","do","does","was","were","he","she","they","his","her",
"him","them","his","its","be","been","have","has","had","will",
"would","could","should","may","might","me","my","we","our"
}
def topics_are_related(question, history, lookback=3):
"""
Returns True if the question shares meaningful words
with recent conversation history.
Also returns True for very short pronoun-heavy questions
since they are almost certainly follow-ups.
"""
if not history:
return False
# Very short questions with pronouns are almost certainly follow-ups
q_lower = question.lower()
# Very short questions with pronouns are almost certainly follow-ups
pronoun_followups = {
"he","she","they","him","her","them","his","it",
"this","that","these","those","who","what","where","when"
}
q_words_all = set(q_lower.replace('?','').replace('.','').split())
if len(q_words_all) <= 5 and q_words_all & pronoun_followups:
print(f" [Pronoun follow-up detected — enriching]")
return True
# Get meaningful words from current question
q_words = set(q_lower.split()) - STOPWORDS
if not q_words:
return False
# Get words from recent history questions
recent = history[-lookback:]
history_words = set()
for exchange in recent:
history_words.update(exchange["question"].lower().split())
history_words -= STOPWORDS
# Check overlap
overlap = len(q_words & history_words)
print(f" [Topic overlap: {overlap} word(s)]")
return overlap > 0
def enrich_query_with_history(question):
if not conversation_history:
return question
if len(question.split()) >= 6:
return question
if not topics_are_related(question, conversation_history):
print(f" [Topic shift detected — no enrichment]")
return question
recent = conversation_history[-3:]
context_words = " ".join([ex["question"] for ex in recent])
enriched = f"{context_words} {question}"
# Don't enrich if result is too long — it will overwhelm the question
if len(enriched.split()) > 30:
print(f" [Enriched query too long — using original]")
return question
print(f" [Enriched query: {enriched}]")
return enriched
# -------------------------
# Retrieve top relevant chunks
# -------------------------
def get_top_chunks(question, filter_term=None):
level_cfg = LEVELS[CURRENT_LEVEL]
# Enrich short follow-up questions with history context
retrieval_question = enrich_query_with_history(question)
if level_cfg["expand"]:
queries = expand_query(retrieval_question)
else:
queries = [retrieval_question]
# Get filtered indices
search_indices = get_filtered_indices(filter_term)
if not search_indices:
print(f" [Warning: no books matched filter '{filter_term}' — searching all]")
search_indices = list(range(len(all_chunks)))
# Subset embeddings and metadata
sub_embeddings = chunk_embeddings[search_indices]
sub_chunks = [all_chunks[i] for i in search_indices]
sub_sources = [all_sources[i] for i in search_indices]
if filter_term:
matched_books = set(os.path.basename(s) for s in sub_sources)
print(f" [Filter '{filter_term}' matched: {', '.join(matched_books)}]")
# Score within filtered subset
sub_scores = np.zeros(len(sub_chunks))
for q in queries:
query_emb = embed_model.encode([q])
scores = cosine_similarity(query_emb, sub_embeddings)[0]
sub_scores += scores
sub_scores /= len(queries)
top_k = level_cfg["top_k"]
top_indices = sub_scores.argsort()[-top_k:][::-1]
return [sub_chunks[i] for i in top_indices], [sub_sources[i] for i in top_indices]
# -------------------------
# Parse search filter from input
# -------------------------
def parse_input(user_input):
"""
Detects 'search keyword: question' syntax.
Returns (question, filter_term) tuple.
"""
pattern = re.match(r'^search\s+(.+?):\s*(.+)$', user_input, re.IGNORECASE)
if pattern:
filter_term = pattern.group(1).strip()
question = pattern.group(2).strip()
return question, filter_term
return user_input, SEARCH_FILTER
# -------------------------
# Ask question
# -------------------------
def ask_question(question, show_sources=False, filter_term=None):
global conversation_history
level_cfg = LEVELS[CURRENT_LEVEL]
top_chunks, sources = get_top_chunks(question, filter_term=filter_term)
if DEBUG:
print("\n--- Retrieved chunks ---")
for i, chunk in enumerate(top_chunks):
print(f"\nChunk {i+1}:")
print(chunk[:300])
print("--- End chunks ---\n")
context = " ".join(top_chunks)[:level_cfg["context_len"]]
# Build conversation history string
history_text = ""
if conversation_history:
history_text = "Previous conversation:\n"
for exchange in conversation_history[-MAX_HISTORY:]:
history_text += f"Q: {exchange['question']}\n"
history_text += f"A: {exchange['answer']}\n"
history_text += "\n"
prompt = (
f"You are a helpful research assistant. "
f"Answer the question using ONLY the provided context. "
f"Be direct and concise. "
f"Only say 'I don't know' if the context contains absolutely nothing relevant. "
f"Do not reference outside sources. "
f"Do not repeat or echo the conversation history in your answer. "
f"Do not include 'Context:' or 'Q:' or 'A:' labels in your answer.\n\n"
f"Do not include separator lines or notes about your sources in your answer. "
)
if history_text:
prompt += (
f"--- BACKGROUND ONLY - DO NOT REPEAT ---\n"
f"{history_text}"
f"--- END BACKGROUND ---\n\n"
)
prompt += (
f"--- REFERENCE CONTEXT ---\n"
f"{context}\n"
f"--- END CONTEXT ---\n\n"
f"Question: {question}\n\n"
f"Answer:"
)
with lm_model.chat_session():
response = lm_model.generate(prompt, max_tokens=level_cfg["max_tokens"])
answer = response.strip()
conversation_history.append({
"question": question,
"answer": answer
})
if len(conversation_history) > MAX_HISTORY:
conversation_history = conversation_history[-MAX_HISTORY:]
if show_sources:
unique_sources = list(set(sources))
short_sources = [os.path.basename(s) for s in unique_sources]
print(f" [Sources: {', '.join(short_sources)}]")
print(f" [Level: {CURRENT_LEVEL} | "
f"expand={'on' if level_cfg['expand'] else 'off'} | "
f"top_k={level_cfg['top_k']} | "
f"max_tokens={level_cfg['max_tokens']}]")
print(f" [Memory: {len(conversation_history)} exchanges]")
if filter_term:
print(f" [Filter: '{filter_term}']")
return answer
# -------------------------
# Interactive loop
# -------------------------
print("\nReady! Ask questions about your books")
print("Commands: 'exit', 'sources on/off', 'level 1-10',")
print(" 'memory clear', 'memory show', 'debug on/off'")
print(" 'books' — list available books")
print(" 'search <keyword>: question' — filter by book\n")
show_sources = False
while True:
user_input = input(f"[L{CURRENT_LEVEL}] You: ")
if user_input.lower() in ["exit", "quit"]:
break
elif user_input.lower() == "memory clear":
conversation_history.clear()
print("Conversation memory cleared.")
continue
elif user_input.lower() == "memory show":
if not conversation_history:
print("No conversation history.")
else:
print(f"\n--- Last {len(conversation_history)} exchanges ---")
for i, exchange in enumerate(conversation_history):
print(f"\nQ{i+1}: {exchange['question']}")
print(f"A{i+1}: {exchange['answer'][:100]}...")
print("---\n")
continue
elif user_input.lower() == "debug on":
DEBUG = True
print("Debug mode enabled.")
continue
elif user_input.lower() == "debug off":
DEBUG = False
print("Debug mode disabled.")
continue
elif user_input.lower() == "sources on":
show_sources = True
print("Source display enabled.")
continue
elif user_input.lower() == "sources off":
show_sources = False
print("Source display disabled.")
continue
elif user_input.lower() == "books":
show_available_books()
continue
elif user_input.lower().startswith("level "):
try:
lvl = int(user_input.split()[1])
if 1 <= lvl <= 10:
CURRENT_LEVEL = lvl
cfg = LEVELS[CURRENT_LEVEL]
print(f"Level set to {CURRENT_LEVEL}"
f"expand={'on' if cfg['expand'] else 'off'}, "
f"top_k={cfg['top_k']}, "
f"max_tokens={cfg['max_tokens']}")
else:
print("Level must be between 1 and 10.")
except:
print("Usage: level 1 through level 10")
continue
# Parse for search filter
question, filter_term = parse_input(user_input)
response = ask_question(question, show_sources=show_sources, filter_term=filter_term)
print("Bot:", response)