State Machine Approach
I went about this in a classical fashion, e.g., parsing a list of values. One could see this as a state machine and we need to keep track of the states ascendingQ
(movement so far has been upwards in general) and levelQ
(currently we are neither ascending nor descending) as shown in the state chart below.

The transitions are almost exclusively triggered by the gradient of ascend which we will store as a list differences
using Differences[list]
. We will step through the list of gradients incrementing the counter i
at the end of each transition-driven action (numbered arcs in the starte chart). The basic actions are given in the list below.

Whenever we mark an element or a list of candidates for ledges, plateaus or straths, we will store the corresponding elements from the list specified by the index values, i.e., l[[i]]
.
Implementation
The code below is now adapted to mark rising and falling ledges differently and it is rather straight forward to adapt it to whatever one needs.
list = {0, 0, 1, 2, 2, 3, 4, 4, 5, 5, 5, 4, 3, 3, 2, 2, 3, 2, 1, 1, 2,
2, 1};
parseList[l_List] := Module[
{
differences = Differences @ l,
ledgeCandidates = {}, \
parsedPoints,
length = (Length @ l) - 1,
ascendingQ = False,
levelQ = False,
mark,
labelFunc = Function[{elem, str}, Callout[ Style[ elem, Red], str, CalloutMarker -> "Circle"]]
}
,(* mark a list of points *)
mark[arg_List, label_String] := With[
{indices = Flatten @ arg},
ledgeCandidates = {};
Which[
label == "RisingLedge",
{
labelFunc[l[[First @ indices]], "Ledge"],
l[[Rest @ indices]]
},
label == "FallingLedge",
{
l[[Most @ indices]],
labelFunc[l[[Last @ indices]], "Ledge"]
},
True,
labelFunc[l[[#]], label]& /@ indices
]
];
(* mark a single point *)
mark[arg_Integer, label_String] := labelFunc[l[[arg]], label];
parsedPoints = Table[
Which[
(* levelQ and ascendingQ *)
differences[[i]] > 0 && levelQ && ascendingQ,
(
levelQ = False;
mark[ledgeCandidates, "RisingLedge"]
)
,
differences[[i]] < 0 && levelQ && ascendingQ,
(
levelQ = False;
ascendingQ = False;
mark[ledgeCandidates, "Plateau"]
)
,(* levelQ and ¬ascendingQ *)
differences[[i]] > 0 && levelQ && ¬ascendingQ,
(
levelQ = False;
ascendingQ = True;
mark[ledgeCandidates, "Strath"]
)
,
differences[[i]] < 0 && levelQ && ¬ascendingQ,
(
levelQ = False;
mark[ledgeCandidates, "FallingLedge"]
)
,
differences[[i]] == 0 && levelQ
,
(
ledgeCandidates = {ledgeCandidates, {i, i + 1}};
Nothing
)
,(* ¬levelQ and ascendingQ *)
differences[[i]] > 0 && ¬levelQ && ascendingQ,
l[[i]]
,
differences[[i]] < 0 && ¬levelQ && ascendingQ,
(
ascendingQ = False;
mark[i, "Peak"]
)
,(* ¬levelQ and ¬ascendingQ *)
differences[[i]] > 0 && ¬levelQ && ¬ascendingQ,
(
ascendingQ = True;
mark[i, "Valley"]
)
,
differences[[i]] < 0 && ¬levelQ && ¬ascendingQ,
l[[i]]
,
True, (* differences[[i]]==0 && ¬levelQ *)
(
levelQ = True;
ledgeCandidates = {ledgeCandidates, {i, i + 1}};
Nothing
)
]
,
{i, 1, length}
];
(* Handle last section *)
parsedPoints = Flatten @ {
parsedPoints
,
Which[
levelQ && ¬ascendingQ, mark[ledgeCandidates, "Strath"]
,
levelQ && ascendingQ, mark[ledgeCandidates, "Plateau"]
,
True, l[[length + 1]]
]
}
]
parseList @ list // ListPlot[ #, Joined -> True, ImageSize -> Large, PlotRangePadding -> 0.5, PlotTheme -> "Detailed"]&

Requested List Output
Using pattern matching it is rather straight forward to come up with the requested list form for the output from parseList
:
toListOutput = Function[ arg, arg //RightComposition[
ReplaceAll[ Callout[val_Integer, ___]| Callout[ Style[ val_Integer,___],___]:> {val}],
ReplaceRepeated[{a___,Longest[l:Repeated[{val_Integer}]], b___} :> {a, Flatten @ {l}, b}]
]
];
parseList @ list // toListOutput
(* {{0,0},1,{2},2,3,{4},4,{5,5,5,5},4,3,{3},{2,2},{3},2,{1,1},{2,2},1} *)
Performance Comparison
To test performance of different approaches, we will use a bigger list bigList
. To fairly compare approaches we should compare the time it takes to come up with a result that is ready to be fed to a ListPlot
or ListLinePlot
.
SeedRandom["1234"];
bigList = NestList[ # + RandomInteger[{-1, 1}] &, 0, 1000 ];
RepeatedTiming[ parseList @ bigList; ]
(* {0.00395423, Null} *)
RepeatedTiming[ annotatePVL @ bigList /. replacements); ]
(* {0.0051159, Null} *)
RepeatedTiming[ pvlPositions @ bigList; ]
(* {5.83603, Null} not even ready for plotting *)
(* RepeatedTiming[ ... @ bigList; ] @vindobona's approach *)
(* {0.00395119, Null} *)
{0, 0, 1, {2}, 2, 3, {4}, 4, {5, 5, 5}, 4, 3, {3}, 2, 2, {3}, 2, 1, 1, {2, 2}, 1}
,that is, the intermediate results should be{5, 5, 5}, 4, 3, {3}
,see my answer. $\endgroup$5, 5, 5
plateau incorrectly: you've marked the first3
in the list of numbers (... {5, 5, 5}, 4, {3}, 3, ...
), but the graph has the second3
marked, so that segment of the list should be... {5, 5, 5}, 4, 3, {3}, ...
$\endgroup$