Optimize GetGainLossWithDetailByDateAndAccount
This commit is contained in:
@@ -44,6 +44,7 @@ namespace MarketData.Cache
|
|||||||
private DividendHistoryCache()
|
private DividendHistoryCache()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
lock(thisLock)
|
lock(thisLock)
|
||||||
@@ -52,6 +53,7 @@ namespace MarketData.Cache
|
|||||||
instance=null;
|
instance=null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DividendHistoryCache GetInstance()
|
public static DividendHistoryCache GetInstance()
|
||||||
{
|
{
|
||||||
lock(typeof(DividendHistoryCache))
|
lock(typeof(DividendHistoryCache))
|
||||||
@@ -60,6 +62,7 @@ namespace MarketData.Cache
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearCache()
|
public void ClearCache()
|
||||||
{
|
{
|
||||||
lock(typeof(DividendHistoryCache))
|
lock(typeof(DividendHistoryCache))
|
||||||
@@ -67,6 +70,7 @@ namespace MarketData.Cache
|
|||||||
dividendHistoryCache.Clear();
|
dividendHistoryCache.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(DividendHistory dividendHistory)
|
public void Add(DividendHistory dividendHistory)
|
||||||
{
|
{
|
||||||
lock(typeof(DividendHistoryCache))
|
lock(typeof(DividendHistoryCache))
|
||||||
@@ -84,6 +88,7 @@ namespace MarketData.Cache
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Add(String symbol,int divExYear,DividendHistory dividendHistory)
|
private void Add(String symbol,int divExYear,DividendHistory dividendHistory)
|
||||||
{
|
{
|
||||||
lock(typeof(DividendHistoryCache))
|
lock(typeof(DividendHistoryCache))
|
||||||
@@ -104,6 +109,7 @@ namespace MarketData.Cache
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DividendHistory GetDividendHistory(String symbol, int[] divExYears)
|
public DividendHistory GetDividendHistory(String symbol, int[] divExYears)
|
||||||
{
|
{
|
||||||
DividendHistory dividendHistory=new DividendHistory();
|
DividendHistory dividendHistory=new DividendHistory();
|
||||||
@@ -123,7 +129,8 @@ namespace MarketData.Cache
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dividendHistory;
|
return dividendHistory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DividendHistory GetDividendHistory(String symbol,int divExYear)
|
private DividendHistory GetDividendHistory(String symbol,int divExYear)
|
||||||
{
|
{
|
||||||
lock(typeof(DividendHistoryCache))
|
lock(typeof(DividendHistoryCache))
|
||||||
@@ -134,6 +141,7 @@ namespace MarketData.Cache
|
|||||||
return dividendHistoryByDixExYear[divExYear];
|
return dividendHistoryByDixExYear[divExYear];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ContainsDividendHistory(String symbol,int divExYear)
|
public bool ContainsDividendHistory(String symbol,int divExYear)
|
||||||
{
|
{
|
||||||
lock(typeof(DividendHistoryCache))
|
lock(typeof(DividendHistoryCache))
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ namespace MarketData.Generator
|
|||||||
int priorYear=currentYear-1;
|
int priorYear=currentYear-1;
|
||||||
|
|
||||||
DividendHistory dividendHistory=DividendHistoryCache.GetInstance().GetDividendHistory(symbol,new int[]{currentYear, priorYear});
|
DividendHistory dividendHistory=DividendHistoryCache.GetInstance().GetDividendHistory(symbol,new int[]{currentYear, priorYear});
|
||||||
// DividendHistory dividendHistory=DividendHistoryDA.GetDividendHistory(symbol,new int[] { currentYear,priorYear });
|
|
||||||
|
|
||||||
if(null==currentPrice||double.IsNaN(currentPrice.Close)||!currentPrice.Date.Year.Equals(currentYear)) return double.NaN;
|
if(null==currentPrice||double.IsNaN(currentPrice.Close)||!currentPrice.Date.Year.Equals(currentYear)) return double.NaN;
|
||||||
DividendHistory lastYearsDividendPayments=new DividendHistory((from DividendHistoryItem item in dividendHistory where item.DivExDate.Year==priorYear&&null!=item.CashAmount select item).ToList());
|
DividendHistory lastYearsDividendPayments=new DividendHistory((from DividendHistoryItem item in dividendHistory where item.DivExDate.Year==priorYear&&null!=item.CashAmount select item).ToList());
|
||||||
DividendHistory currentYearsDividendPayments=new DividendHistory((from DividendHistoryItem item in dividendHistory where item.DivExDate.Year==currentYear&&null!=item.CashAmount select item).ToList());
|
DividendHistory currentYearsDividendPayments=new DividendHistory((from DividendHistoryItem item in dividendHistory where item.DivExDate.Year==currentYear&&null!=item.CashAmount select item).ToList());
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
|
|
||||||
namespace MarketDataServer.Controllers
|
namespace MarketDataServer.Controllers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// GainLossController :
|
||||||
|
/// GetGainLossByDate(String token,DateTime selectedDate)
|
||||||
|
/// GetGainLossByDateAndAccount(String token,DateTime selectedDate,String account)
|
||||||
|
/// GetGainLossWithDetailByDate(String token,DateTime selectedDate)
|
||||||
|
/// GetGainLossWithDetailByDateAndAccount(String token, DateTime selectedDate, String account)
|
||||||
|
/// GetCompoundGainLoss(String token, int selectedDays, bool includeDividends)
|
||||||
|
/// </summary>
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]/[action]")]
|
[Route("api/[controller]/[action]")]
|
||||||
public class GainLossController : ControllerBase
|
public class GainLossController : ControllerBase
|
||||||
@@ -158,7 +167,6 @@ namespace MarketDataServer.Controllers
|
|||||||
gainLossSummaryItemDetail.AnnualDividend = exposure * weightAdjustedDividendYield;
|
gainLossSummaryItemDetail.AnnualDividend = exposure * weightAdjustedDividendYield;
|
||||||
}
|
}
|
||||||
Prices prices = LocalPriceCache.GetInstance().GetPrices(gainLossSummaryItem.Symbol, currentDate, 3); // cache should be refreshed after GainLossSummaryItemCollection
|
Prices prices = LocalPriceCache.GetInstance().GetPrices(gainLossSummaryItem.Symbol, currentDate, 3); // cache should be refreshed after GainLossSummaryItemCollection
|
||||||
// Prices prices = PricingDA.GetPrices(gainLossSummaryItem.Symbol, currentDate, 2);
|
|
||||||
Price p1 = prices.Count>0?prices[0]:default;
|
Price p1 = prices.Count>0?prices[0]:default;
|
||||||
Price p2 = prices.Count>1?prices[1]:default;
|
Price p2 = prices.Count>1?prices[1]:default;
|
||||||
PortfolioTrades symbolTrades = new PortfolioTrades(portfolioTrades.Where(x=>x.Symbol.Equals(gainLossSummaryItem.Symbol)).ToList());
|
PortfolioTrades symbolTrades = new PortfolioTrades(portfolioTrades.Where(x=>x.Symbol.Equals(gainLossSummaryItem.Symbol)).ToList());
|
||||||
@@ -218,105 +226,6 @@ namespace MarketDataServer.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [HttpGet]
|
|
||||||
// public IEnumerable<GainLossSummaryItemDetail> GetGainLossWithDetailByDate(String token,DateTime selectedDate)
|
|
||||||
// {
|
|
||||||
// Profiler profiler = new Profiler();
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// MDTrace.WriteLine(LogLevel.DEBUG,$"Start");
|
|
||||||
// if (!Authorizations.GetInstance().IsAuthorized(token)) return null;
|
|
||||||
// LocalPriceCache.GetInstance().Refresh();
|
|
||||||
// PortfolioTrades portfolioTrades = PortfolioDA.GetTrades();
|
|
||||||
// PortfolioTrades tradesOnOrBefore = portfolioTrades.GetTradesOnOrBefore(selectedDate);
|
|
||||||
// GainLossSummaryItemCollection gainLossSummaryItems = new GainLossSummaryItemCollection(tradesOnOrBefore, selectedDate);
|
|
||||||
|
|
||||||
// List<GainLossSummaryItemDetail> gainLossSummaryItemDetailCollection = new List<GainLossSummaryItemDetail>();
|
|
||||||
// foreach (GainLossSummaryItem gainLossSummaryItem in gainLossSummaryItems)
|
|
||||||
// {
|
|
||||||
// GainLossSummaryItemDetail gainLossSummaryItemDetail = new GainLossSummaryItemDetail(gainLossSummaryItem);
|
|
||||||
// portfolioTrades = PortfolioDA.GetOpenTradesSymbol(gainLossSummaryItem.Symbol);
|
|
||||||
// double weightAdjustedDividendYield = portfolioTrades.GetWeightAdjustedDividendYield();
|
|
||||||
// DateTime currentDate = PricingDA.GetLatestDate(gainLossSummaryItem.Symbol);
|
|
||||||
// if (null == portfolioTrades || 0 == portfolioTrades.Count) continue;
|
|
||||||
// double shares = (from PortfolioTrade portfolioTrade in portfolioTrades select portfolioTrade.Shares).Sum();
|
|
||||||
// double exposure = portfolioTrades.Sum(x => x.Exposure());
|
|
||||||
// if(null==gainLossGenerator) gainLossGenerator=new ActiveGainLossGenerator();
|
|
||||||
// GainLossCollection gainLoss=gainLossGenerator.GenerateGainLoss(portfolioTrades); // gainLoss contains the gain/loss from active positions. Never includes dividends .. just positions
|
|
||||||
// GainLossItem gainLossItem = gainLoss.OrderByDescending(x => x.GainLossPercent).FirstOrDefault();
|
|
||||||
// gainLossSummaryItemDetail.Lots = portfolioTrades.Count;
|
|
||||||
// gainLossSummaryItemDetail.Shares = shares;
|
|
||||||
// gainLossSummaryItemDetail.Exposure = exposure;
|
|
||||||
// if (!double.IsNaN(weightAdjustedDividendYield))
|
|
||||||
// {
|
|
||||||
// gainLossSummaryItemDetail.DividendYield = weightAdjustedDividendYield;
|
|
||||||
// gainLossSummaryItemDetail.AnnualDividend = exposure * weightAdjustedDividendYield;
|
|
||||||
// }
|
|
||||||
// ParityElement parityElement = ParityGenerator.GenerateBreakEven(gainLossSummaryItem.Symbol);
|
|
||||||
// gainLossSummaryItemDetail.ParityElement = parityElement;
|
|
||||||
// if (null != parityElement)
|
|
||||||
// {
|
|
||||||
// gainLossSummaryItemDetail.AllTimeGainLossPercent = gainLossItem.GainLossPercent;
|
|
||||||
// gainLossSummaryItemDetail.PercentDistanceFromAllTimeGainLossPercent = parityElement.ParityOffsetPercent - (gainLossItem.GainLossPercent / 100);
|
|
||||||
// }
|
|
||||||
// DateGenerator dateGenerator = new DateGenerator();
|
|
||||||
// DateTime priorDate = dateGenerator.FindPrevBusinessDay(currentDate);
|
|
||||||
// Price p1 = PricingDA.GetPrice(gainLossSummaryItem.Symbol, currentDate);
|
|
||||||
// Price p2 = PricingDA.GetPrice(gainLossSummaryItem.Symbol, priorDate);
|
|
||||||
// if (null == p2 && null != p1)
|
|
||||||
// {
|
|
||||||
// priorDate = dateGenerator.FindPrevBusinessDay(priorDate);
|
|
||||||
// p2 = PricingDA.GetPrice(gainLossSummaryItem.Symbol, priorDate);
|
|
||||||
// }
|
|
||||||
// if (null != p1 && null != p2)
|
|
||||||
// {
|
|
||||||
// double change = (p1.Close - p2.Close) / p2.Close;
|
|
||||||
// gainLossSummaryItemDetail.LatestPrice = p1;
|
|
||||||
// gainLossSummaryItemDetail.PriceChange = change;
|
|
||||||
// }
|
|
||||||
// gainLossSummaryItemDetailCollection.Add(gainLossSummaryItemDetail);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // **** Add an aggregate entry
|
|
||||||
// GainLossSummaryItemDetail gainLossSummaryTotals = new GainLossSummaryItemDetail();
|
|
||||||
// gainLossSummaryTotals.Symbol = "";
|
|
||||||
// gainLossSummaryTotals.CompanyName = "Account Summary";
|
|
||||||
// if (null != gainLossSummaryItemDetailCollection && gainLossSummaryItemDetailCollection.Count > 0)
|
|
||||||
// {
|
|
||||||
// gainLossSummaryTotals.Date = gainLossSummaryItemDetailCollection.Min(x => x.Date);
|
|
||||||
// gainLossSummaryTotals.Exposure = gainLossSummaryItemDetailCollection.Sum(x => x.Exposure);
|
|
||||||
// gainLossSummaryTotals.Change = gainLossSummaryItemDetailCollection.Sum(x => x.Change);
|
|
||||||
// gainLossSummaryTotals.CurrentGainLoss = gainLossSummaryItemDetailCollection.Sum(x => x.CurrentGainLoss);
|
|
||||||
// gainLossSummaryTotals.PreviousGainLoss = gainLossSummaryItemDetailCollection.Sum(x => x.PreviousGainLoss);
|
|
||||||
// gainLossSummaryTotals.ChangePercent = ((gainLossSummaryTotals.CurrentGainLoss - gainLossSummaryTotals.PreviousGainLoss) / Math.Abs(gainLossSummaryTotals.PreviousGainLoss)) * 100.00;
|
|
||||||
// gainLossSummaryTotals.LatestPrice = new Price();
|
|
||||||
// gainLossSummaryTotals.PriceChange = 0;
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// gainLossSummaryTotals.Date = selectedDate;
|
|
||||||
// gainLossSummaryTotals.Change = 0.00;
|
|
||||||
// gainLossSummaryTotals.CurrentGainLoss = 0.00;
|
|
||||||
// gainLossSummaryTotals.PreviousGainLoss = 0.00;
|
|
||||||
// gainLossSummaryTotals.ChangePercent = 0.00;
|
|
||||||
// gainLossSummaryTotals.LatestPrice = new Price();
|
|
||||||
// gainLossSummaryTotals.PriceChange = 0;
|
|
||||||
// }
|
|
||||||
// gainLossSummaryItemDetailCollection.Insert(0, gainLossSummaryTotals);
|
|
||||||
// // ****
|
|
||||||
// return gainLossSummaryItemDetailCollection;
|
|
||||||
// }
|
|
||||||
// catch(Exception exception)
|
|
||||||
// {
|
|
||||||
// MDTrace.WriteLine(LogLevel.DEBUG,$"Exception:{exception.ToString()}");
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// finally
|
|
||||||
// {
|
|
||||||
// MDTrace.WriteLine(LogLevel.DEBUG,$"Done, total took {profiler.End()} (ms)");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IEnumerable<GainLossSummaryItemDetail> GetGainLossWithDetailByDateAndAccount(String token, DateTime selectedDate, String account)
|
public IEnumerable<GainLossSummaryItemDetail> GetGainLossWithDetailByDateAndAccount(String token, DateTime selectedDate, String account)
|
||||||
{
|
{
|
||||||
@@ -330,6 +239,8 @@ namespace MarketDataServer.Controllers
|
|||||||
portfolioTrades = new PortfolioTrades(portfolioTrades.Where(x => x.Account.Equals(account)).ToList());
|
portfolioTrades = new PortfolioTrades(portfolioTrades.Where(x => x.Account.Equals(account)).ToList());
|
||||||
PortfolioTrades tradesOnOrBefore = portfolioTrades.GetTradesOnOrBefore(selectedDate);
|
PortfolioTrades tradesOnOrBefore = portfolioTrades.GetTradesOnOrBefore(selectedDate);
|
||||||
GainLossSummaryItemCollection gainLossSummaryItems = new GainLossSummaryItemCollection(tradesOnOrBefore, selectedDate);
|
GainLossSummaryItemCollection gainLossSummaryItems = new GainLossSummaryItemCollection(tradesOnOrBefore, selectedDate);
|
||||||
|
List<String> symbols = gainLossSummaryItems.Select(x => x.Symbol).ToList();
|
||||||
|
Dictionary<String,DateTime> latestDates = PricingDA.GetLatestDates(symbols);
|
||||||
|
|
||||||
List<GainLossSummaryItemDetail> gainLossSummaryItemDetailCollection=new List<GainLossSummaryItemDetail>();
|
List<GainLossSummaryItemDetail> gainLossSummaryItemDetailCollection=new List<GainLossSummaryItemDetail>();
|
||||||
foreach(GainLossSummaryItem gainLossSummaryItem in gainLossSummaryItems)
|
foreach(GainLossSummaryItem gainLossSummaryItem in gainLossSummaryItems)
|
||||||
@@ -337,7 +248,7 @@ namespace MarketDataServer.Controllers
|
|||||||
GainLossSummaryItemDetail gainLossSummaryItemDetail = new GainLossSummaryItemDetail(gainLossSummaryItem);
|
GainLossSummaryItemDetail gainLossSummaryItemDetail = new GainLossSummaryItemDetail(gainLossSummaryItem);
|
||||||
portfolioTrades = PortfolioDA.GetOpenTradesSymbol(gainLossSummaryItem.Symbol);
|
portfolioTrades = PortfolioDA.GetOpenTradesSymbol(gainLossSummaryItem.Symbol);
|
||||||
double weightAdjustedDividendYield = portfolioTrades.GetWeightAdjustedDividendYield();
|
double weightAdjustedDividendYield = portfolioTrades.GetWeightAdjustedDividendYield();
|
||||||
DateTime currentDate = PricingDA.GetLatestDate(gainLossSummaryItem.Symbol);
|
DateTime currentDate = latestDates[gainLossSummaryItem.Symbol];
|
||||||
if(null==portfolioTrades||0==portfolioTrades.Count)continue;
|
if(null==portfolioTrades||0==portfolioTrades.Count)continue;
|
||||||
double shares = (from PortfolioTrade portfolioTrade in portfolioTrades select portfolioTrade.Shares).Sum();
|
double shares = (from PortfolioTrade portfolioTrade in portfolioTrades select portfolioTrade.Shares).Sum();
|
||||||
double exposure = portfolioTrades.Sum(x => x.Exposure());
|
double exposure = portfolioTrades.Sum(x => x.Exposure());
|
||||||
@@ -352,23 +263,18 @@ namespace MarketDataServer.Controllers
|
|||||||
gainLossSummaryItemDetail.DividendYield=weightAdjustedDividendYield;
|
gainLossSummaryItemDetail.DividendYield=weightAdjustedDividendYield;
|
||||||
gainLossSummaryItemDetail.AnnualDividend=exposure * weightAdjustedDividendYield;
|
gainLossSummaryItemDetail.AnnualDividend=exposure * weightAdjustedDividendYield;
|
||||||
}
|
}
|
||||||
ParityElement parityElement = ParityGenerator.GenerateBreakEven(gainLossSummaryItem.Symbol);
|
Prices prices = LocalPriceCache.GetInstance().GetPrices(gainLossSummaryItem.Symbol, currentDate, 3); // cache should be refreshed after GainLossSummaryItemCollection
|
||||||
|
Price p1 = prices.Count>0?prices[0]:default;
|
||||||
|
Price p2 = prices.Count>1?prices[1]:default;
|
||||||
|
PortfolioTrades symbolTrades = new PortfolioTrades(portfolioTrades.Where(x=>x.Symbol.Equals(gainLossSummaryItem.Symbol)).ToList());
|
||||||
|
ParityElement parityElement = ParityGenerator.GenerateBreakEven(symbolTrades, p1);
|
||||||
gainLossSummaryItemDetail.ParityElement=parityElement;
|
gainLossSummaryItemDetail.ParityElement=parityElement;
|
||||||
if (null != parityElement)
|
if (null != parityElement)
|
||||||
{
|
{
|
||||||
gainLossSummaryItemDetail.AllTimeGainLossPercent=gainLossItem.GainLossPercent;
|
gainLossSummaryItemDetail.AllTimeGainLossPercent=gainLossItem.GainLossPercent;
|
||||||
gainLossSummaryItemDetail.PercentDistanceFromAllTimeGainLossPercent=parityElement.ParityOffsetPercent - (gainLossItem.GainLossPercent / 100);
|
gainLossSummaryItemDetail.PercentDistanceFromAllTimeGainLossPercent=parityElement.ParityOffsetPercent - (gainLossItem.GainLossPercent / 100);
|
||||||
}
|
}
|
||||||
DateGenerator dateGenerator = new DateGenerator();
|
if(null!=p1 && null!=p2)
|
||||||
DateTime priorDate = dateGenerator.FindPrevBusinessDay(currentDate);
|
|
||||||
Price p1 = PricingDA.GetPrice(gainLossSummaryItem.Symbol, currentDate);
|
|
||||||
Price p2 = PricingDA.GetPrice(gainLossSummaryItem.Symbol, priorDate);
|
|
||||||
if (null == p2 && null != p1)
|
|
||||||
{
|
|
||||||
priorDate = dateGenerator.FindPrevBusinessDay(priorDate);
|
|
||||||
p2 = PricingDA.GetPrice(gainLossSummaryItem.Symbol, priorDate);
|
|
||||||
}
|
|
||||||
if(null!=p1&&null!=p2)
|
|
||||||
{
|
{
|
||||||
double change = (p1.Close - p2.Close) / p2.Close;
|
double change = (p1.Close - p2.Close) / p2.Close;
|
||||||
gainLossSummaryItemDetail.LatestPrice=p1;
|
gainLossSummaryItemDetail.LatestPrice=p1;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ VisualStudioVersion = 17.5.2.0
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarketDataServer", "MarketDataServer.csproj", "{95632102-7AD8-9B9D-1583-0C986A476083}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarketDataServer", "MarketDataServer.csproj", "{95632102-7AD8-9B9D-1583-0C986A476083}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarketDataLib", "..\MarketData\MarketDataLib\MarketDataLib.csproj", "{40159117-A4E2-F689-5B94-20FB81B71B98}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|||||||
Reference in New Issue
Block a user