Add logic to all models to avoid selling and immediately buying back the same security as this is considered a Wash Trade and is illegal.

This commit is contained in:
2026-03-19 19:46:19 -04:00
parent 682ba74241
commit 554f577803
7 changed files with 109 additions and 25 deletions

View File

@@ -392,6 +392,7 @@ namespace MarketData.Generator.CMMomentum
else
{
Positions slotPositions = ActivePositions[slotIndex];
List<String> slotSymbols = slotPositions.ConvertAll(x=>x.Symbol); // capture sell symbols to exclude from purchases to eliminate wash trades
SellPositions(slotPositions, TradeDate);
DisplaySales(slotPositions, TradeDate);
MDTrace.WriteLine(LogLevel.DEBUG, "********************* S E L L *********************");
@@ -401,7 +402,7 @@ namespace MarketData.Generator.CMMomentum
ActivePositions[slotIndex].Clear();
DisplayBalance();
double cashAllocation = Math.Min(CashBalance, (ActivePositions.GetExposure() + CashBalance) / HoldingPeriod); // Even out the cash allocation so that no one slot eats up all the cash
Positions positions=BuyPositions(slotIndex,TradeDate,AnalysisDate,cashAllocation,SymbolsHeld());
Positions positions=BuyPositions(slotIndex,TradeDate,AnalysisDate,cashAllocation,new List<String>(SymbolsHeld().Concat(slotSymbols)));
DisplayPurchases(positions, TradeDate);
MDTrace.WriteLine(LogLevel.DEBUG,"********************** B U Y ********************");
positions.Display();

View File

@@ -623,7 +623,7 @@ namespace MarketData.Generator.CMTrend
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TradeDate ({0}) must be greater than or equal to the max position date ({1}) in the trade file.",TradeDate.ToShortDateString(),ActivePositions.Select(x => x.PurchaseDate).Max().ToShortDateString()));
return result;
}
ManageOpenPositions(TradeDate);
List<String> closedSymbols = ManageOpenPositions(TradeDate); // capture any closed symbols so we don't re-enter immediately (wash trades)
ManageCandidates(TradeDate);
// ************************************************************************************************************************************************************************
// **************************************************************************** N E W P O S I T I O N S *****************************************************************
@@ -641,7 +641,7 @@ namespace MarketData.Generator.CMTrend
result.Success=true;
return result;
}
Positions positions=BuyCandidates(TradeDate,CashBalance,ActivePositions.GetSymbols());
Positions positions=BuyCandidates(TradeDate,CashBalance,new List<String>(ActivePositions.GetSymbols().Concat(closedSymbols)));
if(null != positions && 0!=positions.Count)
{
MDTrace.WriteLine(LogLevel.DEBUG,"******************** B U Y ********************");
@@ -735,7 +735,7 @@ namespace MarketData.Generator.CMTrend
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TradeDate ({0}) must be greater than or equal to the max position date ({1}) in the trade file.",TradeDate.ToShortDateString(),ActivePositions.Select(x => x.PurchaseDate).Max().ToShortDateString()));
return result;
}
ManageOpenPositions(TradeDate);
List<String> closedSymbols = ManageOpenPositions(TradeDate); // capture any closed symbols so we don't re-enter immediately (wash trades)
ManageCandidates(TradeDate);
if(ActivePositions.PositionsOn(TradeDate)>=Parameters.MaxDailyPositions)
{
@@ -743,7 +743,7 @@ namespace MarketData.Generator.CMTrend
result.Success=true;
continue;
}
Positions positions=BuyCandidates(TradeDate,CashBalance,ActivePositions.GetSymbols());
Positions positions=BuyCandidates(TradeDate,CashBalance,new List<String>(ActivePositions.GetSymbols().Concat(closedSymbols)));
if(null!=positions&&0!=positions.Count)
{
MDTrace.WriteLine(LogLevel.DEBUG,"******************** B U Y ********************");
@@ -884,11 +884,11 @@ namespace MarketData.Generator.CMTrend
// ***********************************************************************************************************************************************************************
// *********************************************************************** M A N A G E O P E N P O S I T I O N S *****************************************************
// ***********************************************************************************************************************************************************************
private void ManageOpenPositions(DateTime tradeDate)
private List<String> ManageOpenPositions(DateTime tradeDate)
{
if(0==ActivePositions.Count) return;
List<String> closedSymbols = new List<String>();
if(0==ActivePositions.Count) return closedSymbols;
Positions closedPositions = new Positions();
// List<Position> closedPositions=new List<Position>();
foreach(Position position in ActivePositions)
{
Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,tradeDate);
@@ -960,6 +960,8 @@ namespace MarketData.Generator.CMTrend
ActivePositions.Remove(closedPosition);
}
}
if(closedPositions.Count>0)closedSymbols = closedPositions.ConvertAll(x => x.Symbol);
return closedSymbols;
}
// **********************************************************************************************************************************************************

View File

@@ -509,8 +509,10 @@ namespace MarketData.Generator.MGSHMomentum
public void SellAndBuySlotPositions(int slotIndex)
{
MGSHPositions slotPositions=ActivePositions[slotIndex];
List<String> closedSymbols = new List<string>();
if(!Configuration.KeepSlotPositions) // if we are not configured to KeepSlotPositions then don't sell anything just buy to the max positions allowed for the slot
{
closedSymbols = slotPositions.ConvertAll(x => x.Symbol);
SellPositions(slotPositions,TradeDate);
DisplaySales(slotPositions, TradeDate);
MDTrace.WriteLine(LogLevel.DEBUG,"********************* S E L L *********************");
@@ -524,7 +526,7 @@ namespace MarketData.Generator.MGSHMomentum
int positionsToFill = MaxPositions-slotPositions.Count;
double cashAllocation = (CashBalance / ((HoldingPeriod * MaxPositions) - ActivePositions.GetCount()))*positionsToFill; // split the cash between the total positions we can own less the number of positions we have
MGSHPositions positions = null;
positions=BuyPositions(slotIndex, TradeDate,AnalysisDate,cashAllocation,SymbolsHeld(TradeDate), positionsToFill);
positions=BuyPositions(slotIndex, TradeDate,AnalysisDate,cashAllocation,new List<String>(SymbolsHeld(TradeDate).Concat(closedSymbols)), positionsToFill);
if(CashBalance-positions.Exposure<=0.00)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));

View File

@@ -380,6 +380,7 @@ namespace MarketData.Generator.Momentum
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();
@@ -392,7 +393,7 @@ namespace MarketData.Generator.Momentum
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,SymbolsHeld(TradeDate));
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)