Skip to main content

arvo_api/generated/
arvo.research.v1.rs

1// This file is @generated by prost-build.
2#[derive(serde::Serialize, serde::Deserialize)]
3#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
4pub struct Strategy {
5    #[prost(string, tag = "1")]
6    pub name: ::prost::alloc::string::String,
7    #[prost(string, tag = "2")]
8    pub label: ::prost::alloc::string::String,
9    /// The resolution it runs at, e.g. "1d" or "5m".
10    #[prost(string, tag = "3")]
11    pub interval: ::prost::alloc::string::String,
12    /// What the rule claims about how prices behave.
13    #[prost(string, tag = "4")]
14    pub premise: ::prost::alloc::string::String,
15    /// True when the rule ranks instruments against each other and needs a set.
16    #[prost(bool, tag = "5")]
17    pub ranks_a_set: bool,
18    /// Backtests one study runs: what a front end's progress indicator promises.
19    #[prost(uint32, tag = "6")]
20    pub backtests: u32,
21}
22#[derive(serde::Serialize, serde::Deserialize)]
23#[derive(Clone, PartialEq, ::prost::Message)]
24pub struct Strategies {
25    #[prost(message, repeated, tag = "1")]
26    pub strategies: ::prost::alloc::vec::Vec<Strategy>,
27}
28#[derive(serde::Serialize, serde::Deserialize)]
29#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
30pub struct FindingSummary {
31    #[prost(string, tag = "1")]
32    pub id: ::prost::alloc::string::String,
33    /// What was run, e.g. "study", "walk-forward" or "panel".
34    #[prost(string, tag = "2")]
35    pub kind: ::prost::alloc::string::String,
36    #[prost(string, tag = "3")]
37    pub subject: ::prost::alloc::string::String,
38    /// "Supported", "NotSupported" or "Inconclusive".
39    #[prost(string, tag = "4")]
40    pub verdict: ::prost::alloc::string::String,
41    /// RFC 3339.
42    #[prost(string, tag = "5")]
43    pub recorded_at: ::prost::alloc::string::String,
44    /// The agent or script that ran it; empty for a person.
45    #[prost(string, tag = "6")]
46    pub author: ::prost::alloc::string::String,
47    /// Which rule or ruleset ran, by the name the picker shows. A ruleset is
48    /// the file `rulesets/<name>.json` in the project.
49    #[prost(string, tag = "7")]
50    pub strategy: ::prost::alloc::string::String,
51    /// The engine build that produced it (#189): the git commit, with `-dirty`
52    /// when the tree had uncommitted changes, or `unknown` for a build made
53    /// outside a checkout.
54    #[prost(string, tag = "8")]
55    pub code_commit: ::prost::alloc::string::String,
56    /// The ruleset document's content hash at run time, when the strategy was
57    /// a ruleset rather than a shipped rule; empty otherwise. A finding whose
58    /// hash no longer matches the file is stale: the rule it measured is not
59    /// the rule the file now holds.
60    #[prost(string, tag = "9")]
61    pub ruleset_hash: ::prost::alloc::string::String,
62}
63#[derive(serde::Serialize, serde::Deserialize)]
64#[derive(Clone, PartialEq, ::prost::Message)]
65pub struct Findings {
66    #[prost(message, repeated, tag = "1")]
67    pub findings: ::prost::alloc::vec::Vec<FindingSummary>,
68    /// Records in the evidence store that could not be read.
69    #[prost(uint32, tag = "2")]
70    pub unreadable: u32,
71}
72#[derive(serde::Serialize, serde::Deserialize)]
73#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
74pub struct FindingId {
75    #[prost(string, tag = "1")]
76    pub id: ::prost::alloc::string::String,
77}
78#[derive(serde::Serialize, serde::Deserialize)]
79#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
80pub struct FindingIds {
81    #[prost(string, repeated, tag = "1")]
82    pub ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
83}
84#[derive(serde::Serialize, serde::Deserialize)]
85#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
86pub struct Advice {
87    /// "blocking", "warning" or "note".
88    #[prost(string, tag = "1")]
89    pub severity: ::prost::alloc::string::String,
90    #[prost(string, tag = "2")]
91    pub finding: ::prost::alloc::string::String,
92    #[prost(string, tag = "3")]
93    pub action: ::prost::alloc::string::String,
94    #[prost(string, tag = "4")]
95    pub evidence: ::prost::alloc::string::String,
96}
97/// One finding: the verdict and how far it may be read, before any number.
98#[derive(serde::Serialize, serde::Deserialize)]
99#[derive(Clone, PartialEq, ::prost::Message)]
100pub struct Finding {
101    #[prost(message, optional, tag = "1")]
102    pub summary: ::core::option::Option<FindingSummary>,
103    /// A plain instruction on how far the verdict may be read. Read first.
104    #[prost(string, tag = "2")]
105    pub read_this_first: ::prost::alloc::string::String,
106    #[prost(string, repeated, tag = "3")]
107    pub reasons: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
108    #[prost(message, repeated, tag = "4")]
109    pub advice: ::prost::alloc::vec::Vec<Advice>,
110    /// The numbers, which differ by kind: the search and the out-of-sample
111    /// metrics for a study, the stitched record for a walk-forward, the pooled
112    /// outcome for a panel. JSON, so a new number is not a new message.
113    #[prost(string, tag = "5")]
114    pub detail_json: ::prost::alloc::string::String,
115    /// Files kept with the finding (#157).
116    #[prost(message, repeated, tag = "6")]
117    pub attachments: ::prost::alloc::vec::Vec<Attachment>,
118}
119/// One file kept with a finding: bytes stored once by content hash, listed
120/// on the record.
121#[derive(serde::Serialize, serde::Deserialize)]
122#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
123pub struct Attachment {
124    #[prost(string, tag = "1")]
125    pub name: ::prost::alloc::string::String,
126    #[prost(string, tag = "2")]
127    pub media_type: ::prost::alloc::string::String,
128    /// blake3 of the bytes, hex.
129    #[prost(string, tag = "3")]
130    pub hash: ::prost::alloc::string::String,
131    #[prost(uint64, tag = "4")]
132    pub bytes: u64,
133    /// RFC 3339.
134    #[prost(string, tag = "5")]
135    pub added_at: ::prost::alloc::string::String,
136}
137/// A file to keep with a finding the caller already recorded or ran.
138#[derive(serde::Serialize, serde::Deserialize)]
139#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
140pub struct AttachRequest {
141    #[prost(string, tag = "1")]
142    pub finding_id: ::prost::alloc::string::String,
143    /// As the author names it: `report.md`, `equity.png`.
144    #[prost(string, tag = "2")]
145    pub name: ::prost::alloc::string::String,
146    /// `text/markdown`, `image/png`; empty means application/octet-stream.
147    #[prost(string, tag = "3")]
148    pub media_type: ::prost::alloc::string::String,
149    #[prost(bytes = "vec", tag = "4")]
150    pub data: ::prost::alloc::vec::Vec<u8>,
151}
152#[derive(serde::Serialize, serde::Deserialize)]
153#[derive(Clone, PartialEq, ::prost::Message)]
154pub struct Attachments {
155    #[prost(message, repeated, tag = "1")]
156    pub attachments: ::prost::alloc::vec::Vec<Attachment>,
157}
158#[derive(serde::Serialize, serde::Deserialize)]
159#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
160pub struct AttachmentRef {
161    #[prost(string, tag = "1")]
162    pub finding_id: ::prost::alloc::string::String,
163    #[prost(string, tag = "2")]
164    pub hash: ::prost::alloc::string::String,
165}
166#[derive(serde::Serialize, serde::Deserialize)]
167#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
168pub struct RunRequest {
169    #[prost(string, tag = "1")]
170    pub instrument: ::prost::alloc::string::String,
171    #[prost(string, tag = "2")]
172    pub strategy: ::prost::alloc::string::String,
173    /// Who is running this, e.g. "script:momentum-scan". Required: a run is
174    /// saved as this author's and deflated against everything the author has
175    /// run, so a script iterating until something passes does not make it pass.
176    #[prost(string, tag = "3")]
177    pub author: ::prost::alloc::string::String,
178    /// Where in the author's code the call was made, as "path:line", so the
179    /// workbench can put the finding's caveats against that line. Optional.
180    #[prost(string, tag = "4")]
181    pub origin: ::prost::alloc::string::String,
182}
183#[derive(serde::Serialize, serde::Deserialize)]
184#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
185pub struct StudyRequest {
186    #[prost(string, tag = "1")]
187    pub instrument: ::prost::alloc::string::String,
188    /// A rule or ruleset name; the default rule when absent.
189    #[prost(string, optional, tag = "2")]
190    pub strategy: ::core::option::Option<::prost::alloc::string::String>,
191}
192#[derive(serde::Serialize, serde::Deserialize)]
193#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
194pub struct BookRequest {
195    #[prost(string, repeated, tag = "1")]
196    pub instruments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
197    #[prost(string, optional, tag = "2")]
198    pub strategy: ::core::option::Option<::prost::alloc::string::String>,
199    #[prost(uint32, optional, tag = "3")]
200    pub max_concurrent_positions: ::core::option::Option<u32>,
201    #[prost(uint32, optional, tag = "4")]
202    pub max_per_sector: ::core::option::Option<u32>,
203}
204#[derive(serde::Serialize, serde::Deserialize)]
205#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
206pub struct SharedExperiment {
207    /// The file's text, as the person pasted or opened it.
208    #[prost(string, tag = "1")]
209    pub text: ::prost::alloc::string::String,
210    #[prost(string, tag = "2")]
211    pub instrument: ::prost::alloc::string::String,
212}
213/// One point of an equity curve. `at` is `YYYY-MM-DDTHH:MM:SS` or a date.
214#[derive(serde::Serialize, serde::Deserialize)]
215#[derive(Clone, PartialEq, ::prost::Message)]
216pub struct Point {
217    #[prost(string, tag = "1")]
218    pub at: ::prost::alloc::string::String,
219    #[prost(double, tag = "2")]
220    pub equity: f64,
221}
222/// One trade in a ledger: a round trip, or a position still open at the end
223/// (empty `closed`, no `exit`).
224#[derive(serde::Serialize, serde::Deserialize)]
225#[derive(Clone, PartialEq, ::prost::Message)]
226pub struct LedgerTrade {
227    /// `SYMBOL.VENUE`; empty when it is the experiment's own instrument.
228    #[prost(string, tag = "1")]
229    pub instrument: ::prost::alloc::string::String,
230    #[prost(string, tag = "2")]
231    pub opened: ::prost::alloc::string::String,
232    #[prost(string, tag = "3")]
233    pub closed: ::prost::alloc::string::String,
234    /// "long" or "short"; empty means long.
235    #[prost(string, tag = "4")]
236    pub direction: ::prost::alloc::string::String,
237    #[prost(double, tag = "5")]
238    pub quantity: f64,
239    #[prost(double, tag = "6")]
240    pub entry: f64,
241    #[prost(double, optional, tag = "7")]
242    pub exit: ::core::option::Option<f64>,
243    #[prost(double, tag = "8")]
244    pub pnl: f64,
245    #[prost(double, tag = "9")]
246    pub commission: f64,
247    /// "signal", "stop", "halted", "expired" or "still_open"; empty means signal.
248    #[prost(string, tag = "10")]
249    pub exit_reason: ::prost::alloc::string::String,
250    /// What the rule saw at entry (#190): the condition that fired, the value it
251    /// was judged on, the regime it saw, and the quantity it asked the gate for.
252    /// Absent on a trade recorded before the journal or opened by no rule.
253    #[prost(string, optional, tag = "11")]
254    pub rule: ::core::option::Option<::prost::alloc::string::String>,
255    #[prost(double, optional, tag = "12")]
256    pub signal: ::core::option::Option<f64>,
257    #[prost(string, optional, tag = "13")]
258    pub regime: ::core::option::Option<::prost::alloc::string::String>,
259    #[prost(double, optional, tag = "14")]
260    pub asked: ::core::option::Option<f64>,
261}
262/// Evidence an engine Arvo did not run computed (ADR-0026). What a run
263/// produces, before anyone concludes anything from it; Arvo computes the
264/// verdict. There is no field for a verdict, a metric or a p-value.
265#[derive(serde::Serialize, serde::Deserialize)]
266#[derive(Clone, PartialEq, ::prost::Message)]
267pub struct ReportRequest {
268    /// As RunRequest: who is recording this, and where in their code.
269    #[prost(string, tag = "1")]
270    pub author: ::prost::alloc::string::String,
271    #[prost(string, tag = "2")]
272    pub origin: ::prost::alloc::string::String,
273    /// The hypothesis: an id, and the claim in prose.
274    #[prost(string, tag = "3")]
275    pub hypothesis_id: ::prost::alloc::string::String,
276    #[prost(string, tag = "4")]
277    pub claim: ::prost::alloc::string::String,
278    /// The experiment as data.
279    #[prost(string, tag = "5")]
280    pub instrument: ::prost::alloc::string::String,
281    /// ISO dates, inclusive.
282    #[prost(string, tag = "6")]
283    pub from: ::prost::alloc::string::String,
284    #[prost(string, tag = "7")]
285    pub to: ::prost::alloc::string::String,
286    /// As arvo_data spells it: "1day", "5minute".
287    #[prost(string, tag = "8")]
288    pub interval: ::prost::alloc::string::String,
289    /// The engine's own dataset: an id, and a content hash of what it read.
290    #[prost(string, tag = "9")]
291    pub dataset_id: ::prost::alloc::string::String,
292    #[prost(string, tag = "10")]
293    pub dataset_version: ::prost::alloc::string::String,
294    /// "split" (the default) or "total_return".
295    #[prost(string, tag = "11")]
296    pub adjustment: ::prost::alloc::string::String,
297    /// The engine's own strategy name, and its parameters.
298    #[prost(string, tag = "12")]
299    pub strategy: ::prost::alloc::string::String,
300    #[prost(map = "string, double", tag = "13")]
301    pub params: ::std::collections::HashMap<::prost::alloc::string::String, f64>,
302    /// The cost model the run assumed.
303    #[prost(double, tag = "14")]
304    pub commission_bps: f64,
305    #[prost(double, tag = "15")]
306    pub slippage_bps: f64,
307    #[prost(double, tag = "16")]
308    pub per_fill: f64,
309    /// The risk model, as far as the run assumed one.
310    #[prost(double, optional, tag = "17")]
311    pub stop_atr_multiple: ::core::option::Option<f64>,
312    /// Zero means the curve's first point.
313    #[prost(double, tag = "18")]
314    pub starting_cash: f64,
315    /// What computed this, by name and version.
316    #[prost(string, tag = "19")]
317    pub engine: ::prost::alloc::string::String,
318    /// The evidence.
319    #[prost(message, repeated, tag = "20")]
320    pub strategy_curve: ::prost::alloc::vec::Vec<Point>,
321    #[prost(message, repeated, tag = "21")]
322    pub ledger: ::prost::alloc::vec::Vec<LedgerTrade>,
323    /// Buy-and-hold over the same window and capital. Without it the finding
324    /// is Inconclusive: a return with nothing to beat is not a result.
325    #[prost(message, repeated, tag = "22")]
326    pub benchmark_curve: ::prost::alloc::vec::Vec<Point>,
327    /// How many trials the author's whole search has run (ADR-0014).
328    #[prost(uint32, optional, tag = "23")]
329    pub trials: ::core::option::Option<u32>,
330}
331#[derive(serde::Serialize, serde::Deserialize)]
332#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
333pub struct ReportFigure {
334    #[prost(string, tag = "1")]
335    pub finding_id: ::prost::alloc::string::String,
336    /// The equity chart the front end captured, base64 PNG. Absent is a report
337    /// with no figure, not an error: only a window can draw one.
338    #[prost(string, optional, tag = "2")]
339    pub figure: ::core::option::Option<::prost::alloc::string::String>,
340}
341#[derive(serde::Serialize, serde::Deserialize)]
342#[derive(Clone, PartialEq, ::prost::Message)]
343pub struct TradeExport {
344    #[prost(string, tag = "1")]
345    pub name: ::prost::alloc::string::String,
346    /// The rows as the front end has them, in the order it is showing them. The
347    /// engine does not derive these from the finding, because the sort order is
348    /// the view's, and an export that silently differed from the table above it
349    /// would be worse than none.
350    #[prost(message, repeated, tag = "2")]
351    pub rows: ::prost::alloc::vec::Vec<TradeRow>,
352}
353/// One round trip, as a trades table shows it and a CSV export writes it.
354#[derive(serde::Serialize, serde::Deserialize)]
355#[derive(Clone, PartialEq, ::prost::Message)]
356pub struct TradeRow {
357    /// Which instrument the round trip was in. Empty on an ordinary study,
358    /// whose subject line already says; on a book it is the difference between
359    /// a usable export and trades from three instruments with no way to tell
360    /// them apart.
361    #[prost(string, tag = "1")]
362    pub instrument: ::prost::alloc::string::String,
363    #[prost(string, tag = "2")]
364    pub opened: ::prost::alloc::string::String,
365    #[prost(string, tag = "3")]
366    pub closed: ::prost::alloc::string::String,
367    #[prost(string, tag = "4")]
368    pub direction: ::prost::alloc::string::String,
369    #[prost(double, tag = "5")]
370    pub quantity: f64,
371    #[prost(double, tag = "6")]
372    pub entry: f64,
373    #[prost(double, optional, tag = "7")]
374    pub exit: ::core::option::Option<f64>,
375    #[prost(double, tag = "8")]
376    pub pnl: f64,
377    #[prost(double, tag = "9")]
378    pub commission: f64,
379    #[prost(double, optional, tag = "10")]
380    pub held_days: ::core::option::Option<f64>,
381    #[prost(string, tag = "11")]
382    pub exit_reason: ::prost::alloc::string::String,
383    /// What the rule saw at entry (#190): the condition that fired, the value it
384    /// was judged on, the regime it saw, and the quantity it asked the gate for.
385    /// Absent on a trade recorded before the journal or opened by no rule.
386    #[prost(string, optional, tag = "12")]
387    pub rule: ::core::option::Option<::prost::alloc::string::String>,
388    #[prost(double, optional, tag = "13")]
389    pub signal: ::core::option::Option<f64>,
390    #[prost(string, optional, tag = "14")]
391    pub regime: ::core::option::Option<::prost::alloc::string::String>,
392    #[prost(double, optional, tag = "15")]
393    pub asked: ::core::option::Option<f64>,
394}
395/// A ruleset parameter: one value fixes it, several search it.
396#[derive(serde::Serialize, serde::Deserialize)]
397#[derive(Clone, PartialEq, ::prost::Message)]
398pub struct Param {
399    #[prost(string, tag = "1")]
400    pub name: ::prost::alloc::string::String,
401    #[prost(double, repeated, tag = "2")]
402    pub values: ::prost::alloc::vec::Vec<f64>,
403}
404#[derive(serde::Serialize, serde::Deserialize)]
405#[derive(Clone, PartialEq, ::prost::Message)]
406pub struct Fixed {
407    #[prost(string, tag = "1")]
408    pub name: ::prost::alloc::string::String,
409    #[prost(double, tag = "2")]
410    pub value: f64,
411}
412/// One file under `<project>`/rulesets/, as the picker sees it.
413#[derive(serde::Serialize, serde::Deserialize)]
414#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
415pub struct Ruleset {
416    #[prost(string, tag = "1")]
417    pub path: ::prost::alloc::string::String,
418    #[prost(string, tag = "2")]
419    pub name: ::prost::alloc::string::String,
420    #[prost(string, tag = "3")]
421    pub label: ::prost::alloc::string::String,
422    #[prost(string, tag = "4")]
423    pub rule: ::prost::alloc::string::String,
424    #[prost(string, tag = "5")]
425    pub interval: ::prost::alloc::string::String,
426    #[prost(uint32, tag = "6")]
427    pub searches: u32,
428    /// Why it cannot be run, when it cannot.
429    #[prost(string, optional, tag = "7")]
430    pub problem: ::core::option::Option<::prost::alloc::string::String>,
431}
432#[derive(serde::Serialize, serde::Deserialize)]
433#[derive(Clone, PartialEq, ::prost::Message)]
434pub struct Rulesets {
435    #[prost(message, repeated, tag = "1")]
436    pub rulesets: ::prost::alloc::vec::Vec<Ruleset>,
437}
438#[derive(serde::Serialize, serde::Deserialize)]
439#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
440pub struct RulesetPath {
441    #[prost(string, tag = "1")]
442    pub path: ::prost::alloc::string::String,
443}
444/// A grid ruleset's parts, for a form.
445#[derive(serde::Serialize, serde::Deserialize)]
446#[derive(Clone, PartialEq, ::prost::Message)]
447pub struct RulesetForm {
448    #[prost(string, tag = "1")]
449    pub name: ::prost::alloc::string::String,
450    #[prost(string, tag = "2")]
451    pub rule: ::prost::alloc::string::String,
452    #[prost(string, tag = "3")]
453    pub label: ::prost::alloc::string::String,
454    #[prost(string, tag = "4")]
455    pub premise: ::prost::alloc::string::String,
456    #[prost(message, repeated, tag = "5")]
457    pub params: ::prost::alloc::vec::Vec<Param>,
458}
459/// A rule this build implements, with the search it ships.
460#[derive(serde::Serialize, serde::Deserialize)]
461#[derive(Clone, PartialEq, ::prost::Message)]
462pub struct Rule {
463    #[prost(string, tag = "1")]
464    pub name: ::prost::alloc::string::String,
465    #[prost(string, tag = "2")]
466    pub label: ::prost::alloc::string::String,
467    #[prost(string, tag = "3")]
468    pub premise: ::prost::alloc::string::String,
469    #[prost(string, tag = "4")]
470    pub interval: ::prost::alloc::string::String,
471    #[prost(message, repeated, tag = "5")]
472    pub fixed: ::prost::alloc::vec::Vec<Fixed>,
473    #[prost(message, repeated, tag = "6")]
474    pub axes: ::prost::alloc::vec::Vec<Param>,
475    #[prost(bool, tag = "7")]
476    pub ranks_a_set: bool,
477    #[prost(bool, tag = "8")]
478    pub trades_options: bool,
479}
480#[derive(serde::Serialize, serde::Deserialize)]
481#[derive(Clone, PartialEq, ::prost::Message)]
482pub struct Rules {
483    #[prost(message, repeated, tag = "1")]
484    pub rules: ::prost::alloc::vec::Vec<Rule>,
485}
486/// The project's risk file (.arvo/risk.json), as read for the next study.
487#[derive(serde::Serialize, serde::Deserialize)]
488#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
489pub struct RiskModel {
490    #[prost(string, tag = "1")]
491    pub path: ::prost::alloc::string::String,
492    #[prost(bool, tag = "2")]
493    pub exists: bool,
494    /// The model in force, as JSON: the file's when it reads, the shipped one
495    /// when it does not.
496    #[prost(string, tag = "3")]
497    pub model_json: ::prost::alloc::string::String,
498    /// Why the file was refused, when it was.
499    #[prost(string, optional, tag = "4")]
500    pub error: ::core::option::Option<::prost::alloc::string::String>,
501}
502/// The dividend bias, measured.
503///
504/// On split-adjusted prices no dividend is paid to anything in a backtest.
505/// Both sides forgo them — but the benchmark held through every ex-date and a
506/// rule in the market some of the time did not, so the margin between them is
507/// overstated in the strategy's favour, and \[`Self::corrected_excess`\] takes it
508/// off. On total-return prices the distributions are already in the returns and
509/// the same measurement describes what the margin is made of instead.
510/// A study's result after tax. See `arvo_research::tax` for what is modelled.
511#[derive(serde::Serialize, serde::Deserialize)]
512#[derive(Clone, Copy, PartialEq, ::prost::Message)]
513pub struct AfterTaxView {
514    #[prost(double, tag = "1")]
515    pub strategy_return: f64,
516    #[prost(double, tag = "2")]
517    pub benchmark_return: f64,
518    /// `strategy_return - benchmark_return`: the margin a taxable account
519    /// would actually have kept.
520    #[prost(double, tag = "3")]
521    pub excess: f64,
522    #[prost(double, tag = "4")]
523    pub strategy_tax: f64,
524    #[prost(double, tag = "5")]
525    pub benchmark_tax: f64,
526    #[prost(double, tag = "6")]
527    pub short_term_rate: f64,
528    #[prost(double, tag = "7")]
529    pub long_term_rate: f64,
530}
531/// What one account holding every member of a panel would have done.
532///
533/// The panel's pooled figures average its members. This combines them, which
534/// is a different number in the place that matters: falls that did not
535/// coincide hurt a book less than they hurt its average member.
536#[derive(serde::Serialize, serde::Deserialize)]
537#[derive(Clone, Copy, PartialEq, ::prost::Message)]
538pub struct BookView {
539    #[prost(message, optional, tag = "1")]
540    pub metrics: ::core::option::Option<MetricsView>,
541    /// Mean member drawdown less the book's. Positive is diversification;
542    /// zero or below means the members fell together and the panel was one bet.
543    #[prost(double, tag = "2")]
544    pub diversification: f64,
545}
546/// How much of a panel's apparent breadth is real.
547///
548/// The pooled statistics read as evidence in proportion to the instrument
549/// count — three that agree feel like three times the confidence of one. They
550/// are not, if the three moved together.
551#[derive(serde::Serialize, serde::Deserialize)]
552#[derive(Clone, PartialEq, ::prost::Message)]
553pub struct BreadthView {
554    #[prost(string, repeated, tag = "1")]
555    pub instruments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
556    /// Square and symmetric. `None` where a pair had too little overlap to
557    /// say — which is different from a correlation of zero.
558    #[prost(message, repeated, tag = "2")]
559    pub correlations: ::prost::alloc::vec::Vec<CorrelationRow>,
560    #[prost(double, optional, tag = "3")]
561    pub mean_correlation: ::core::option::Option<f64>,
562    /// How many independent instruments the panel behaves like.
563    #[prost(double, optional, tag = "4")]
564    pub effective: ::core::option::Option<f64>,
565    /// How much the pooled average's certainty is overstated by counting
566    /// instruments instead of independent ones.
567    #[prost(double, optional, tag = "5")]
568    pub overstatement: ::core::option::Option<f64>,
569}
570/// One finding in a side-by-side comparison.
571#[derive(serde::Serialize, serde::Deserialize)]
572#[derive(Clone, PartialEq, ::prost::Message)]
573pub struct ComparisonRowView {
574    #[prost(string, tag = "1")]
575    pub id: ::prost::alloc::string::String,
576    #[prost(string, tag = "2")]
577    pub subject: ::prost::alloc::string::String,
578    #[prost(string, tag = "3")]
579    pub kind: ::prost::alloc::string::String,
580    #[prost(string, tag = "4")]
581    pub strategy_name: ::prost::alloc::string::String,
582    #[prost(string, tag = "5")]
583    pub verdict: ::prost::alloc::string::String,
584    #[prost(string, tag = "6")]
585    pub recorded_at: ::prost::alloc::string::String,
586    #[prost(double, tag = "7")]
587    pub total_return: f64,
588    #[prost(double, tag = "8")]
589    pub excess_return: f64,
590    #[prost(double, optional, tag = "9")]
591    pub sharpe: ::core::option::Option<f64>,
592    #[prost(double, tag = "10")]
593    pub max_drawdown: f64,
594    #[prost(uint32, tag = "11")]
595    pub trades: u32,
596    #[prost(double, optional, tag = "12")]
597    pub win_rate: ::core::option::Option<f64>,
598    #[prost(double, optional, tag = "13")]
599    pub profit_factor: ::core::option::Option<f64>,
600    /// Whether the data behind it has changed since it was recorded.
601    #[prost(bool, optional, tag = "14")]
602    pub stale: ::core::option::Option<bool>,
603}
604/// Several findings, read against each other.
605#[derive(serde::Serialize, serde::Deserialize)]
606#[derive(Clone, PartialEq, ::prost::Message)]
607pub struct ComparisonView {
608    #[prost(message, repeated, tag = "1")]
609    pub rows: ::prost::alloc::vec::Vec<ComparisonRowView>,
610    #[prost(message, repeated, tag = "2")]
611    pub curves: ::prost::alloc::vec::Vec<NamedCurveView>,
612    /// The best out-of-sample Sharpe among them, and the score the best of a
613    /// comparison this size would be expected to reach with no skill at all.
614    ///
615    /// The point of the whole screen. Comparing six strategies and keeping the
616    /// best one is a search of size six, and until now nothing counted it:
617    /// each finding deflated the grid *inside* it and none of them knew about
618    /// the other five.
619    #[prost(double, optional, tag = "3")]
620    pub best_sharpe: ::core::option::Option<f64>,
621    #[prost(double, optional, tag = "4")]
622    pub expected_best_under_null: ::core::option::Option<f64>,
623    #[prost(bool, tag = "5")]
624    pub survived_deflation: bool,
625    /// What is worth saying about the comparison itself.
626    #[prost(string, repeated, tag = "6")]
627    pub notes: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
628}
629/// One row of a correlation matrix. A cell with no value is a pair with too
630/// little overlap to say anything about, which is not the same as zero.
631#[derive(serde::Serialize, serde::Deserialize)]
632#[derive(Clone, PartialEq, ::prost::Message)]
633pub struct CorrelationRow {
634    #[prost(message, repeated, tag = "1")]
635    pub cells: ::prost::alloc::vec::Vec<OptionalDouble>,
636}
637/// One point on a curve, in the shape a chart library wants: an ISO date and
638/// a value.
639#[derive(serde::Serialize, serde::Deserialize)]
640#[derive(Clone, Copy, PartialEq, ::prost::Message)]
641pub struct CurvePoint {
642    /// Seconds since the epoch, not a date string.
643    ///
644    /// A date was enough while every curve was daily and is not once two
645    /// points can share a day: the chart keys points by time, so an intraday
646    /// series collapsed to dates loses every point but the last of each day.
647    /// The previous version did exactly that, deliberately and with a comment
648    /// saying so — this is what removing the limitation instead looks like.
649    #[prost(int64, tag = "1")]
650    pub time: i64,
651    #[prost(double, tag = "2")]
652    pub value: f64,
653}
654/// Where a replay first stopped matching the record.
655#[derive(serde::Serialize, serde::Deserialize)]
656#[derive(Clone, PartialEq, ::prost::Message)]
657pub struct DivergenceView {
658    #[prost(string, tag = "1")]
659    pub what: ::prost::alloc::string::String,
660    #[prost(uint32, optional, tag = "2")]
661    pub at: ::core::option::Option<u32>,
662    #[prost(string, optional, tag = "3")]
663    pub when: ::core::option::Option<::prost::alloc::string::String>,
664    #[prost(double, tag = "4")]
665    pub recorded: f64,
666    #[prost(double, tag = "5")]
667    pub replayed: f64,
668    /// Gap relative to the recorded value, so a report can separate a
669    /// rounding difference from a different answer.
670    #[prost(double, tag = "6")]
671    pub relative: f64,
672}
673#[derive(serde::Serialize, serde::Deserialize)]
674#[derive(Clone, Copy, PartialEq, ::prost::Message)]
675pub struct DividendGapView {
676    /// Distributions that went ex inside the window.
677    ///
678    /// Zero is a real answer and a good one: these instruments pay nothing, so
679    /// the margin needs no correction.
680    #[prost(uint32, tag = "1")]
681    pub events: u32,
682    #[prost(double, tag = "2")]
683    pub strategy_income: f64,
684    #[prost(double, tag = "3")]
685    pub benchmark_income: f64,
686    /// The gap as a fraction of starting capital, in the same units as the
687    /// excess return it comes off.
688    #[prost(double, tag = "4")]
689    pub overstatement: f64,
690    /// The excess return with the gap taken out, where taking it out means
691    /// anything.
692    ///
693    /// `None` on a total-return dataset: the distributions are already in the
694    /// excess return there, so subtracting the gap again would report a margin
695    /// smaller than the account earned. The rest of this view still holds —
696    /// only the subtraction stops being valid.
697    #[prost(double, optional, tag = "5")]
698    pub corrected_excess: ::core::option::Option<f64>,
699    /// False when some instrument the run held has no distribution series, in
700    /// which case the figure is a floor rather than the answer.
701    #[prost(bool, tag = "6")]
702    pub complete: bool,
703    #[prost(uint32, tag = "7")]
704    pub covered: u32,
705    #[prost(uint32, tag = "8")]
706    pub instruments: u32,
707}
708/// One fold of a walk-forward, flattened for display.
709#[derive(serde::Serialize, serde::Deserialize)]
710#[derive(Clone, PartialEq, ::prost::Message)]
711pub struct FoldView {
712    #[prost(string, tag = "1")]
713    pub chose_on: ::prost::alloc::string::String,
714    #[prost(string, tag = "2")]
715    pub judged_on: ::prost::alloc::string::String,
716    #[prost(message, repeated, tag = "3")]
717    pub params: ::prost::alloc::vec::Vec<NamedNumber>,
718    #[prost(double, tag = "4")]
719    pub strategy_return: f64,
720    #[prost(double, tag = "5")]
721    pub benchmark_return: f64,
722    #[prost(uint32, tag = "6")]
723    pub trades: u32,
724    /// Whether this fold's winner beat what a no-skill search of that size
725    /// would produce. Per fold, because a procedure that selects noise in most
726    /// periods has not been shown to select.
727    #[prost(bool, tag = "7")]
728    pub survived_deflation: bool,
729}
730#[derive(serde::Serialize, serde::Deserialize)]
731#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
732pub struct HistoryEntryView {
733    #[prost(string, tag = "1")]
734    pub id: ::prost::alloc::string::String,
735    #[prost(string, tag = "2")]
736    pub kind: ::prost::alloc::string::String,
737    #[prost(string, tag = "3")]
738    pub subject: ::prost::alloc::string::String,
739    #[prost(string, tag = "4")]
740    pub verdict: ::prost::alloc::string::String,
741    #[prost(string, tag = "5")]
742    pub recorded_at: ::prost::alloc::string::String,
743    /// True when the data this was produced from no longer matches disk, or
744    /// the ruleset it ran has changed since. `None` when the data it
745    /// referenced can no longer be found at all.
746    #[prost(bool, optional, tag = "6")]
747    pub stale: ::core::option::Option<bool>,
748    /// The agent that ran it, or `None` for a person. `default` for a view
749    /// built before agents could run research.
750    #[prost(string, optional, tag = "7")]
751    pub agent: ::core::option::Option<::prost::alloc::string::String>,
752    /// Where the agent's code asked for it, as `path:line`, when it said.
753    #[prost(string, optional, tag = "8")]
754    pub origin: ::core::option::Option<::prost::alloc::string::String>,
755    /// The size of the search an agent's finding was held to.
756    #[prost(uint32, optional, tag = "9")]
757    pub trials: ::core::option::Option<u32>,
758    /// How many files are kept with it (#157).
759    #[prost(uint32, tag = "10")]
760    pub attachments: u32,
761    /// What produced it (#189): the engine build, and for a ruleset the
762    /// document's hash at run time. Two findings from different versions of
763    /// one ruleset are told apart by this.
764    #[prost(string, tag = "11")]
765    pub code_commit: ::prost::alloc::string::String,
766    #[prost(string, tag = "12")]
767    pub ruleset_hash: ::prost::alloc::string::String,
768    /// Why `stale` is set: `data`, `ruleset`, or `data and ruleset`. Empty
769    /// when fresh, and when the data is gone altogether.
770    #[prost(string, tag = "13")]
771    pub stale_reason: ::prost::alloc::string::String,
772}
773/// What the workbench shows before anything has been run.
774/// Everything in research memory, and what could not be read of it.
775#[derive(serde::Serialize, serde::Deserialize)]
776#[derive(Clone, PartialEq, ::prost::Message)]
777pub struct HistoryView {
778    #[prost(message, repeated, tag = "1")]
779    pub entries: ::prost::alloc::vec::Vec<HistoryEntryView>,
780    #[prost(message, repeated, tag = "2")]
781    pub unreadable: ::prost::alloc::vec::Vec<UnreadableView>,
782}
783/// What one member of a book actually contributed.
784///
785/// A book reports one return for several instruments, and the single most
786/// useful question about it is which of them produced that return. The panel
787/// answers the same question by running members separately; a book cannot,
788/// because the members interfere — so it has to be read out of the ledger.
789#[derive(serde::Serialize, serde::Deserialize)]
790#[derive(Clone, PartialEq, ::prost::Message)]
791pub struct MemberView {
792    #[prost(string, tag = "1")]
793    pub instrument: ::prost::alloc::string::String,
794    #[prost(uint32, tag = "2")]
795    pub trades: u32,
796    /// Realised profit, net of what the venue charged.
797    #[prost(double, tag = "3")]
798    pub pnl: f64,
799    /// This member's share of the book's total realised profit.
800    ///
801    /// `None` when the book realised nothing at all, rather than a share of
802    /// zero — which would read as a measurement.
803    #[prost(double, optional, tag = "4")]
804    pub share: ::core::option::Option<f64>,
805    /// True when the member never opened a position.
806    ///
807    /// The failure mode a shared account introduces: an instrument that was
808    /// asked for, funded by nothing, and silently absent from every number.
809    #[prost(bool, tag = "5")]
810    pub silent: bool,
811}
812#[derive(serde::Serialize, serde::Deserialize)]
813#[derive(Clone, Copy, PartialEq, ::prost::Message)]
814pub struct MetricsView {
815    #[prost(double, tag = "1")]
816    pub total_return: f64,
817    #[prost(double, tag = "2")]
818    pub cagr: f64,
819    #[prost(double, tag = "3")]
820    pub max_drawdown: f64,
821    #[prost(double, tag = "4")]
822    pub volatility: f64,
823    #[prost(double, optional, tag = "5")]
824    pub sharpe: ::core::option::Option<f64>,
825    #[prost(double, optional, tag = "6")]
826    pub sortino: ::core::option::Option<f64>,
827    #[prost(double, optional, tag = "7")]
828    pub calmar: ::core::option::Option<f64>,
829    /// Probability the true Sharpe is above zero, given the sample.
830    ///
831    /// The second number a Sharpe needs. 1.2 from forty returns and 1.2 from
832    /// four thousand print identically and are not the same finding.
833    #[prost(double, optional, tag = "8")]
834    pub psr: ::core::option::Option<f64>,
835    #[prost(uint32, tag = "9")]
836    pub trades: u32,
837}
838/// One month of the strategy's return, for the heatmap.
839#[derive(serde::Serialize, serde::Deserialize)]
840#[derive(Clone, Copy, PartialEq, ::prost::Message)]
841pub struct MonthlyReturnView {
842    #[prost(int32, tag = "1")]
843    pub year: i32,
844    #[prost(uint32, tag = "2")]
845    pub month: u32,
846    #[prost(double, tag = "3")]
847    pub value: f64,
848}
849/// A named equity curve, for drawing several on one chart.
850#[derive(serde::Serialize, serde::Deserialize)]
851#[derive(Clone, PartialEq, ::prost::Message)]
852pub struct NamedCurveView {
853    #[prost(string, tag = "1")]
854    pub name: ::prost::alloc::string::String,
855    #[prost(message, repeated, tag = "2")]
856    pub points: ::prost::alloc::vec::Vec<CurvePoint>,
857}
858/// What a front end renders.
859///
860/// These are the shapes the engine answers with. They are here rather than
861/// in a Rust crate both sides link so that the contract has no language in
862/// it: a front end in any language generates these and reads a finding
863/// natively (#127).
864///
865/// Generated once from the desktop's Rust, then maintained here. Field numbers are
866/// the contract; add, never renumber.
867/// A parameter and its value, in the order a run chose them. Ordered, so a
868/// repeated field rather than a map.
869#[derive(serde::Serialize, serde::Deserialize)]
870#[derive(Clone, PartialEq, ::prost::Message)]
871pub struct NamedNumber {
872    #[prost(string, tag = "1")]
873    pub name: ::prost::alloc::string::String,
874    #[prost(double, tag = "2")]
875    pub value: f64,
876}
877#[derive(serde::Serialize, serde::Deserialize)]
878#[derive(Clone, Copy, PartialEq, ::prost::Message)]
879pub struct OptionalDouble {
880    #[prost(double, optional, tag = "1")]
881    pub value: ::core::option::Option<f64>,
882}
883/// One instrument's out-of-sample outcome under the panel's configuration.
884#[derive(serde::Serialize, serde::Deserialize)]
885#[derive(Clone, PartialEq, ::prost::Message)]
886pub struct OutcomeView {
887    #[prost(string, tag = "1")]
888    pub instrument: ::prost::alloc::string::String,
889    #[prost(double, tag = "2")]
890    pub strategy_return: f64,
891    #[prost(double, tag = "3")]
892    pub benchmark_return: f64,
893    #[prost(double, tag = "4")]
894    pub excess_return: f64,
895    #[prost(double, tag = "5")]
896    pub max_drawdown: f64,
897    #[prost(uint32, tag = "6")]
898    pub trades: u32,
899}
900/// A panel study, flattened for display.
901#[derive(serde::Serialize, serde::Deserialize)]
902#[derive(Clone, PartialEq, ::prost::Message)]
903pub struct PanelView {
904    /// The stored finding this view came from, so the window can ask for
905    /// something to be done to the record it is showing — a report composed
906    /// from it, a file kept with it. Empty for a view that was never
907    /// recorded. `default` because every view built before this had none.
908    #[prost(string, tag = "1")]
909    pub id: ::prost::alloc::string::String,
910    #[prost(string, tag = "2")]
911    pub verdict: ::prost::alloc::string::String,
912    #[prost(string, repeated, tag = "3")]
913    pub reasons: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
914    #[prost(message, repeated, tag = "4")]
915    pub recommendations: ::prost::alloc::vec::Vec<RecommendationView>,
916    #[prost(uint32, tag = "5")]
917    pub instruments: u32,
918    #[prost(uint32, tag = "6")]
919    pub total_trades: u32,
920    #[prost(double, tag = "7")]
921    pub mean_excess_return: f64,
922    #[prost(uint32, tag = "8")]
923    pub beat_benchmark: u32,
924    #[prost(double, tag = "9")]
925    pub mean_max_drawdown: f64,
926    #[prost(message, optional, tag = "10")]
927    pub breadth: ::core::option::Option<BreadthView>,
928    #[prost(message, optional, tag = "11")]
929    pub book: ::core::option::Option<BookView>,
930    #[prost(double, tag = "12")]
931    pub worst_max_drawdown: f64,
932    #[prost(uint32, tag = "13")]
933    pub trials: u32,
934    #[prost(double, tag = "14")]
935    pub best_sharpe: f64,
936    #[prost(double, optional, tag = "15")]
937    pub expected_best_under_null: ::core::option::Option<f64>,
938    #[prost(bool, tag = "16")]
939    pub survived_deflation: bool,
940    #[prost(string, tag = "17")]
941    pub in_sample: ::prost::alloc::string::String,
942    #[prost(string, tag = "18")]
943    pub out_of_sample: ::prost::alloc::string::String,
944    #[prost(message, repeated, tag = "19")]
945    pub selected_params: ::prost::alloc::vec::Vec<NamedNumber>,
946    #[prost(message, repeated, tag = "20")]
947    pub per_instrument: ::prost::alloc::vec::Vec<OutcomeView>,
948    #[prost(string, repeated, tag = "21")]
949    pub failures: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
950    #[prost(string, tag = "22")]
951    pub dataset_version: ::prost::alloc::string::String,
952    #[prost(string, tag = "23")]
953    pub strategy_name: ::prost::alloc::string::String,
954    #[prost(double, tag = "24")]
955    pub starting_cash: f64,
956    #[prost(double, tag = "25")]
957    pub commission_bps: f64,
958    #[prost(double, tag = "26")]
959    pub slippage_bps: f64,
960    #[prost(string, tag = "27")]
961    pub engine: ::prost::alloc::string::String,
962}
963/// One thing to do about a finding, flattened for display.
964#[derive(serde::Serialize, serde::Deserialize)]
965#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
966pub struct RecommendationView {
967    #[prost(string, tag = "1")]
968    pub severity: ::prost::alloc::string::String,
969    #[prost(string, tag = "2")]
970    pub finding: ::prost::alloc::string::String,
971    #[prost(string, tag = "3")]
972    pub action: ::prost::alloc::string::String,
973    #[prost(string, tag = "4")]
974    pub evidence: ::prost::alloc::string::String,
975}
976/// A stored finding, reopened.
977#[derive(serde::Serialize, serde::Deserialize)]
978#[derive(Clone, PartialEq, ::prost::Message)]
979pub struct RecordView {
980    #[prost(oneof = "record_view::Of", tags = "1, 2, 3, 4")]
981    pub of: ::core::option::Option<record_view::Of>,
982}
983/// Nested message and enum types in `RecordView`.
984pub mod record_view {
985    #[derive(serde::Serialize, serde::Deserialize)]
986    #[derive(Clone, PartialEq, ::prost::Oneof)]
987    pub enum Of {
988        #[prost(message, tag = "1")]
989        Study(super::StudyView),
990        #[prost(message, tag = "2")]
991        Panel(super::PanelView),
992        #[prost(message, tag = "3")]
993        Walkforward(super::WalkForwardView),
994        #[prost(message, tag = "4")]
995        Reported(super::ReportedView),
996    }
997}
998/// What came back from re-running a stored finding.
999///
1000/// A finding whose numbers cannot be regenerated is not evidence, so this is
1001/// the check the whole evidence store rests on.
1002#[derive(serde::Serialize, serde::Deserialize)]
1003#[derive(Clone, PartialEq, ::prost::Message)]
1004pub struct ReplayView {
1005    /// `reproduced`, `data-changed`, `engine-changed`, `diverged`,
1006    /// `not-replayable` or `failed`. A slug so the window can style it
1007    /// without parsing prose.
1008    #[prost(string, tag = "1")]
1009    pub outcome: ::prost::alloc::string::String,
1010    /// Whether the finding still stands. Only a clean reproduction counts:
1011    /// changed data or a changed engine leaves the claim untested, which is
1012    /// not the same as confirmed.
1013    #[prost(bool, tag = "2")]
1014    pub holds: bool,
1015    #[prost(string, tag = "3")]
1016    pub detail: ::prost::alloc::string::String,
1017    #[prost(message, optional, tag = "4")]
1018    pub divergence: ::core::option::Option<DivergenceView>,
1019}
1020/// A finding an engine Arvo did not run computed, judged by Arvo
1021/// (ADR-0026). Shown as what it is: the numbers and the verdict, and where
1022/// the data lives.
1023#[derive(serde::Serialize, serde::Deserialize)]
1024#[derive(Clone, PartialEq, ::prost::Message)]
1025pub struct ReportedView {
1026    #[prost(string, tag = "1")]
1027    pub id: ::prost::alloc::string::String,
1028    #[prost(string, tag = "2")]
1029    pub hypothesis: ::prost::alloc::string::String,
1030    #[prost(string, tag = "3")]
1031    pub claim: ::prost::alloc::string::String,
1032    #[prost(string, tag = "4")]
1033    pub instrument: ::prost::alloc::string::String,
1034    #[prost(string, tag = "5")]
1035    pub engine: ::prost::alloc::string::String,
1036    #[prost(string, tag = "6")]
1037    pub from: ::prost::alloc::string::String,
1038    #[prost(string, tag = "7")]
1039    pub to: ::prost::alloc::string::String,
1040    #[prost(string, tag = "8")]
1041    pub interval: ::prost::alloc::string::String,
1042    /// `id@version`: the engine's own dataset, not in Arvo's library.
1043    #[prost(string, tag = "9")]
1044    pub dataset: ::prost::alloc::string::String,
1045    #[prost(string, tag = "10")]
1046    pub strategy: ::prost::alloc::string::String,
1047    #[prost(string, tag = "11")]
1048    pub verdict: ::prost::alloc::string::String,
1049    #[prost(string, repeated, tag = "12")]
1050    pub reasons: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
1051    #[prost(uint32, tag = "13")]
1052    pub trades: u32,
1053    #[prost(uint32, optional, tag = "14")]
1054    pub trials: ::core::option::Option<u32>,
1055    /// Absent when there was no benchmark to judge against.
1056    #[prost(double, optional, tag = "15")]
1057    pub total_return: ::core::option::Option<f64>,
1058    #[prost(double, optional, tag = "16")]
1059    pub excess_return: ::core::option::Option<f64>,
1060    #[prost(double, optional, tag = "17")]
1061    pub sharpe: ::core::option::Option<f64>,
1062    #[prost(double, optional, tag = "18")]
1063    pub max_drawdown: ::core::option::Option<f64>,
1064    #[prost(string, tag = "19")]
1065    pub recorded_at: ::prost::alloc::string::String,
1066    #[prost(string, tag = "20")]
1067    pub author: ::prost::alloc::string::String,
1068    /// The files kept with it: a report, a figure, the trades (#157).
1069    #[prost(message, repeated, tag = "21")]
1070    pub attachments: ::prost::alloc::vec::Vec<Attachment>,
1071}
1072/// How much one parameter moved across the folds.
1073#[derive(serde::Serialize, serde::Deserialize)]
1074#[derive(Clone, PartialEq, ::prost::Message)]
1075pub struct StabilityView {
1076    #[prost(string, tag = "1")]
1077    pub axis: ::prost::alloc::string::String,
1078    #[prost(uint32, tag = "2")]
1079    pub distinct: u32,
1080    #[prost(double, tag = "3")]
1081    pub modal: f64,
1082    #[prost(double, tag = "4")]
1083    pub modal_share: f64,
1084}
1085/// The full result of a study, flattened for display.
1086#[derive(serde::Serialize, serde::Deserialize)]
1087#[derive(Clone, PartialEq, ::prost::Message)]
1088pub struct StudyView {
1089    /// The stored finding this view came from, so the window can ask for
1090    /// something to be done to the record it is showing — a report composed
1091    /// from it, a file kept with it. Empty for a view that was never
1092    /// recorded. `default` because every view built before this had none.
1093    #[prost(string, tag = "1")]
1094    pub id: ::prost::alloc::string::String,
1095    #[prost(string, tag = "2")]
1096    pub instrument: ::prost::alloc::string::String,
1097    /// Every instrument the run held, head first.
1098    ///
1099    /// One entry is an ordinary study. More than one is a book: the same rule
1100    /// on several instruments settling against a single account, where a
1101    /// position one member takes is capital another cannot have.
1102    ///
1103    /// `default` because this is a persisted format — a finding recorded
1104    /// before a run could hold more than one loads as what it was.
1105    #[prost(string, repeated, tag = "3")]
1106    pub instruments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
1107    /// What each member of a book contributed. Empty for a single study.
1108    #[prost(message, repeated, tag = "4")]
1109    pub members: ::prost::alloc::vec::Vec<MemberView>,
1110    #[prost(string, tag = "5")]
1111    pub verdict: ::prost::alloc::string::String,
1112    #[prost(string, repeated, tag = "6")]
1113    pub reasons: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
1114    #[prost(uint32, tag = "7")]
1115    pub trials: u32,
1116    /// Configurations tried elsewhere before a shared experiment arrived,
1117    /// counted into the bar. `default`: a view built before sharing had none.
1118    #[prost(uint32, tag = "8")]
1119    pub prior_trials: u32,
1120    #[prost(double, tag = "9")]
1121    pub best_sharpe: f64,
1122    #[prost(double, optional, tag = "10")]
1123    pub expected_best_under_null: ::core::option::Option<f64>,
1124    #[prost(bool, tag = "11")]
1125    pub survived_deflation: bool,
1126    /// Every configuration the search tried, not only the one it picked.
1127    #[prost(message, optional, tag = "12")]
1128    pub surface: ::core::option::Option<SurfaceView>,
1129    #[prost(string, tag = "13")]
1130    pub in_sample: ::prost::alloc::string::String,
1131    #[prost(string, tag = "14")]
1132    pub out_of_sample: ::prost::alloc::string::String,
1133    #[prost(message, repeated, tag = "15")]
1134    pub selected_params: ::prost::alloc::vec::Vec<NamedNumber>,
1135    #[prost(message, optional, tag = "16")]
1136    pub strategy: ::core::option::Option<MetricsView>,
1137    #[prost(message, optional, tag = "17")]
1138    pub benchmark: ::core::option::Option<MetricsView>,
1139    /// Uncorrected. \[`Self::dividend_gap`\] sits beside it rather than being
1140    /// folded in: a number silently adjusted by something the reader cannot see
1141    /// is what this platform exists to stop.
1142    #[prost(double, tag = "18")]
1143    pub excess_return: f64,
1144    /// How much of that margin is the dividend bias rather than skill.
1145    ///
1146    /// `None` means it was never measured — no distribution series is held for
1147    /// these instruments. The bias is real either way; only its size is
1148    /// unknown, and `recommendations` says so.
1149    #[prost(message, optional, tag = "19")]
1150    pub dividend_gap: ::core::option::Option<DividendGapView>,
1151    /// Both sides after capital-gains tax at stated rates, sold at the end
1152    /// (#26). Beside the excess return for the same reason as the dividend
1153    /// gap. `None` when the curves are too short to tax.
1154    #[prost(message, optional, tag = "20")]
1155    pub after_tax: ::core::option::Option<AfterTaxView>,
1156    /// The two curves behind the numbers. A table says a strategy returned
1157    /// less than the market; a chart says whether it did so steadily or lost
1158    /// it all in one month, and those are different findings.
1159    #[prost(message, repeated, tag = "21")]
1160    pub strategy_curve: ::prost::alloc::vec::Vec<CurvePoint>,
1161    #[prost(message, repeated, tag = "22")]
1162    pub benchmark_curve: ::prost::alloc::vec::Vec<CurvePoint>,
1163    /// The instrument's own bars over the judged period, with every entry and
1164    /// exit marked on them.
1165    ///
1166    /// The view that shows what the rule *did* rather than what it added up
1167    /// to. A summary cannot say the entries all landed on three days, or that
1168    /// every winner came out of one gap; this says it at a glance.
1169    #[prost(message, repeated, tag = "23")]
1170    pub price: ::prost::alloc::vec::Vec<CandlePoint>,
1171    #[prost(message, repeated, tag = "24")]
1172    pub markers: ::prost::alloc::vec::Vec<TradeMarkerView>,
1173    /// The rest of a book's instruments, each with its own bars and its own
1174    /// trades. Empty for a single study.
1175    ///
1176    /// The head instrument is deliberately *not* repeated here — it is already
1177    /// \[`Self::price`\] and \[`Self::markers`\] above. The shape mirrors
1178    /// `Experiment.alongside`, which is head plus the rest for the same reason:
1179    /// carrying the head twice would put a third of a three-member book's
1180    /// candles on the wire for nothing.
1181    #[prost(message, repeated, tag = "25")]
1182    pub alongside_charts: ::prost::alloc::vec::Vec<InstrumentChartView>,
1183    /// Depth below the running peak, as a percentage.
1184    #[prost(message, repeated, tag = "26")]
1185    pub underwater: ::prost::alloc::vec::Vec<CurvePoint>,
1186    /// What is wrong with the bars this was produced from.
1187    #[prost(message, repeated, tag = "27")]
1188    pub data_findings: ::prost::alloc::vec::Vec<
1189        super::super::market::v1::DataFindingView,
1190    >,
1191    /// Every round trip, so nobody has to take the summary on trust.
1192    #[prost(message, repeated, tag = "28")]
1193    pub trades: ::prost::alloc::vec::Vec<TradeRowView>,
1194    /// Month-by-month, so a total return can be read as steady or as one
1195    /// lucky quarter. Derived from the same curve, not a second measurement.
1196    #[prost(message, repeated, tag = "29")]
1197    pub monthly: ::prost::alloc::vec::Vec<MonthlyReturnView>,
1198    /// The round trips behind the return, and what they cost.
1199    #[prost(message, optional, tag = "30")]
1200    pub trades_detail: ::core::option::Option<TradesView>,
1201    /// What to do about this finding, most stopping first.
1202    ///
1203    /// Derived on read rather than stored, so a finding pulled out of memory
1204    /// is read against today's rules rather than the ones in force when it
1205    /// was recorded.
1206    #[prost(message, repeated, tag = "31")]
1207    pub recommendations: ::prost::alloc::vec::Vec<RecommendationView>,
1208    /// The dataset this result was produced from, as a content hash. Compared
1209    /// against the live one to decide whether the result is still current.
1210    #[prost(string, tag = "32")]
1211    pub dataset_version: ::prost::alloc::string::String,
1212    #[prost(string, tag = "33")]
1213    pub strategy_name: ::prost::alloc::string::String,
1214    #[prost(double, tag = "34")]
1215    pub starting_cash: f64,
1216    #[prost(double, tag = "35")]
1217    pub commission_bps: f64,
1218    #[prost(double, tag = "36")]
1219    pub slippage_bps: f64,
1220    #[prost(string, tag = "37")]
1221    pub engine: ::prost::alloc::string::String,
1222}
1223/// One configuration's cell on the search surface.
1224#[derive(serde::Serialize, serde::Deserialize)]
1225#[derive(Clone, Copy, PartialEq, ::prost::Message)]
1226pub struct SurfaceCell {
1227    #[prost(double, tag = "1")]
1228    pub x: f64,
1229    #[prost(double, tag = "2")]
1230    pub y: f64,
1231    #[prost(double, tag = "3")]
1232    pub sharpe: f64,
1233    /// Whether this is the configuration that was chosen.
1234    #[prost(bool, tag = "4")]
1235    pub selected: bool,
1236    /// Whether it beat what a no-skill search of this size would produce.
1237    ///
1238    /// The distinction the whole chart is drawn around. A cell below the bar
1239    /// is not a weak result, it is *not a result* — a score a coin-flipping
1240    /// search of the same size would have been expected to reach anyway.
1241    #[prost(bool, tag = "5")]
1242    pub above_null: bool,
1243}
1244/// The in-sample score of every configuration the search tried.
1245///
1246/// # Why this exists at all
1247///
1248/// Reporting only the winner shows two completely different situations
1249/// identically: a broad region of configurations that all scored well, which
1250/// suggests something real and robust to the exact parameters; and one bright
1251/// cell surrounded by nothing, which is what fitting noise looks like from
1252/// above. The verdict machinery already deflates for the *size* of the search;
1253/// this is the part a person has to look at.
1254#[derive(serde::Serialize, serde::Deserialize)]
1255#[derive(Clone, PartialEq, ::prost::Message)]
1256pub struct SurfaceView {
1257    /// The two axes drawn, by name.
1258    #[prost(string, tag = "1")]
1259    pub x_axis: ::prost::alloc::string::String,
1260    #[prost(string, tag = "2")]
1261    pub y_axis: ::prost::alloc::string::String,
1262    #[prost(double, repeated, tag = "3")]
1263    pub x_values: ::prost::alloc::vec::Vec<f64>,
1264    #[prost(double, repeated, tag = "4")]
1265    pub y_values: ::prost::alloc::vec::Vec<f64>,
1266    #[prost(message, repeated, tag = "5")]
1267    pub cells: ::prost::alloc::vec::Vec<SurfaceCell>,
1268    /// The bar a cell has to clear to be worth anything. `None` when there
1269    /// were too few trials to say.
1270    #[prost(double, optional, tag = "6")]
1271    pub null_bar: ::core::option::Option<f64>,
1272    #[prost(double, tag = "7")]
1273    pub best: f64,
1274    /// Axes not drawn, because a surface has two dimensions and a grid may
1275    /// have more. Named so nobody reads the chart as the whole search.
1276    #[prost(string, repeated, tag = "8")]
1277    pub collapsed: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
1278}
1279/// Where a trade happened, to be drawn on the price.
1280#[derive(serde::Serialize, serde::Deserialize)]
1281#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
1282pub struct TradeMarkerView {
1283    #[prost(int64, tag = "1")]
1284    pub time: i64,
1285    /// `entry` or `exit`; the chart decides shape and side from it.
1286    #[prost(string, tag = "2")]
1287    pub kind: ::prost::alloc::string::String,
1288    /// `stop` or `signal` for an exit, so a stop-out is visually distinct
1289    /// from a rule that chose to leave. Empty for an entry.
1290    #[prost(string, tag = "3")]
1291    pub reason: ::prost::alloc::string::String,
1292    #[prost(string, tag = "4")]
1293    pub label: ::prost::alloc::string::String,
1294}
1295/// One round trip, as a table row.
1296///
1297/// Every field the ledger holds, because the point of a table is that nobody
1298/// has to decide in advance which column someone will want to sort by. The
1299/// aggregate statistics above it answer "how did it do"; this answers "what
1300/// did it actually do", and those are different questions with different
1301/// failure modes — an expectancy of +£300 built from one +£9,000 trade and
1302/// nineteen losses is a fact only the rows show.
1303#[derive(serde::Serialize, serde::Deserialize)]
1304#[derive(Clone, PartialEq, ::prost::Message)]
1305pub struct TradeRowView {
1306    /// Which instrument this round trip was in.
1307    ///
1308    /// Empty for a run recorded before the ledger named instruments, and for
1309    /// an ordinary single-instrument study where the subject line already
1310    /// says it. Only a book shows the column.
1311    #[prost(string, tag = "1")]
1312    pub instrument: ::prost::alloc::string::String,
1313    #[prost(string, tag = "2")]
1314    pub opened: ::prost::alloc::string::String,
1315    /// Empty while the position is still open at the end of the run.
1316    #[prost(string, tag = "3")]
1317    pub closed: ::prost::alloc::string::String,
1318    #[prost(string, tag = "4")]
1319    pub direction: ::prost::alloc::string::String,
1320    #[prost(double, tag = "5")]
1321    pub quantity: f64,
1322    #[prost(double, tag = "6")]
1323    pub entry: f64,
1324    /// `None` while still open, so the table shows a gap rather than a price
1325    /// nobody traded at.
1326    #[prost(double, optional, tag = "7")]
1327    pub exit: ::core::option::Option<f64>,
1328    #[prost(double, tag = "8")]
1329    pub pnl: f64,
1330    #[prost(double, tag = "9")]
1331    pub commission: f64,
1332    /// Days held. Fractional, because an intraday trade held forty minutes is
1333    /// not "0 days" — it is 0.03, and rounding it away would make every
1334    /// intraday ledger look like a column of zeroes.
1335    #[prost(double, optional, tag = "10")]
1336    pub held_days: ::core::option::Option<f64>,
1337    /// `signal`, `stop`, or `open`.
1338    #[prost(string, tag = "11")]
1339    pub exit_reason: ::prost::alloc::string::String,
1340    /// What the rule saw at entry (#190): the condition that fired, the value it
1341    /// was judged on, the regime it saw, and the quantity it asked the gate for.
1342    /// Absent on a trade recorded before the journal or opened by no rule.
1343    #[prost(string, optional, tag = "12")]
1344    pub rule: ::core::option::Option<::prost::alloc::string::String>,
1345    #[prost(double, optional, tag = "13")]
1346    pub signal: ::core::option::Option<f64>,
1347    #[prost(string, optional, tag = "14")]
1348    pub regime: ::core::option::Option<::prost::alloc::string::String>,
1349    #[prost(double, optional, tag = "15")]
1350    pub asked: ::core::option::Option<f64>,
1351}
1352/// What the round trips looked like, flattened for display.
1353///
1354/// Beside the metrics rather than inside them: metrics come from the equity
1355/// curve and these come from the ledger, and keeping the two apart is what
1356/// makes it obvious which is which when they say different things.
1357#[derive(serde::Serialize, serde::Deserialize)]
1358#[derive(Clone, Copy, PartialEq, ::prost::Message)]
1359pub struct TradesView {
1360    #[prost(uint32, tag = "1")]
1361    pub closed: u32,
1362    #[prost(uint32, tag = "2")]
1363    pub still_open: u32,
1364    #[prost(double, optional, tag = "3")]
1365    pub win_rate: ::core::option::Option<f64>,
1366    #[prost(double, optional, tag = "4")]
1367    pub profit_factor: ::core::option::Option<f64>,
1368    #[prost(double, optional, tag = "5")]
1369    pub expectancy: ::core::option::Option<f64>,
1370    #[prost(double, optional, tag = "6")]
1371    pub average_win: ::core::option::Option<f64>,
1372    #[prost(double, optional, tag = "7")]
1373    pub average_loss: ::core::option::Option<f64>,
1374    /// Mean holding period in days, which is the unit a reader thinks in and
1375    /// the one that decides short- versus long-term tax treatment.
1376    #[prost(double, optional, tag = "8")]
1377    pub average_holding_days: ::core::option::Option<f64>,
1378    /// Every fee and commission the venue charged, in account currency.
1379    ///
1380    /// Fees only — slippage is charged inside the fill prices and is already
1381    /// reflected in the return, not here. Naming this `fees` rather than
1382    /// `cost` is the whole point: a reader who took it for the total cost of
1383    /// trading would be short by the spread assumption on every round trip.
1384    #[prost(double, tag = "9")]
1385    pub fees_paid: f64,
1386    /// Fees as a fraction of starting capital, so they can be read against
1387    /// the return directly.
1388    #[prost(double, tag = "10")]
1389    pub fees_fraction: f64,
1390    #[prost(uint32, tag = "11")]
1391    pub signal_exits: u32,
1392    #[prost(uint32, tag = "12")]
1393    pub stop_exits: u32,
1394}
1395/// A finding that could not be read, and why.
1396///
1397/// Shown rather than logged. Four findings were once lost to a field rename
1398/// and the only trace was a warning nobody had reason to look at — a research
1399/// store that quietly forgets things is worse than one that says it has.
1400#[derive(serde::Serialize, serde::Deserialize)]
1401#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
1402pub struct UnreadableView {
1403    #[prost(string, tag = "1")]
1404    pub id: ::prost::alloc::string::String,
1405    #[prost(string, tag = "2")]
1406    pub reason: ::prost::alloc::string::String,
1407}
1408/// A walk-forward run, flattened for display.
1409#[derive(serde::Serialize, serde::Deserialize)]
1410#[derive(Clone, PartialEq, ::prost::Message)]
1411pub struct WalkForwardView {
1412    /// The stored finding this view came from, so the window can ask for
1413    /// something to be done to the record it is showing — a report composed
1414    /// from it, a file kept with it. Empty for a view that was never
1415    /// recorded. `default` because every view built before this had none.
1416    #[prost(string, tag = "1")]
1417    pub id: ::prost::alloc::string::String,
1418    #[prost(string, tag = "2")]
1419    pub instrument: ::prost::alloc::string::String,
1420    #[prost(string, tag = "3")]
1421    pub verdict: ::prost::alloc::string::String,
1422    #[prost(string, repeated, tag = "4")]
1423    pub reasons: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
1424    #[prost(message, repeated, tag = "5")]
1425    pub recommendations: ::prost::alloc::vec::Vec<RecommendationView>,
1426    #[prost(message, repeated, tag = "6")]
1427    pub folds: ::prost::alloc::vec::Vec<FoldView>,
1428    #[prost(uint32, tag = "7")]
1429    pub folds_surviving_deflation: u32,
1430    /// Folds in which the selected configuration never opened a position — the
1431    /// symptom of a step too short for the rule's warm-up.
1432    #[prost(uint32, tag = "8")]
1433    pub folds_without_trades: u32,
1434    /// How the selection moved. The thing only a rolling procedure can show.
1435    #[prost(message, repeated, tag = "9")]
1436    pub stability: ::prost::alloc::vec::Vec<StabilityView>,
1437    #[prost(message, optional, tag = "10")]
1438    pub strategy: ::core::option::Option<MetricsView>,
1439    #[prost(message, optional, tag = "11")]
1440    pub benchmark: ::core::option::Option<MetricsView>,
1441    #[prost(double, tag = "12")]
1442    pub excess_return: f64,
1443    #[prost(message, repeated, tag = "13")]
1444    pub strategy_curve: ::prost::alloc::vec::Vec<CurvePoint>,
1445    #[prost(message, repeated, tag = "14")]
1446    pub benchmark_curve: ::prost::alloc::vec::Vec<CurvePoint>,
1447    #[prost(message, repeated, tag = "15")]
1448    pub price: ::prost::alloc::vec::Vec<CandlePoint>,
1449    #[prost(message, repeated, tag = "16")]
1450    pub markers: ::prost::alloc::vec::Vec<TradeMarkerView>,
1451    #[prost(message, repeated, tag = "17")]
1452    pub underwater: ::prost::alloc::vec::Vec<CurvePoint>,
1453    /// What is wrong with the bars this was produced from.
1454    #[prost(message, repeated, tag = "18")]
1455    pub data_findings: ::prost::alloc::vec::Vec<
1456        super::super::market::v1::DataFindingView,
1457    >,
1458    #[prost(message, repeated, tag = "19")]
1459    pub trades: ::prost::alloc::vec::Vec<TradeRowView>,
1460    #[prost(message, optional, tag = "20")]
1461    pub trades_detail: ::core::option::Option<TradesView>,
1462    #[prost(int64, tag = "21")]
1463    pub in_sample_days: i64,
1464    #[prost(int64, tag = "22")]
1465    pub step_days: i64,
1466    #[prost(bool, tag = "23")]
1467    pub anchored: bool,
1468    #[prost(string, tag = "24")]
1469    pub dataset_version: ::prost::alloc::string::String,
1470    #[prost(string, tag = "25")]
1471    pub strategy_name: ::prost::alloc::string::String,
1472    #[prost(double, tag = "26")]
1473    pub starting_cash: f64,
1474    #[prost(double, tag = "27")]
1475    pub commission_bps: f64,
1476    #[prost(double, tag = "28")]
1477    pub slippage_bps: f64,
1478    #[prost(string, tag = "29")]
1479    pub engine: ::prost::alloc::string::String,
1480}
1481/// One bar, as the chart wants it.
1482#[derive(serde::Serialize, serde::Deserialize)]
1483#[derive(Clone, Copy, PartialEq, ::prost::Message)]
1484pub struct CandlePoint {
1485    #[prost(int64, tag = "1")]
1486    pub time: i64,
1487    #[prost(double, tag = "2")]
1488    pub open: f64,
1489    #[prost(double, tag = "3")]
1490    pub high: f64,
1491    #[prost(double, tag = "4")]
1492    pub low: f64,
1493    #[prost(double, tag = "5")]
1494    pub close: f64,
1495}
1496/// One instrument's bars with its own trades marked on them.
1497///
1498/// A book holds several, and each chart must show only the round trips that
1499/// happened in *its* instrument. Plotting the whole ledger on one member's
1500/// prices puts entries on days that instrument never traded.
1501#[derive(serde::Serialize, serde::Deserialize)]
1502#[derive(Clone, PartialEq, ::prost::Message)]
1503pub struct InstrumentChartView {
1504    #[prost(string, tag = "1")]
1505    pub instrument: ::prost::alloc::string::String,
1506    #[prost(message, repeated, tag = "2")]
1507    pub price: ::prost::alloc::vec::Vec<CandlePoint>,
1508    #[prost(message, repeated, tag = "3")]
1509    pub markers: ::prost::alloc::vec::Vec<TradeMarkerView>,
1510}
1511/// A finding's caveat, against the script line that produced the finding:
1512/// what the Problems panel lists with source "Arvo" (ADR-0021).
1513#[derive(serde::Serialize, serde::Deserialize)]
1514#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
1515pub struct ResearchProblemView {
1516    /// Relative to the project folder.
1517    #[prost(string, tag = "1")]
1518    pub path: ::prost::alloc::string::String,
1519    #[prost(uint32, tag = "2")]
1520    pub line: u32,
1521    /// `error`, `warning` or `info`.
1522    #[prost(string, tag = "3")]
1523    pub severity: ::prost::alloc::string::String,
1524    #[prost(string, tag = "4")]
1525    pub message: ::prost::alloc::string::String,
1526    /// The finding it comes from, to open.
1527    #[prost(string, tag = "5")]
1528    pub finding: ::prost::alloc::string::String,
1529}
1530/// The Problems panel's rows.
1531///
1532/// A list, as a message: an RPC returns one message, never a bare repeated
1533/// field, so a call that answers with a list needs a name for the list.
1534#[derive(serde::Serialize, serde::Deserialize)]
1535#[derive(Clone, PartialEq, ::prost::Message)]
1536pub struct ProblemsView {
1537    #[prost(message, repeated, tag = "1")]
1538    pub problems: ::prost::alloc::vec::Vec<ResearchProblemView>,
1539}