// Quote field panel
// Part 22 - "Quote Fields and the Real-Time Quote Window"
//
// Shows the documented real-time quote fields for the selected symbol and states,
// on every row, where that number came from: the streaming feed, or your own bars.
//
// ASSUMPTIONS - read these before trusting anything the panel prints:
//
//  1. GetRTData() is documented as PROFESSIONAL edition only. On the Standard
//     edition it returns Null for every field without raising an error. On any
//     database that is not fed by a streaming real-time plugin there is no field
//     data for it to report at all.
//  2. The official documentation promises no particular value for the "nothing
//     was supplied" case, so this formula treats Null and exact zero alike as
//     absent rather than assuming a sentinel. A genuine zero bid size is rare,
//     and printing "not supplied" for one is safer than printing a price of 0.00
//     for a symbol whose feed has died.
//  3. The fallback numbers are computed from the bars already in your database.
//     They are NOT quotes, and the panel labels them so you cannot confuse the
//     two. Bid, ask and the two sizes have no bar-derived equivalent whatsoever,
//     so they stay blank offline - which is itself the lesson.
//  4. Session boundaries are detected with Day(). That is correct for a database
//     whose bar time stamps are already in the exchange's local time (Part 20).

_SECTION_BEGIN( "Quote field panel" );

RefreshSeconds = Param( "Refresh interval (seconds)", 5, 1, 60, 1 );
StaleLimit     = Param( "Call the feed stale after (seconds)", 30, 5, 600, 5 );
ShowFallback   = ParamToggle( "Show bar-derived fallback", "No|Yes", 1 );

// Re-executes this pane on a timer. Documented to work with or without a data
// plugin, which is what makes the offline behaviour of this panel testable.
RequestTimedRefresh( RefreshSeconds );

// ---------------------------------------------------------------- live fields
// Every string below appears verbatim in the documented GetRTData field list.
RtLast    = GetRTData( "Last" );
RtBid     = GetRTData( "Bid" );
RtAsk     = GetRTData( "Ask" );
RtBidSize = GetRTData( "BidSize" );
RtAskSize = GetRTData( "AskSize" );
RtHigh    = GetRTData( "High" );
RtLow     = GetRTData( "Low" );
RtVolume  = GetRTData( "TotalVolume" );
RtPrev    = GetRTData( "Prev" );

function IsSupplied( FieldValue )
{
    return NOT IsNull( FieldValue ) AND FieldValue != 0;
}

// One row of the panel. The suffix is the whole point: a reader must never have
// to guess whether a number on screen came from the market or from arithmetic.
function ResolveText( LiveValue, BarValue, Digits, AllowFallback )
{
    if( IsSupplied( LiveValue ) )
    {
        RowText = NumToStr( LiveValue, Digits ) + "   [live field]";
    }
    else
    {
        if( AllowFallback AND NOT IsNull( BarValue ) )
            RowText = NumToStr( BarValue, Digits ) + "   [from bars - not a quote]";
        else
            RowText = "not supplied";
    }

    return RowText;
}

// ------------------------------------------------------------- feed staleness
// Status("lastrtupdate") reports the time of the last update sent by the plugin.
// DateTime values may only be compared reliably with == and !=, so test against
// zero rather than asking whether it is "greater than" anything.
LastUpdate = Status( "lastrtupdate" );

if( LastUpdate != 0 )
{
    AgeSeconds = DateTimeDiff( Now( 5 ), LastUpdate );
    UpdateText = DateTimeToStr( LastUpdate );

    if( AgeSeconds > StaleLimit )
        StaleText = "STALE - " + NumToStr( AgeSeconds, 1.0 ) + " s since the plugin last spoke";
    else
        StaleText = "fresh - " + NumToStr( AgeSeconds, 1.0 ) + " s since the plugin last spoke";
}
else
{
    AgeSeconds = -1;
    UpdateText = "none reported";
    StaleText  = "unknown - no real-time update has been reported to this formula";
}

// --------------------------------------------------- bar-derived substitutes
// A running session high, low and volume, computed without looking forward.
NewSession  = Day() != Ref( Day(), -1 );
SessionHigh = HighestSince( NewSession, High );
SessionLow  = LowestSince( NewSession, Low );
CumVolume   = Cum( Volume );
SessionVol  = CumVolume - ValueWhen( NewSession, CumVolume - Volume );

BarLast   = LastValue( Close );
BarHigh   = LastValue( SessionHigh );
BarLow    = LastValue( SessionLow );
BarVolume = LastValue( SessionVol );
BarPrev   = LastValue( ValueWhen( NewSession, Ref( Close, -1 ) ) );

// ------------------------------------------------------------------- the rows
FeedLooksLive = IsSupplied( RtLast ) OR IsSupplied( RtBid );

LastText  = ResolveText( RtLast, BarLast, 1.2, ShowFallback );
BidText   = ResolveText( RtBid, Null, 1.2, ShowFallback );
AskText   = ResolveText( RtAsk, Null, 1.2, ShowFallback );
BidSzText = ResolveText( RtBidSize, Null, 1.0, ShowFallback );
AskSzText = ResolveText( RtAskSize, Null, 1.0, ShowFallback );
HighText  = ResolveText( RtHigh, BarHigh, 1.2, ShowFallback );
LowText   = ResolveText( RtLow, BarLow, 1.2, ShowFallback );
PrevText  = ResolveText( RtPrev, BarPrev, 1.2, ShowFallback );
VolText   = ResolveText( RtVolume, BarVolume, 1.0, ShowFallback );

// A spread needs both sides. Half a spread is not a number worth printing.
if( IsSupplied( RtBid ) AND IsSupplied( RtAsk ) )
    SpreadText = NumToStr( RtAsk - RtBid, 1.4 ) + "   [live field]";
else
    SpreadText = "cannot be computed - at least one side is missing";

// Bar Replay is the offline stand-in for a moving market, so say when it is on.
PlaybackAt = GetPlaybackDateTime();

if( PlaybackAt )
    ReplayText = "Bar Replay : ACTIVE, playback position " + DateTimeToStr( PlaybackAt );
else
    ReplayText = "Bar Replay : not active";

SourceText = WriteIf( FeedLooksLive,
                      "Streaming quote fields are being reported for this symbol.",
                      "NO LIVE QUOTE FIELDS. Anything below marked [from bars] is arithmetic on your database." );

// ------------------------------------------------------------------ the panel
Plot( Close, "Close", colorDefault, styleCandle );

Title =
    "Quote field panel - " + Name() + "   (" + Interval( 2 ) + " bars)\n" +
    SourceText + "\n" +
    "Plugin last update: " + UpdateText + "\n" +
    "Staleness  : " + StaleText + "\n" +
    ReplayText + "\n" +
    "\n" +
    "Last       : " + LastText + "\n" +
    "Bid        : " + BidText + "      size " + BidSzText + "\n" +
    "Ask        : " + AskText + "      size " + AskSzText + "\n" +
    "Spread     : " + SpreadText + "\n" +
    "Day high   : " + HighText + "\n" +
    "Day low    : " + LowText + "\n" +
    "Prev close : " + PrevText + "\n" +
    "Day volume : " + VolText;

_SECTION_END();
