676 lines
34 KiB
C#
676 lines
34 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 ||
|
|
parts[0].Length is < 1 or > 64 ||
|
|
parts[0].Any(character => char.IsControl(character) || char.IsSurrogate(character)) ||
|
|
parts[1].Length != 2 ||
|
|
parts[1].Any(character => character is < 'A' or > 'Z') ||
|
|
!string.Equals(parts[2], expectedType, StringComparison.Ordinal) ||
|
|
target == CandleMarketTarget.OverseasStock &&
|
|
parts[1] is not ("US" or "TW"))
|
|
{
|
|
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;
|
|
}
|
|
}
|