Files
MBN_STOCK_WEBVIEW/LegacySceneRuntimeFactory.cs

673 lines
33 KiB
C#

using MBN_STOCK_WEBVIEW.Core.Playout;
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
using MBN_STOCK_WEBVIEW.Infrastructure;
using MMoneyCoderSharp.Data;
namespace MBN_STOCK_WEBVIEW;
/// <summary>
/// Application composition root for native DB-to-scene routes. Routes are added only
/// when their explicit loader and mutation builder have been ported and tested.
/// </summary>
internal static class LegacySceneRuntimeFactory
{
public static LegacyPlayoutWorkflow Create(
IPlayoutEngine engine,
IDataQueryExecutor executor,
LegacySceneCueCompositionOptions? compositionOptions = null,
ILegacySceneCueCompositionOptionsSource? compositionOptionsSource = null,
IS5025TrustedManualDataSource? trustedManualSource = null,
ITrustedViStockNameSnapshotSource? trustedViSource = null)
{
ArgumentNullException.ThrowIfNull(engine);
ArgumentNullException.ThrowIfNull(executor);
var revenueLoader = new S5076SceneDataLoader(executor);
var profitLoader = new S5081SceneDataLoader(executor);
var tradingLoader = new S5082SceneDataLoader(executor);
var nxtDetailLoader = new S5006NxtSceneDataLoader(executor);
var domesticDetailLoader = new S5006DomesticSceneDataLoader(executor);
var equityDetailLoader = new S5011SceneDataLoader(executor);
var threePanelLoader = new S5016SceneDataLoader(executor);
var twoPanelLoader = new S50160SceneDataLoader(executor);
var imageQuoteLoader = new S6001SceneDataLoader(executor);
var worldMapLoader = new S8067SceneDataLoader(executor);
var candleLoader = new S8010SceneDataLoader(executor);
var fiveRowLoader = new S5074SceneDataLoader(executor);
var sixRowLoader = new S5077SceneDataLoader(executor);
var twelveRowLoader = new S5088SceneDataLoader(executor);
var parameterizedRequestResolver = new LegacyParameterizedSceneRequestResolver(executor);
var oneColumnLoader = new S5001SceneDataLoader(executor);
var futuresPairLoader = new S5032SceneDataLoader(executor);
var twoColumnLoader = new S8018SceneDataLoader(executor);
var sectorSquareLoader = new S8001SceneDataLoader(executor);
var comparisonRequestResolver = new ComparisonAndYieldLegacyRequestResolver(executor);
var candlePairLoader = new S5026SceneDataLoader(executor);
var comparisonYieldLoader = new S5029SceneDataLoader(executor);
var singleYieldLoader = new S5086SceneDataLoader(executor);
var quotedYieldLoader = new S50860SceneDataLoader(executor);
var linePairLoader = new S5087SceneDataLoader(executor);
var chartRequestResolver = new ChartLegacySceneRequestResolver();
var sectorIndexLoader = new S5078SceneDataLoader(executor);
var growthMetricsLoader = new S5079SceneDataLoader(executor);
var salesLoader = new S5080SceneDataLoader(executor);
var participantTrendLoader = new S5083SceneDataLoader(executor);
var indexTrendLoader = new S5084SceneDataLoader(executor);
if (trustedManualSource is null)
{
var manualDataDirectory = Environment.GetEnvironmentVariable(
S5025TrustedManualFileDataSource.DirectoryEnvironmentVariable);
trustedManualSource = string.IsNullOrWhiteSpace(manualDataDirectory)
? null
: new S5025TrustedManualFileDataSource(manualDataDirectory);
}
var gridMarketRequestResolver = new LegacyGridMarketSceneRequestResolver(
executor,
trustedManualSource);
var dailyInvestorLoader = new S5023SceneDataLoader(executor);
var investorBarLoader = new S5024SceneDataLoader(executor);
var traderLoader = new S5037SceneDataLoader(executor);
var programTradingLoader = new S5085SceneDataLoader(executor);
var institutionNetBuyLoader = new S6067SceneDataLoader(executor);
var orderBookLoader = new S8003SceneDataLoader(executor);
var dataSource = new LegacySceneDataSourceRouter(
[
new LegacySceneDataSourceRoute(["5076"], async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var selection = RequireSelection(entry);
var data = await revenueLoader.LoadAsync(
selection.Subject,
cancellationToken).ConfigureAwait(false);
return new LegacySceneDataPage("s5076", data, 1);
}),
new LegacySceneDataSourceRoute(["5081"], async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var selection = RequireSelection(entry);
var data = await profitLoader.LoadAsync(
selection.Subject,
cancellationToken).ConfigureAwait(false);
return new LegacySceneDataPage("s5081", data, 1);
}),
new LegacySceneDataSourceRoute(["5082"], async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var data = await tradingLoader.LoadAsync(cancellationToken).ConfigureAwait(false);
return new LegacySceneDataPage("s5082", data, 1);
}),
new LegacySceneDataSourceRoute(["5001", "N5001"], async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var request = parameterizedRequestResolver.CreateS5001Request(entry);
var data = await oneColumnLoader.LoadAsync(request, cancellationToken)
.ConfigureAwait(false);
return new LegacySceneDataPage("s5001", data, 1);
}),
new LegacySceneDataSourceRoute(["8018", "8032", "5032"], async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var resolved = await parameterizedRequestResolver.ResolveTwoColumnRequestAsync(
entry,
cancellationToken).ConfigureAwait(false);
return resolved switch
{
LegacyS5032LoadRequest request => new LegacySceneDataPage(
"s5032",
await futuresPairLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
LegacyS8018LoadRequest request => new LegacySceneDataPage(
"s8018",
await twoColumnLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
_ => throw new LegacySceneDataException(
"The two-column scene selection is invalid.")
};
}),
new LegacySceneDataSourceRoute(["8001", "8002"], async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var request = parameterizedRequestResolver.CreateS8001Request(entry);
var data = await sectorSquareLoader.LoadAsync(request, cancellationToken)
.ConfigureAwait(false);
return new LegacySceneDataPage("s8001", data, 1);
}),
new LegacySceneDataSourceRoute(
["5026", "5029", "5086", "50860", "5087"],
async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var resolved = await comparisonRequestResolver.ResolveAsync(
entry,
cancellationToken).ConfigureAwait(false);
return resolved switch
{
LegacyS5026SceneLoadRequest request => new LegacySceneDataPage(
"s5026",
await candlePairLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
LegacyS5029SceneLoadRequest request => new LegacySceneDataPage(
"s5029",
await comparisonYieldLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
LegacyS5086SceneLoadRequest request => new LegacySceneDataPage(
"s5086",
await singleYieldLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
LegacyS50860SceneLoadRequest request => new LegacySceneDataPage(
"s50860",
await quotedYieldLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
LegacyS5087SceneLoadRequest request => new LegacySceneDataPage(
"s5087",
await linePairLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
_ => throw new LegacySceneDataException(
"The comparison scene selection is invalid.")
};
}),
new LegacySceneDataSourceRoute(
["5078", "5079", "5080", "5083", "5084"],
async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var resolved = chartRequestResolver.Resolve(entry);
return resolved switch
{
LegacyS5078ChartLoadRequest request => new LegacySceneDataPage(
"s5078",
await sectorIndexLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
LegacyS5079ChartLoadRequest request => new LegacySceneDataPage(
"s5079",
await growthMetricsLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
LegacyS5080ChartLoadRequest request => new LegacySceneDataPage(
"s5080",
await salesLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
LegacyS5083ChartLoadRequest request => new LegacySceneDataPage(
"s5083",
await participantTrendLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
LegacyS5084ChartLoadRequest request => new LegacySceneDataPage(
"s5084",
await indexTrendLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
_ => throw new LegacySceneDataException(
"The chart scene selection is invalid.")
};
}),
new LegacySceneDataSourceRoute(
["5023", "5024", "5025", "5037", "5085", "6067", "8003"],
async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var resolved = await gridMarketRequestResolver.ResolveAsync(
entry,
cancellationToken).ConfigureAwait(false);
return resolved switch
{
LegacyS5023SceneLoadRequest request => new LegacySceneDataPage(
"s5023",
await dailyInvestorLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
LegacyS5024SceneLoadRequest request => new LegacySceneDataPage(
"s5024",
await investorBarLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
LegacyS5025SceneLoadRequest => new LegacySceneDataPage(
"s5025",
await gridMarketRequestResolver.LoadTrustedS5025Async(
entry,
cancellationToken).ConfigureAwait(false),
1),
LegacyS5037SceneLoadRequest request => new LegacySceneDataPage(
"s5037",
await traderLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
LegacyS5085SceneLoadRequest => new LegacySceneDataPage(
"s5085",
await programTradingLoader.LoadAsync(cancellationToken)
.ConfigureAwait(false),
1),
LegacyS6067SceneLoadRequest => new LegacySceneDataPage(
"s6067",
await institutionNetBuyLoader.LoadAsync(cancellationToken)
.ConfigureAwait(false),
1),
LegacyS8003SceneLoadRequest request => new LegacySceneDataPage(
"s8003",
await orderBookLoader.LoadAsync(request.Request, cancellationToken)
.ConfigureAwait(false),
1),
_ => throw new LegacySceneDataException(
"The grid-market scene selection is invalid.")
};
}),
new LegacySceneDataSourceRoute(["5006"], async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var selection = RequireSelection(entry);
var market = ParseDomesticMarket(selection.GroupCode);
S5006SceneData data;
if (market is LegacyDomesticEquityMarket.NxtKospi or
LegacyDomesticEquityMarket.NxtKosdaq)
{
data = await nxtDetailLoader.LoadAsync(
market,
selection.DataCode,
cancellationToken).ConfigureAwait(false);
}
else
{
data = await domesticDetailLoader.LoadAsync(
new S5006DomesticSceneDataRequest(market, selection.DataCode),
cancellationToken).ConfigureAwait(false);
}
return new LegacySceneDataPage("s5006", data, 1);
}),
new LegacySceneDataSourceRoute(["5011"], async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var selection = RequireSelection(entry);
var data = await equityDetailLoader.LoadAsync(
new S5011SceneDataRequest(
ParseDomesticMarket(selection.GroupCode),
ParseClosedEnum<S5011DetailBranch>(selection.Subtype, "5011 detail branch"),
selection.DataCode),
cancellationToken).ConfigureAwait(false);
return new LegacySceneDataPage("s5011", data, 1);
}),
new LegacySceneDataSourceRoute(["5016"], async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var selection = RequireSelection(entry);
var data = await threePanelLoader.LoadAsync(
ParseClosedEnum<S5016PanelTarget>(selection.Subtype, "5016 panel target"),
cancellationToken).ConfigureAwait(false);
return new LegacySceneDataPage("s5016", data, 1);
}),
new LegacySceneDataSourceRoute(["50160"], async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var selection = RequireSelection(entry);
var data = await twoPanelLoader.LoadAsync(
ParseClosedEnum<S50160PanelTarget>(selection.Subtype, "50160 panel target"),
cancellationToken).ConfigureAwait(false);
return new LegacySceneDataPage("s50160", data, 1);
}),
new LegacySceneDataSourceRoute(["6001"], async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var selection = RequireSelection(entry);
var data = await imageQuoteLoader.LoadAsync(
ParseClosedEnum<S6001QuoteTarget>(selection.Subtype, "6001 quote target"),
cancellationToken).ConfigureAwait(false);
return new LegacySceneDataPage("s6001", data, 1);
}),
new LegacySceneDataSourceRoute(
["8067", "5068", "5070", "5072"],
async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var data = await worldMapLoader.LoadAsync(cancellationToken)
.ConfigureAwait(false);
return new LegacySceneDataPage("s8067", data, 1);
}),
new LegacySceneDataSourceRoute(
["8035", "8061", "8040", "8046", "8051", "8056"],
async (entry, page, cancellationToken) =>
{
RequireFirstPage(page);
var selection = RequireSelection(entry);
var target = ParseCandleMarket(selection.GroupCode);
var foreignIdentity = target is
CandleMarketTarget.OverseasIndex or CandleMarketTarget.OverseasStock
? ParseForeignCandleIdentity(selection.DataCode, target)
: default;
var request = new S8010SceneDataRequest(
ParseCandleAlias(entry.CutCode),
target,
ParseCandleMode(selection.Subtype),
CandleRequiresInstrument(target)
? RequireLookupValue(selection.Subject, "instrument")
: null,
HasClosedFlag(selection.GraphicType, "MA5"),
HasClosedFlag(selection.GraphicType, "MA20"),
foreignIdentity.Symbol,
foreignIdentity.NationCode);
var data = await candleLoader.LoadAsync(
request,
cancellationToken).ConfigureAwait(false);
return new LegacySceneDataPage("s8010", data, 1);
}),
new LegacySceneDataSourceRoute(["5074"], async (entry, page, cancellationToken) =>
{
var selection = RequireSelection(entry);
var request = selection.GroupCode == "PAGED_VI"
? await TrustedViManualReference.ResolveAsync(
selection,
page,
trustedViSource,
cancellationToken).ConfigureAwait(false)
: CreatePagedQuoteRequest(selection, page);
var data = await fiveRowLoader.LoadAsync(request, cancellationToken)
.ConfigureAwait(false);
return new LegacySceneDataPage("s5074", data, data.Rows.Count);
}, async (entry, cancellationToken) =>
{
var selection = RequireSelection(entry);
var request = selection.GroupCode == "PAGED_VI"
? await TrustedViManualReference.ResolveAsync(
selection,
pageIndexZeroBased: 0,
trustedSource: trustedViSource,
cancellationToken: cancellationToken).ConfigureAwait(false)
: CreatePagedQuoteRequest(selection, 0);
var data = await fiveRowLoader.LoadPagePlanAsync(request, cancellationToken)
.ConfigureAwait(false);
return new LegacySceneDataPage("s5074", data, data.Rows.Count);
}),
new LegacySceneDataSourceRoute(["5077"], async (entry, page, cancellationToken) =>
{
var request = CreatePagedQuoteRequest(RequireSelection(entry), page);
var data = await sixRowLoader.LoadAsync(request, cancellationToken)
.ConfigureAwait(false);
return new LegacySceneDataPage("s5077", data, data.Rows.Count);
}, async (entry, cancellationToken) =>
{
var request = CreatePagedQuoteRequest(RequireSelection(entry), 0);
var data = await sixRowLoader.LoadPagePlanAsync(request, cancellationToken)
.ConfigureAwait(false);
return new LegacySceneDataPage("s5077", data, data.Rows.Count);
}),
new LegacySceneDataSourceRoute(["5088"], async (entry, page, cancellationToken) =>
{
var request = CreatePagedQuoteRequest(RequireSelection(entry), page);
var data = await twelveRowLoader.LoadAsync(request, cancellationToken)
.ConfigureAwait(false);
return new LegacySceneDataPage("s5088", data, data.Rows.Count);
}, async (entry, cancellationToken) =>
{
var request = CreatePagedQuoteRequest(RequireSelection(entry), 0);
var data = await twelveRowLoader.LoadPagePlanAsync(request, cancellationToken)
.ConfigureAwait(false);
return new LegacySceneDataPage("s5088", data, data.Rows.Count);
})
]);
LegacySceneRuntimeCoverage.Validate(dataSource);
if (compositionOptions is not null && compositionOptionsSource is not null)
{
throw new ArgumentException(
"Specify either fixed or mutable scene composition options, not both.");
}
var cueProvider = compositionOptionsSource is null
? new RegisteredLegacySceneCueProvider(dataSource, options: compositionOptions)
: new RegisteredLegacySceneCueProvider(dataSource, compositionOptionsSource);
return new LegacyPlayoutWorkflow(engine, cueProvider);
}
private static LegacySceneSelection RequireSelection(LegacyPlaylistEntry entry) =>
entry.Selection ?? throw new LegacySceneDataException(
"The selected cut requires native database lookup information.");
private static void RequireFirstPage(int pageIndexZeroBased)
{
if (pageIndexZeroBased != 0)
{
throw new LegacySceneDataException("The selected cut is not paged.");
}
}
private static LegacyDomesticEquityMarket ParseDomesticMarket(string value) => value switch
{
"KOSPI" or "KOSPI_STOCK" or "코스피" or "코스피종목" =>
LegacyDomesticEquityMarket.Kospi,
"KOSDAQ" or "KOSDAQ_STOCK" or "코스닥" or "코스닥종목" =>
LegacyDomesticEquityMarket.Kosdaq,
"NXT_KOSPI" or "NXT코스피" or "코스피(NXT)" =>
LegacyDomesticEquityMarket.NxtKospi,
"NXT_KOSDAQ" or "NXT코스닥" or "코스닥(NXT)" =>
LegacyDomesticEquityMarket.NxtKosdaq,
_ => throw new LegacySceneDataException(
"The domestic equity market selection is invalid.")
};
private static CandleCutAlias ParseCandleAlias(string value) => value switch
{
"8035" => CandleCutAlias.Cut8035,
"8061" => CandleCutAlias.Cut8061,
"8040" => CandleCutAlias.Cut8040,
"8046" => CandleCutAlias.Cut8046,
"8051" => CandleCutAlias.Cut8051,
"8056" => CandleCutAlias.Cut8056,
_ => throw new LegacySceneDataException("The candle cut alias is invalid.")
};
private static CandleMarketTarget ParseCandleMarket(string value) => value switch
{
"KOSPI_INDEX" or "코스피지수" => CandleMarketTarget.KospiIndex,
"KOSDAQ_INDEX" or "코스닥지수" => CandleMarketTarget.KosdaqIndex,
"KOSPI200_INDEX" or "코스피200" => CandleMarketTarget.Kospi200Index,
"KRX100_INDEX" or "KRX100" => CandleMarketTarget.Krx100Index,
"FUTURES_INDEX" or "선물" => CandleMarketTarget.FuturesIndex,
"KOSPI_INDUSTRY" or "코스피업종" => CandleMarketTarget.KospiIndustry,
"KOSDAQ_INDUSTRY" or "코스닥업종" => CandleMarketTarget.KosdaqIndustry,
"FOREIGN_INDEX" or "해외지수" => CandleMarketTarget.OverseasIndex,
"FOREIGN_STOCK" or "해외종목" => CandleMarketTarget.OverseasStock,
"KOSPI_STOCK" or "코스피종목" => CandleMarketTarget.KospiStock,
"KOSDAQ_STOCK" or "코스닥종목" => CandleMarketTarget.KosdaqStock,
_ => throw new LegacySceneDataException("The candle market selection is invalid.")
};
private static CandleChartMode ParseCandleMode(string value) => value switch
{
"" or "PRICE" or "현재가" => CandleChartMode.Price,
"EXPECTED" or "예상체결" => CandleChartMode.ExpectedExecution,
"VOLUME" or "거래량" => CandleChartMode.PriceWithVolume,
"TRADING_HALT" or "거래정지" => CandleChartMode.TradingHalt,
_ => throw new LegacySceneDataException("The candle chart mode is invalid.")
};
private static bool CandleRequiresInstrument(CandleMarketTarget value) => value is
CandleMarketTarget.KospiIndustry or
CandleMarketTarget.KosdaqIndustry or
CandleMarketTarget.OverseasIndex or
CandleMarketTarget.OverseasStock or
CandleMarketTarget.KospiStock or
CandleMarketTarget.KosdaqStock;
private static (string? Symbol, string? NationCode) ParseForeignCandleIdentity(
string value,
CandleMarketTarget target)
{
var parts = value.Split('|', StringSplitOptions.None);
var expectedType = target switch
{
CandleMarketTarget.OverseasIndex => "0",
CandleMarketTarget.OverseasStock => "1",
_ => throw new LegacySceneDataException(
"The foreign candle target is invalid.")
};
if (parts.Length != 3 ||
!LegacyNamedForeignStockRestoreService.IsCanonicalBoundSymbol(parts[0]) ||
parts[1].Length != 2 ||
parts[1].Any(character => character is < 'A' or > 'Z') ||
!string.Equals(parts[2], expectedType, StringComparison.Ordinal))
{
throw new LegacySceneDataException(
"The foreign candle identity is invalid.");
}
return (parts[0], parts[1]);
}
private static string RequireLookupValue(string value, string label)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new LegacySceneDataException($"The scene {label} selection is required.");
}
return value;
}
private static bool HasClosedFlag(string value, string flag) =>
value.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
.Contains(flag, StringComparer.Ordinal);
private static PagedQuoteLoadRequest CreatePagedQuoteRequest(
LegacySceneSelection selection,
int pageIndexZeroBased)
{
var page = checked(pageIndexZeroBased + 1);
return selection.GroupCode switch
{
"PAGED_DOMESTIC_KOSPI" => new DomesticPagedQuoteLoadRequest(
PagedQuoteMarket.Kospi,
ParseDomesticPagedList(selection.Subtype),
ParseAsOfDate(selection.DataCode),
page),
"PAGED_DOMESTIC_KOSDAQ" => new DomesticPagedQuoteLoadRequest(
PagedQuoteMarket.Kosdaq,
ParseDomesticPagedList(selection.Subtype),
ParseAsOfDate(selection.DataCode),
page),
"PAGED_NXT_KOSPI" => new NxtPagedQuoteLoadRequest(
PagedQuoteMarket.Kospi,
ParseNxtPagedList(selection.Subtype),
ParseNxtSession(selection.GraphicType),
page),
"PAGED_NXT_KOSDAQ" => new NxtPagedQuoteLoadRequest(
PagedQuoteMarket.Kosdaq,
ParseNxtPagedList(selection.Subtype),
ParseNxtSession(selection.GraphicType),
page),
"PAGED_THEME" => new ThemePagedQuoteLoadRequest(
RequireLookupValue(selection.DataCode, "theme code"),
RequireLookupValue(selection.Subject, "theme"),
ParseThemeSort(selection.GraphicType),
ParseThemeValueKind(selection.Subtype),
page),
"PAGED_NXT_THEME" => new NxtThemePagedQuoteLoadRequest(
RequireLookupValue(selection.DataCode, "NXT theme code"),
RequireLookupValue(selection.Subject, "NXT theme"),
ParseThemeSort(selection.Subtype),
ParseNxtSession(selection.GraphicType),
page),
"PAGED_EXPERT" => new ExpertPagedQuoteLoadRequest(
RequireLookupValue(selection.DataCode, "expert code"),
RequireLookupValue(selection.Subject, "expert"),
page),
"PAGED_VI" => new ViPagedQuoteLoadRequest(
ParseStockNames(selection.Subject),
page),
_ => throw new LegacySceneDataException("The paged quote branch is invalid.")
};
}
private static DomesticPagedQuoteList ParseDomesticPagedList(string value) => value switch
{
"EXPECTED_MARKET_CAP" => DomesticPagedQuoteList.ExpectedMarketCapitalization,
"MARKET_CAP" => DomesticPagedQuoteList.MarketCapitalization,
"INDUSTRY" => DomesticPagedQuoteList.Industry,
"AFTER_HOURS_GAIN_RATE" => DomesticPagedQuoteList.AfterHoursGainRate,
"AFTER_HOURS_LOSS_RATE" => DomesticPagedQuoteList.AfterHoursLossRate,
"GAIN_RATE" => DomesticPagedQuoteList.GainRate,
"LOSS_RATE" => DomesticPagedQuoteList.LossRate,
"52_WEEK_HIGH" => DomesticPagedQuoteList.FiftyTwoWeekHigh,
"52_WEEK_LOW" => DomesticPagedQuoteList.FiftyTwoWeekLow,
"KOSPI200" => DomesticPagedQuoteList.Kospi200,
"ETF" => DomesticPagedQuoteList.Etf,
_ => throw new LegacySceneDataException("The domestic paged quote list is invalid.")
};
private static NxtPagedQuoteList ParseNxtPagedList(string value) => value switch
{
"MARKET_CAP" => NxtPagedQuoteList.MarketCapitalization,
"AFTER_HOURS_GAIN_RATE" => NxtPagedQuoteList.AfterHoursGainRate,
"AFTER_HOURS_LOSS_RATE" => NxtPagedQuoteList.AfterHoursLossRate,
"GAIN_RATE" => NxtPagedQuoteList.GainRate,
"LOSS_RATE" => NxtPagedQuoteList.LossRate,
"VOLUME" => NxtPagedQuoteList.Volume,
_ => throw new LegacySceneDataException("The NXT paged quote list is invalid.")
};
private static NxtMarketSession ParseNxtSession(string value) => value switch
{
"PRE_MARKET" => NxtMarketSession.PreMarket,
"AFTER_MARKET" => NxtMarketSession.AfterMarket,
_ => throw new LegacySceneDataException("The NXT market session is invalid.")
};
private static PagedQuoteThemeSort ParseThemeSort(string value) => value switch
{
"" or "INPUT_ORDER" => PagedQuoteThemeSort.InputOrder,
"GAIN_DESC" => PagedQuoteThemeSort.GainRateDescending,
"GAIN_ASC" => PagedQuoteThemeSort.GainRateAscending,
_ => throw new LegacySceneDataException("The theme sort selection is invalid.")
};
private static PagedQuoteThemeValueKind ParseThemeValueKind(string value) => value switch
{
"" or "CURRENT" => PagedQuoteThemeValueKind.Current,
"EXPECTED" => PagedQuoteThemeValueKind.ExpectedExecution,
_ => throw new LegacySceneDataException("The theme value selection is invalid.")
};
private static DateOnly ParseAsOfDate(string value) =>
DateOnly.TryParseExact(
value,
"yyyy-MM-dd",
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None,
out var date)
? date
: throw new LegacySceneDataException("The paged quote as-of date is invalid.");
private static IReadOnlyList<string> ParseStockNames(string value)
{
var names = value.Split(
',',
StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
if (names.Length is < 1 or > 240)
{
throw new LegacySceneDataException("The VI stock-name selection is invalid.");
}
return Array.AsReadOnly(names);
}
private static TEnum ParseClosedEnum<TEnum>(string value, string label)
where TEnum : struct, Enum
{
if (!Enum.TryParse<TEnum>(value, ignoreCase: false, out var parsed) ||
!Enum.IsDefined(parsed))
{
throw new LegacySceneDataException($"The {label} selection is invalid.");
}
return parsed;
}
}