Initial Commit
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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
BIN
.python-version
Normal file
Binary file not shown.
178
Books/Code/Momentum/ActivePositions.cs
Normal file
178
Books/Code/Momentum/ActivePositions.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
758
Books/Code/Momentum/Backtest.cs
Normal file
758
Books/Code/Momentum/Backtest.cs
Normal 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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Books/Code/Momentum/BacktestResult.cs
Normal file
17
Books/Code/Momentum/BacktestResult.cs
Normal 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;}
|
||||||
|
}
|
||||||
|
}
|
||||||
98
Books/Code/Momentum/CandidateSelector.cs
Normal file
98
Books/Code/Momentum/CandidateSelector.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Books/Code/Momentum/CandidateViolation.cs
Normal file
21
Books/Code/Momentum/CandidateViolation.cs
Normal 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>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Books/Code/Momentum/Candidates.cs
Normal file
53
Books/Code/Momentum/Candidates.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
Books/Code/Momentum/IDIndicator.cs
Normal file
64
Books/Code/Momentum/IDIndicator.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
242
Books/Code/Momentum/MGConfiguration.cs
Normal file
242
Books/Code/Momentum/MGConfiguration.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
164
Books/Code/Momentum/MGSessionManager.cs
Normal file
164
Books/Code/Momentum/MGSessionManager.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Books/Code/Momentum/MGSessionParams.cs
Normal file
32
Books/Code/Momentum/MGSessionParams.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
699
Books/Code/Momentum/MomentumGenerator.cs
Normal file
699
Books/Code/Momentum/MomentumGenerator.cs
Normal 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;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Books/Code/Momentum/PositionNotification.cs
Normal file
24
Books/Code/Momentum/PositionNotification.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
306
Books/Code/Momentum/Positions.cs
Normal file
306
Books/Code/Momentum/Positions.cs
Normal 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,"****************************************************************************************************************************");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Books/Code/Momentum/QualityIndicator.cs
Normal file
53
Books/Code/Momentum/QualityIndicator.cs
Normal 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;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Books/Code/Momentum/ScoreIndicator.cs
Normal file
39
Books/Code/Momentum/ScoreIndicator.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
77
Books/Code/Momentum/SlotPosition.cs
Normal file
77
Books/Code/Momentum/SlotPosition.cs
Normal 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
2717
Books/Finance/Accounting Made Simple_ Account - Mike Piper.txt
Normal file
2717
Books/Finance/Accounting Made Simple_ Account - Mike Piper.txt
Normal file
File diff suppressed because it is too large
Load Diff
5936
Books/Finance/Beat the Crowd - Kenneth L. Fisher.txt
Normal file
5936
Books/Finance/Beat the Crowd - Kenneth L. Fisher.txt
Normal file
File diff suppressed because it is too large
Load Diff
3806
Books/Finance/Common Stocks and Uncommon Prof - Philip A. Fisher.txt
Normal file
3806
Books/Finance/Common Stocks and Uncommon Prof - Philip A. Fisher.txt
Normal file
File diff suppressed because it is too large
Load Diff
2044
Books/Finance/Dividend Investing - Jenny Harrington.txt
Normal file
2044
Books/Finance/Dividend Investing - Jenny Harrington.txt
Normal file
File diff suppressed because it is too large
Load Diff
4002
Books/Finance/Mindset Secrets for Winning_ Ho - Mark Minervini.txt
Normal file
4002
Books/Finance/Mindset Secrets for Winning_ Ho - Mark Minervini.txt
Normal file
File diff suppressed because it is too large
Load Diff
1660
Books/Finance/PositionSizing - Unknown.txt
Normal file
1660
Books/Finance/PositionSizing - Unknown.txt
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4810
Books/Finance/Quantitative Value - Carlisle, Tobias, Gray, Wes.txt
Normal file
4810
Books/Finance/Quantitative Value - Carlisle, Tobias, Gray, Wes.txt
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3115
Books/Finance/Successful Traders Size Their P - Basso, Tom.txt
Normal file
3115
Books/Finance/Successful Traders Size Their P - Basso, Tom.txt
Normal file
File diff suppressed because it is too large
Load Diff
6582
Books/Finance/Super Trader, Expanded Edition - Van K. Tharp.txt
Normal file
6582
Books/Finance/Super Trader, Expanded Edition - Van K. Tharp.txt
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4616
Books/Finance/Think Trade Like a Champion by - Unknown.txt
Normal file
4616
Books/Finance/Think Trade Like a Champion by - Unknown.txt
Normal file
File diff suppressed because it is too large
Load Diff
4336
Books/Finance/Thinking in Systems_ A Primer - Donella H. Meadows.txt
Normal file
4336
Books/Finance/Thinking in Systems_ A Primer - Donella H. Meadows.txt
Normal file
File diff suppressed because it is too large
Load Diff
3101
Books/Finance/Trade Like a Stock Market Wizar - Mark Minervini.txt
Normal file
3101
Books/Finance/Trade Like a Stock Market Wizar - Mark Minervini.txt
Normal file
File diff suppressed because it is too large
Load Diff
26852
Books/Finance/Trend Following - Michael W. Covel.txt
Normal file
26852
Books/Finance/Trend Following - Michael W. Covel.txt
Normal file
File diff suppressed because it is too large
Load Diff
3146
Books/Finance/Value Investing - Bruce C. N. Greenwald;Judd Kahn.txt
Normal file
3146
Books/Finance/Value Investing - Bruce C. N. Greenwald;Judd Kahn.txt
Normal file
File diff suppressed because it is too large
Load Diff
4588
Books/Finance/Value Investing - Joseph D Piotroski.txt
Normal file
4588
Books/Finance/Value Investing - Joseph D Piotroski.txt
Normal file
File diff suppressed because it is too large
Load Diff
1983
Books/Finance/Way of the Turtle - Curtis Faith.txt
Normal file
1983
Books/Finance/Way of the Turtle - Curtis Faith.txt
Normal file
File diff suppressed because it is too large
Load Diff
13149
Books/History/Coming of the Third Reich, The - Richard J. Evans.txt
Normal file
13149
Books/History/Coming of the Third Reich, The - Richard J. Evans.txt
Normal file
File diff suppressed because it is too large
Load Diff
148
Books/History/Squadron.txt
Normal file
148
Books/History/Squadron.txt
Normal 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
31036
Books/History/The Last Lion Box Set _ Winston - William Manchester.txt
Normal file
31036
Books/History/The Last Lion Box Set _ Winston - William Manchester.txt
Normal file
File diff suppressed because it is too large
Load Diff
49805
Books/History/The Rise and Fall of the Third - William Shirer.txt
Normal file
49805
Books/History/The Rise and Fall of the Third - William Shirer.txt
Normal file
File diff suppressed because it is too large
Load Diff
18244
Books/History/The Third Reich at War - RICHARD J. EVANS.txt
Normal file
18244
Books/History/The Third Reich at War - RICHARD J. EVANS.txt
Normal file
File diff suppressed because it is too large
Load Diff
15624
Books/History/The Third Reich in Power - Richard J. Evans.txt
Normal file
15624
Books/History/The Third Reich in Power - Richard J. Evans.txt
Normal file
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
13591
Books/Music/Jazz Theory.txt
Normal file
File diff suppressed because it is too large
Load Diff
99971
Books/Religeon/Holy Bible (NIV) - Zondervan.txt
Normal file
99971
Books/Religeon/Holy Bible (NIV) - Zondervan.txt
Normal file
File diff suppressed because it is too large
Load Diff
8269
Books/Tax/2025 Publication 17 - W_CAR_MP_FP.txt
Normal file
8269
Books/Tax/2025 Publication 17 - W_CAR_MP_FP.txt
Normal file
File diff suppressed because one or more lines are too long
2035
Books/Tax/2025 Publication 501 - W_CAR_MP_FP.txt
Normal file
2035
Books/Tax/2025 Publication 501 - W_CAR_MP_FP.txt
Normal file
File diff suppressed because it is too large
Load Diff
2834
Books/Tax/2025 Publication 505 - W_CAR_MP_FP.txt
Normal file
2834
Books/Tax/2025 Publication 505 - W_CAR_MP_FP.txt
Normal file
File diff suppressed because one or more lines are too long
1471
Books/Tax/tiaa-wealth-management-2025-yea - Unknown.txt
Normal file
1471
Books/Tax/tiaa-wealth-management-2025-yea - Unknown.txt
Normal file
File diff suppressed because it is too large
Load Diff
10948
Books/War/Manstein.txt
Normal file
10948
Books/War/Manstein.txt
Normal file
File diff suppressed because it is too large
Load Diff
593
Chartwell.py
Normal file
593
Chartwell.py
Normal 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)
|
||||||
Reference in New Issue
Block a user