feat(settings): forward-test (paper-trading) settings section

Surfaces the PaperTradingWorker's config on the Settings page — Enabled toggle,
min-score, flat-stake, and poll interval — so forward-testing can be switched on
and tuned from the UI instead of hand-editing committed appsettings.json.

Uses the established UI-mirror-options pattern (PaperTradingSettingsForm bound to
the "PaperTrading" section, same as the UI WorkerOptions mirror) and writes through
the existing ISettingsWriter to appsettings.Local.json. Notifications is deliberately
NOT surfaced: its section holds the Telegram secret and the section-replace writer
would clobber the token — that section stays Local.json-only by design.

- PaperTradingSettingsForm (no secrets) + DI binding; Settings section + en/ru resx.
This commit is contained in:
2026-05-29 02:38:20 +03:00
parent 39aef449f7
commit 76306ef59b
5 changed files with 78 additions and 0 deletions
+37
View File
@@ -6,6 +6,7 @@
@inject IOptionsMonitor<WorkerOptions> WorkerOpts
@inject IOptionsMonitor<StorageOptions> StorageOpts
@inject IOptionsMonitor<AnomalyOptions> AnomalyOpts
@inject IOptionsMonitor<PaperTradingSettingsForm> PaperTradingOpts
@inject IOptionsMonitor<Marathon.UI.Services.LocalizationOptions> LocaleOpts
@inject ISettingsWriter Writer
@inject IDialogService Dialogs
@@ -157,6 +158,33 @@
</div>
</article>
@* FORWARD-TEST (PAPER TRADING) *@
<article class="m-section m-rise m-rise-5">
<header class="m-section__head">
<h2>@L["Settings.Section.PaperTrading"]</h2>
<MudButton Variant="Variant.Text" Size="Size.Small"
OnClick="@(() => ResetSectionAsync(PaperTradingSettingsForm.SectionName))">
@L["Settings.Action.Reset"]
</MudButton>
</header>
<div class="m-section__body">
<Field Label="@L["Settings.PaperTrading.Enabled"]" Hint="@L["Settings.PaperTrading.Enabled.Hint"]">
<MudSwitch T="bool" @bind-Value="_paperTrading.Enabled" Color="Color.Primary" />
</Field>
<Field Label="@L["Settings.PaperTrading.MinScore"]" Hint="@L["Settings.PaperTrading.MinScore.Hint"]">
<MudNumericField T="decimal" @bind-Value="_paperTrading.MinScore" Min="0m" Max="1m" Step="0.05m" Variant="Variant.Outlined" />
</Field>
<Field Label="@L["Settings.PaperTrading.FlatStake"]" Hint="@L["Settings.PaperTrading.FlatStake.Hint"]">
<MudNumericField T="decimal" @bind-Value="_paperTrading.FlatStake" Min="0.01m" Step="5m" Variant="Variant.Outlined" />
</Field>
<Field Label="@L["Settings.PaperTrading.PollIntervalSeconds"]">
<MudNumericField T="int" @bind-Value="_paperTrading.PollIntervalSeconds" Min="5" Max="3600" Variant="Variant.Outlined" />
</Field>
<SectionFooter OnSave="@(() => SaveSectionAsync(PaperTradingSettingsForm.SectionName, _paperTrading))" />
</div>
</article>
@* LOCALIZATION *@
<article class="m-section m-rise m-rise-5">
<header class="m-section__head">
@@ -184,6 +212,7 @@
private WorkerOptions _workers = new();
private StorageOptions _storage = new();
private AnomalyOptions _anomaly = new();
private PaperTradingSettingsForm _paperTrading = new();
private LocalizationOptions _locale = new();
private string _userAgentsRaw = string.Empty;
@@ -218,6 +247,14 @@
DetectionIntervalSeconds = AnomalyOpts.CurrentValue.DetectionIntervalSeconds,
};
_paperTrading = new PaperTradingSettingsForm
{
Enabled = PaperTradingOpts.CurrentValue.Enabled,
MinScore = PaperTradingOpts.CurrentValue.MinScore,
FlatStake = PaperTradingOpts.CurrentValue.FlatStake,
PollIntervalSeconds = PaperTradingOpts.CurrentValue.PollIntervalSeconds,
};
_locale = new LocalizationOptions { DefaultCulture = LocaleOpts.CurrentValue.DefaultCulture };
}