In some cases applying \protected@edef and \@onelevel@sanitize before checking for a "stringified" _ might work out:
\documentclass{article}
\usepackage{amsmath}
\usepackage{xifthen}
\makeatletter
\DeclareRobustCommand\DetectUnderscore[1]{%
\begingroup
\protected@edef\@tempa{#1}%
\@onelevel@sanitize\@tempa
\expandafter\expandafter\expandafter\endgroup
\expandafter\expandafter\expandafter\ifthenelse
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter\isin
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\string_%
\expandafter}%
\expandafter{%
\@tempa}}{{(#1)}}{{#1}}%
}%
\newcommand\low[1]{%
\DetectUnderscore{#1}%
_{l_{\mathcal{A}}}%
}
\makeatother
\begin{document}
\[
\low{\Sigma} \qquad
\low{\low{\Sigma}} \qquad
\low{\low{\low{\Sigma}}} \qquad
\]
\[
\low{\Sigma_b} \qquad
\low{\low{\Sigma_b}} \qquad
\low{\low{\low{\Sigma_b}}} \qquad
\]
\[
\low{{\Sigma_b}} \qquad
\low{b_{\low{c_{\low{\Sigma_d}}}}} \qquad
(\Sigma_b)_{l_\mathcal{A}}
\]
\[
\low{\low{1} + 2}
\]
\end{document}

\expandafter causes that the next but one token —if expandable—gets expanded exactly once before the next token gets expanded if expandable. (La)TeX considers the work of \expandafter done when expansion of the next but one token is done. Therefore you can use chains/sequences of \expandafter to have (La)TeX "jump" over k tokens for first expanding the (k+1)-th token.
\@onelevel@sanitize\macro changes the definition of \macro so that \macro spits out a sequence of character tokens of category code 12(other) that looks like the token-sequence that would have been "spit out" by \macro before applying \@onelevel@sanitize. It is almost like redefining \macro to what you get by applying \string to each token of \macro's definition.
\protected@edef defines a macro but before doing so, it expands all expandable tokens of the definition-text except those that either are defined via \DeclareRobustCommand or are preceded by the token \protect. You might say: \protected@edef does "unroll" the definitions of the tokens contained in its definition-text before actually performing the assignment.
\@tempa is a scratch-macro which gets defined by means of \protected@edef to expand to the argument #1 with all definitions in #1 "unrolled".
The \ifthenelse{\isin...}-test does not find _ that are nested in curly-braces as curly braces usually have a special function. Therefore \@onelevel@sanitize is applied for turning all tokens, and thus also the curly braces, into ordinary harmless character-tokens of category code 12(other) which do not disturb the \ifthenelse{\isin...} test.
And here is a routine which does not check for (stringified) underscore of category code 12 (other) via xifthen's \ifthenelse{\isin...}-thingie but does check for tokens of category code 8 (subscript) without stringification.
The routine recursively calls itself when examining the tokens that form the argument.
The routine still does not expand the argument—this still has to be done via \protected@edef.
\documentclass{article}
\usepackage{amsmath}
\makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia ;-) :
%%.............................................................................
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%% \UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% \UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%
%% !!! The argument of \UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via \UD@CheckWhetherNull before applying
%% \UD@ExtractFirstArg.
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
\romannumeral0%
\UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{ #1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%.............................................................................
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
\UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has leading
%% catcode-1-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has no leading
%% catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
\romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
\UD@firstoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
\romannumeral0\UD@CheckWhetherNull{#1}%
{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
{\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
{\UD@exchange{\UD@firstoftwo}}{\UD@exchange{\UD@secondoftwo}}%
{\UD@exchange{ }{\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter}\expandafter\expandafter
\expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%.............................................................................
%% Check whether brace-balanced argument starts with a token of
%% category code 8 (subscript)
%%.............................................................................
%% \UD@CheckWhetherFirstTokenHasCatcodeSubscript{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% <argument which is to be checked> has a first
%% token of catcode 8>}%
%% {<Tokens to be delivered in case that
%% <argument which is to be checked> does not have
%% a first token of catcode 8>}%
%%
\newcommand\UD@CheckWhetherFirstTokenHasCatcodeSubscript[1]{%
\romannumeral0%
\UD@CheckWhetherNull{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
\UD@CheckWhetherBrace{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
\UD@CheckWhetherLeadingSpace{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
\expandafter\expandafter\expandafter\UD@@CheckWhetherFirstTokenHasCatcodeSubscript
\UD@ExtractFirstArg{#1}%
}%
}%
}%
}%
\newcommand\UD@@CheckWhetherFirstTokenHasCatcodeSubscript[1]{%
\expandafter\ifcat_#1%
\expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
{\UD@exchange{ }{\expandafter}\UD@firstoftwo}%
{\UD@exchange{ }{\expandafter}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument does contain underscore/tokens of
%% category code 8 (subscript), no matter if nested in braces or not.
%%
%% \UD@CheckWhetherSubscriptTokens{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% <argument which is to be checked> contains
%% some token(s) of catcode 8 (subscript)>}%
%% {<Tokens to be delivered in case that
%% <argument which is to be checked> contains
%% no token of catcode 8 (subscript)>}%
%%
%%-----------------------------------------------------------------------------
\newcommand\UD@CheckWhetherSubscriptTokens{\romannumeral0\UD@@CheckWhetherSubscriptTokens}%
\newcommand\UD@@CheckWhetherSubscriptTokens[3]{%
\UD@CheckWhetherNull{#1}{ #3}{%
\UD@CheckWhetherLeadingSpace{#1}{%
\expandafter\UD@@CheckWhetherSubscriptTokens\expandafter{\UD@removespace#1}{#2}{#3}%
}{%
\UD@CheckWhetherBrace{#1}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherSubscriptTokens
\UD@ExtractFirstArg{#1}{ #2}{%
\expandafter\UD@@CheckWhetherSubscriptTokens\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
}%
}{%
\UD@CheckWhetherFirstTokenHasCatcodeSubscript{#1}{ #2}{%
\expandafter\UD@@CheckWhetherSubscriptTokens\expandafter{\UD@firstoftwo{}#1}{#2}{#3}%
}%
}%
}%
}%
}%
\DeclareRobustCommand\UD@CallUD@CheckWhetherSubscriptTokensOnExpansion[1]{%
\begingroup
\protected@edef\@tempa{#1}%
\expandafter\endgroup
\expandafter\UD@CheckWhetherSubscriptTokens\expandafter{\@tempa}{{(#1)}}{{#1}}%
}%
\newcommand\low[1]{%
\UD@CallUD@CheckWhetherSubscriptTokensOnExpansion{#1}_{l_{\mathcal{A}}}%
}
\makeatother
\begin{document}
% Let`s use | and \myunderscore in the same way as _ :
\catcode`\|=8
\let\myunderscore=_
\[
\low{\Sigma} \qquad
\low{\low{\Sigma}} \qquad
\low{\low{\low{\Sigma}}} \qquad
\]
\[
\low{\Sigma\myunderscore b} \qquad
\low{\low{\Sigma\myunderscore b}} \qquad
\low{\low{\low{\Sigma|b}}} \qquad
\]
\[
\low{{\Sigma_b}} \qquad
\low{b_{\low{c_{\low{\Sigma|d}}}}} \qquad
(\Sigma_b)_{l_\mathcal{A}}
\]
\[
\low{\low{1} + 2}
\]
\end{document}

Be aware that this routine does not aim at detecting (stringified) _-character-tokens of category-code 12 (other) but at detecting all character-tokens (be they explicit or implicit) of category code 8 (subscript).
_with a different catcode for other purposes than math-subscripting, and it also takes into account that you may assign catcode 8 to characters other than_for using them for math-subscripting... – Ulrich Diez Mar 13 '19 at 15:11