// This is an independent project of an individual developer. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

/**
 * File:   PnlAction.cpp
 * Author: kan
 *
 * Created on 2015.11.11
 * @lastupdate 2018.05.18
 */

#include <map>
#include <list>
#include <vector>
#include <set>
#include <cassert>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <numeric>
#include <climits>
#include <stdlib.h>     /* srand, rand */
#include <time.h>       /* time */
#include <boost/math/distributions/students_t.hpp>

#include "DelphisRound.h"
#include "Comparers.h"

//#define FULLDATA
#ifdef FULLDATA
    #include <iostream>
    #include "Indicators.h"
#endif

#include "PnlAction.h"

//------------------------------------------------------------------------------------------
TPriceSeries DealsToPnLs( const TDeals & aDeals ) {

    //<editor-fold desc="Удалить все пустые сделки">
    TDeals lDeals( aDeals );
    auto it = lDeals.begin();
    while( it != lDeals.end() ) {
        if( isZero( it->ClosePrice - it->OpenPrice ) ) {
            it = lDeals.erase( it );
        } else {
            ++it;
        }
    }
    //</editor-fold>

    TPriceSeries lPnLs; //( lDeals.size() );
    lPnLs.reserve(lDeals.size());

    //<editor-fold desc="Собственно конвертация">
    for( const TDeal& lDeal : lDeals ) {
        lPnLs.emplace_back(
            lDeal.CloseTime,
            ( (lDeal.DealSide == TDealSide::Buy) ? (lDeal.ClosePrice - lDeal.OpenPrice) : (lDeal.OpenPrice - lDeal.ClosePrice) ),
            ToDouble( lDeal.Volume )
        );
    }
    //</editor-fold>

    return lPnLs;
}

//------------------------------------------------------------------------------------------
bool CalcDrawDown(
    const TPriceSeries & aPnL,
    TPrice & aoMaxDD,
    TInnerDate & aoBegin,
    TInnerDate & aoReturn ) {

    aoBegin = -1;
    aoReturn = -1;

    if( aPnL.empty() ) {
        aoMaxDD = -1;
        return false;
    }

    TPrice lMax = 0.0;
    TPrice lMin = 0.0;
    TPrice lPnl = 0.0;
    TPrice lDelta = 0.0;
    size_t lBegin = 0;
    bool lHaveMin = false;
    bool lResult = false;

    for( size_t i = 0; i < aPnL.size() ; ++i ) {
        lPnl += aPnL[i].Price;

        if( IsGreat( lPnl, lMax ) ) { //очередной максимум
            lMax = lPnl;
            lMin = lPnl;

            if( lHaveMin ) {
                    aoBegin = aPnL[ lBegin ].DateTime;
                    aoReturn = aPnL[ i ].DateTime;
                    lHaveMin = false;
                    lResult = true;
            }

            lBegin = i;

        } else if( IsGreat( lMin, lPnl ) ) { //новый минимум
            lMin = lPnl;

            if( IsGreat( lMax - lMin, lDelta ) ) {
                lDelta = lMax - lMin;
                lHaveMin = true;
            }
        }
    }

    aoMaxDD = lDelta;

    return lResult;
}

//------------------------------------------------------------------------------------------
TPriceSeries ReductionOfTheIncome(
    const TPriceSeries & aPnL,
    const size_t aProfitNum,
    const double aProfitCoef,
    const size_t aLossNum,
    const double aLossCoef ) {

    std::multimap< TPrice, size_t > lProfits;
    for( size_t i = 0; i < aPnL.size(); ++i ) {

        TPrice lValue = aPnL[ i ].Price ;
        //lProfits.insert( std::pair< TPrice, size_t >( lValue, i ) ) ;
        lProfits.emplace( lValue, i ) ;
    }

    TPriceSeries lPnLVector( aPnL );
    size_t i = aProfitNum;
    for( auto it = lProfits.rbegin(); it != lProfits.rend(); ++it ) {
        if( it->first > 0 ) {
            const size_t lRealID = it->second ;
            TSimpleTick lTick = aPnL[ lRealID ] ;
            lTick.Price  /= aProfitCoef ;
            lPnLVector[ lRealID ] = lTick;
        }
        if( --i == 0 ) break;
    }

    i = aLossNum;
    for( auto it = lProfits.begin(); it != lProfits.end() ;++it ) {
        if( it->first < 0 ) {
            const size_t lRealID = it->second ;
            TSimpleTick lTick = aPnL[ lRealID ] ;
            lTick.Price  *= aLossCoef ;
            lPnLVector[ lRealID ] = lTick;
        }
        if( --i == 0 ) break;
    }

    return lPnLVector;
}

//------------------------------------------------------------------------------------------
TPrice PnLsToMoneyResult( const TPriceSeries & aPnls, const bool aUseVolume ) {
    TPrice lResult = 0.0;
    for( const TSimpleTick& lPnl : aPnls ) {
        lResult += lPnl.Price * ( aUseVolume ? lPnl.Volume : 1.0 );
    }

    return lResult;
}

//------------------------------------------------------------------------------------------
double Student_t_value( const size_t Sn, const double tail=0.2 ){//80%
    using boost::math::students_t;
    students_t dist( ToDouble(Sn) - 1 );
    const double T = quantile(complement(dist, tail / 2.0));

    return T;
}

//------------------------------------------------------------------------------------------
TPrice PnLsToMoneyStatValue( const TPriceSeries & aPnl, const bool aUseVolume, const size_t N, const double aQuantile ) {
    const size_t lSize = aPnl.size();
    if( lSize < 2 or N < 2 ) {
        return 0.0;
    }

    std::vector< TPrice > lPnl( lSize );
    TPrice sum = 0.0;
    for( size_t i=0; i<lSize; ++i ){
        const TPrice lValue = aPnl[i].Price * ( aUseVolume ? aPnl[i].Volume : 1.0 );
        sum += lValue;
        lPnl[i] = lValue;
    }

    const TPrice mean = sum / ToDouble(lSize);

    TPrice lstdev = 0.0;
    for( size_t i=0; i<lSize; ++i ){
        lstdev += pow(lPnl[i] - mean, 2);
    }

    lstdev /= ToDouble(lSize);

    const double lStudAN = Student_t_value( lSize, aQuantile ) / sqrt( ToDouble(N) );

    return (mean - sqrt(lstdev)*lStudAN)*ToDouble(N);
}

//------------------------------------------------------------------------------------------
TPrice PnLsToMoneyStatValueGost( const TPriceSeries & aPnl, const bool aUseVolume, const size_t N ) {
    const size_t lSize = aPnl.size();
    if( lSize < 2 ) {
        return 0.0;
    }

    std::vector< TPrice > lPnl( lSize );
    TPrice sum = 0.0;
    for( size_t i=0; i<lSize; ++i ){
        const TPrice lValue = aPnl[i].Price * ( aUseVolume ? aPnl[i].Volume : 1.0 );
        sum += lValue;
        lPnl[i] = lValue;
    }

    const TPrice mean = sum / ToDouble(lSize);

    TPrice lstdev = 0.0;
    for( size_t i=0; i<lSize; ++i ){
        lstdev += pow(lPnl[i] - mean, 2);
    }

    lstdev /= (ToDouble(lSize) - 1.5);//ГОСТ Р 8.736-2011

    return ( mean - sqrt( lstdev ) / sqrt( ToDouble(lSize) ) )*ToDouble(N);
}

//------------------------------------------------------------------------------------------
TPrice PnLsToMoneyMonteCarlo( const TPriceSeries & aPnl, const bool aUseVolume, const size_t N, const size_t aSamples ){
    const size_t lSize = aPnl.size();
    if( lSize < 2 or N < 2 ) {
        return 0.0;
    }

    TPrice lResult = ToDouble( ULLONG_MAX ); //очень большое число

    for( size_t i=0; i<aSamples; ++i ){
        TPrice lPnlTest = 0.0;
        for( size_t j=0; j<N; ++j ){
            const size_t lID = rand() % lSize;
            const TPrice lPnlSample = aPnl[lID].Price * (aUseVolume ? aPnl[lID].Volume : 1.0);
            lPnlTest += lPnlSample;
        }
        lPnlTest /= ToDouble(N);

        lResult = std::min(lResult, lPnlTest);
    }

    return lResult * ToDouble(N);
}

//------------------------------------------------------------------------------------------
TPrice PnLsToMoneyMonteCarloQuantile( const TPriceSeries & aPnl, const bool aUseVolume, const size_t N, const size_t aSamples, const double aQuantile ) {
    const size_t lSize = aPnl.size();
    if( lSize < 2 or N < 2 or IsGreat( aQuantile, 1.0 ) ) {
        return 0.0;
    }

    std::multiset<TPrice> lData;

    for( size_t i=0; i<aSamples; ++i ){
        TPrice lPnlTest = 0.0;
        for( size_t j=0; j<N; ++j ){
            const size_t lID = rand() % lSize;
            const TPrice lPnlSample = aPnl[lID].Price * (aUseVolume ? aPnl[lID].Volume : 1.0);
            lPnlTest += lPnlSample;
        }
        lPnlTest /= ToDouble(N);

        lData.insert( lPnlTest );
    }

    const size_t lID = RoundToSize_t( aQuantile * ToDouble( aSamples ) );

    auto iter = lData.cbegin();
    std::advance(iter, lID);

    const TPrice lResult = *iter;

    return lResult * ToDouble(N);
}

//------------------------------------------------------------------------------------------
TPrice DealsToPNLCoefficientQuick(
    const TDeals & aDeals,
    const TPrice aFirstPrice,
    const size_t aMinDeals ) {

    TPriceSeries lPnL( DealsToPnLs( aDeals ) );
    if( lPnL.size() <= aMinDeals ) {
        return gMaxBedAttraction;
    }

    TPrice lMaxDD = -1;
    TInnerDate lBegin;
    TInnerDate lReturn;

    bool lDDOk = CalcDrawDown( lPnL, lMaxDD, lBegin, lReturn );
    bool lGoodResult = lDDOk or ( isZero( lMaxDD ) );

    if( not lGoodResult ) { return gMaxBedAttraction; }

    const size_t lDealsToReduction = std::max( std::min( aMinDeals / 2, 3UL ), 1UL );
    const double lReductionCoeff = 2.0;
    const TPriceSeries lPnLPes( ReductionOfTheIncome( lPnL, lDealsToReduction, lReductionCoeff, lDealsToReduction, lReductionCoeff ) );
    const TPrice lPnlMoneyReduction = PnLsToMoneyResult( lPnLPes );

    const TPrice lPnlMoney = PnLsToMoneyResult( lPnL );
    const TPrice lResult =  lPnlMoneyReduction / ( lMaxDD + std::abs( lPnlMoney ) );
//    TPrice lResult =  lPnlMoney / ( lMaxDD + std::abs( lPnlMoney ) );
//    TPrice lResult =  lPnlMoney / ( lMaxDD + 1 );

    return lResult ;
}

//------------------------------------------------------------------------------------------
TPrice DealsToPNLCoefficient(
    const TDeals & aDeals,
    const TInnerDate aFirstPoint,
    const TInnerDate aLastPoint,
    const TPrice aMinPnl,
    const size_t aMinDeals,
    const size_t aQuantTime ) {

    if( aDeals.size() < aMinDeals ) {
        return gMaxBedAttraction;
    }

    //region перевести список сделок в список d_pnl
    TPrice lResult = gMaxBedAttraction ;
    TPrice lPnLValue = 0.0;
    //TInnerDate lPnLTime = 0.0;
    std::list< TSimpleTick > lPnLs;
    lPnLs.push_back( {aFirstPoint, 0.0, 0} );

    for( const auto &lDeal : aDeals ) {
        TInnerDate lEmptyDate = lDeal.OpenTime;
        TSimpleTick lEmptyTick = { lEmptyDate, 0.0, 0 };
        lPnLs.push_back( lEmptyTick );

        TInnerDate lBeginDate = lDeal.CloseTime;

        const TPrice lDealPnl =
            (lDeal.DealSide == TDealSide::Buy) ?
                (lDeal.ClosePrice - lDeal.OpenPrice) :
                (lDeal.OpenPrice - lDeal.ClosePrice);

        TSimpleTick lRealTick = { lBeginDate, lDealPnl, 1 };
        lPnLs.push_back( lRealTick );

        lPnLValue += lDealPnl;
        //lPnLTime += ( lDeal.CloseTime - lDeal.OpenTime );
    }

    lPnLs.push_back( {aLastPoint, 0.0, 0} );
    //endregion

    // рассчитать общий pnl, учесть aMinPnl
    if( lPnLValue <= aMinPnl ) {
        return gMaxBedAttraction;
    }

    //region  рассчитать ср. и макс. d_pnl, число сделок
    const size_t lWeekCouner = CeilToSize_t( ( aLastPoint - aFirstPoint ) / static_cast< double >( aQuantTime ) );
    std::vector< TPrice > lQuantPnL( lWeekCouner );
    size_t lIdx=0;
    TInnerDate lNextDate = aFirstPoint + static_cast<TInnerDate>( aQuantTime );

    std::list< TSimpleTick > lPnLsToQuanting( lPnLs );
    while( not lPnLsToQuanting.empty() ) {
        auto lTick = lPnLsToQuanting.front();
        lPnLsToQuanting.pop_front();

        if( lTick.DateTime > lNextDate ) {
            lNextDate += static_cast<TInnerDate>( aQuantTime );
            lIdx++;
        }
        lQuantPnL[ lIdx ] += lTick.Price;
    }

    TPrice lMaxPnl = 0.0; //если lPnLValue>0 значит точно есть положительный pnl за интервал
    TPrice lMidPnl = 0.0;
    size_t lDealCounter = 0 ;
    for( const auto ldPnl : lQuantPnL ) {
        if( ldPnl > lMaxPnl ){ lMaxPnl = ldPnl; }

        lMidPnl += ldPnl ;
        lDealCounter++ ;
    }
    //endregion

    // ... рассчитать разброс pnl,
    TPrice lPnLValatility = ( lMidPnl / ToDouble( lDealCounter ) ) / lMaxPnl;

    //region расчитать d_DD, учесть DD==0
    TInnerDate lDD_begin ;
    TPrice lCurrDD = 0.0;
    TPrice lSumCurrDD = 0.0;
    TInnerDate lPrioreDate = aFirstPoint ;
    for( const auto& lTick : lPnLs ) {

        if( isZero( lCurrDD ) and lTick.Price < 0.0 ) {

            lCurrDD = lTick.Price;
            lDD_begin = lPrioreDate ;

            lSumCurrDD += lCurrDD * ( lTick.DateTime - lDD_begin ) ;// 86400.0;

        } else if( lCurrDD < 0.0 ) {
            TPrice lPriorDD = lCurrDD;
            lCurrDD += lTick.Price;

            if( lCurrDD > 0.0 ) {
                lCurrDD = 0.0;

            } else {
                lPriorDD = lCurrDD;
            }

            lSumCurrDD += lPriorDD * ( lTick.DateTime - lPrioreDate ) ;// 86400.0;
        }

        lPrioreDate = lTick.DateTime ;
    }
    //endregion

    if( isPositiveValue( lSumCurrDD ) ) {
        return gMaxBedAttraction;
    }

    // ... рассчитать ср.DD
    const TPrice lTW_DD = -lSumCurrDD / ( aLastPoint - aFirstPoint );

    // Расчитать коэффициент            Это среднеинтервальный заработок
    lResult = lPnLValatility / ( lTW_DD + 1.0 ) * lMidPnl /  ToDouble( lDealCounter ) ; //lPnLValue / lPnLTime ;

    return lResult ;
}

//------------------------------------------------------------------------------------------
TPrice DealsToCoeff(
    const TBarSeries & aBars,
    const TDeals & aDeals,
    const size_t aMinDeals,
    TPrice & aoPnl,
    TPrice & aoMaxDD,
    size_t & aoMaxPos,
    size_t & aoMeadPos ) {
    #ifdef FULLDATA
        std::cout << "aDeals.size() = " << aDeals.size() << std::endl;
    #endif

    if( aDeals.size() <= aMinDeals ) {
        aoPnl = GetBadPrice();
        aoMaxDD = GetBadPrice();
        aoMaxPos = 0;
        aoMeadPos = 0;
        return gMaxBedAttraction;
    }

    TPriceSeries lBalance( aBars.size() );

    for( size_t i=0; i<aBars.size(); ++i ){
        lBalance[i]={aBars[i].DateTime,0.0,0.0};
    }

    auto lTickCompare = []( const TSimpleTick& aLeft, const TSimpleTick& aRigth )->bool {
        return aLeft.DateTime < aRigth.DateTime;
    };

    #ifdef FULLDATA
        std::cout << "\nDeals" << std::endl;
    #endif

    for( auto it=aDeals.begin(); it!=aDeals.end(); ++it ) {

        const TInnerDate lOpenTime = it->OpenTime;
        const TInnerDate lCloseTime = it->CloseTime;
        const TPrice lOpenPrice = it->OpenPrice;
        const TPrice lClosePrice = it->ClosePrice;

        auto itBegin = std::lower_bound (
            lBalance.begin(),
            lBalance.end(),
            TSimpleTick{ lOpenTime,0.0,0.0 },
            lTickCompare
        );
        assert( itBegin != lBalance.end() );

        auto itEnd = std::upper_bound(
            itBegin,
            lBalance.end(),
            TSimpleTick{ lCloseTime,0.0,0.0 },
            lTickCompare
        );

        TPrice lBalanceVolume = 0.0;

        if( it->DealSide == TDealSide::Buy ) {
            for( auto itBalance=itBegin; itBalance != std::prev( itEnd ) ; ++itBalance ){
                itBalance->Volume += 1.0;
                itBalance->Price -= lOpenPrice;
            }
            lBalanceVolume = lClosePrice - lOpenPrice ;

        } else {
            for( auto itBalance=itBegin; itBalance != std::prev( itEnd ) ; ++itBalance ) {
                itBalance->Volume -= 1.0;
                itBalance->Price += lOpenPrice;
            }
            lBalanceVolume = lOpenPrice - lClosePrice;
        }

        for( auto itBalance=std::prev( itEnd ); itBalance != lBalance.end(); ++itBalance ) {
            itBalance->Price += lBalanceVolume;
        }
    }

    if( not isZero( lBalance.rbegin()->Volume ) ) { ///\todo вообще-то нужно учесть сторону для каждой конкретной сделки
        const TPrice lClosePrice = aBars.rbegin()->Close;
        const TPrice lBalancedSide = ( aDeals.rbegin()->DealSide == TDealSide::Buy ) ? 1.0 : -1.0;
        lBalance.rbegin()->Price += lBalancedSide * lBalance.rbegin()->Volume * lClosePrice;
        lBalance.rbegin()->Volume = 0;
    }

    TPrice lMax = 0.0;
    TPrice lMin = 0.0;
    aoPnl = 0.0;
    aoMaxDD = 0.0;

    #ifdef FULLDATA
    std::cout << "\nDD" << std::endl;
    #endif

    std::vector< size_t > lPosition( aBars.size(), 0 );

    for( size_t i=0; i<aBars.size(); ++i ) {
        lPosition[ i ] = ToSize_t( std::fabs( lBalance[i].Volume ) );
        aoPnl = lBalance[i].Price + aBars[i].Close * lBalance[i].Volume;

        if( IsGreat( aoPnl, lMax ) ) { //очередной максимум
            lMax = aoPnl;
            lMin = aoPnl;

        } else if( IsGreat( lMin, aoPnl ) ) { //новый минимум
            lMin = aoPnl;
            const TPrice lCurrentDD = lMax - lMin;
            if( IsGreat( lCurrentDD, aoMaxDD ) ) {
                aoMaxDD = lCurrentDD;
            }
        }
        #ifdef FULLDATA
        std::cout << RoundToSize_t( aBars[i].DateTime ) << " " << aBars[i].Close << " " << lBalance[i].Volume << " " << aoPnl << " " << lMax << " " << lMin << " " << aoMaxDD << std::endl;
        #endif
    }

    std::sort( lPosition.begin(), lPosition.end() );
    aoMaxPos = *lPosition.rbegin();
    aoMeadPos = lPosition[ lPosition.size() / 2 ];

    const TPrice lResult = isZero( std::abs( aoPnl ) + aoMaxDD ) ? gMaxBedAttraction : (aoPnl / ( std::abs( aoPnl ) + aoMaxDD )) ;
    #ifdef FULLDATA
    std::cout << "lResult = " << lResult << std::endl;
    #endif

    return lResult;
}

//------------------------------------------------------------------------------------------
TDoubles ToDoublesArray( const TPriceSeries & aPriceSeries ) {
    const size_t lArraySize = aPriceSeries.size();
    TDoubles lResult( lArraySize );

    for( size_t i=0; i<lArraySize; ++i ) {
        lResult[i]=aPriceSeries[i].Price;
    }

    return lResult;
}

//------------------------------------------------------------------------------------------
TPriceSeries PnlsToDaily( const TPriceSeries & aPnls ) {

    if( aPnls.empty() ) {
        return TPriceSeries();
    }
    TInnerDate lMinDate = gMaxInteger;
    TInnerDate lMaxDate = 0;

    for( const auto &lDeal : aPnls ) {
        const TInnerDate lDealDate = Trunc( lDeal.DateTime / gOneDay );
        lMinDate = IsLess( lMinDate, lDealDate ) ? lMinDate : lDealDate;
        lMaxDate = IsGreat( lMaxDate, lDealDate ) ? lMaxDate : lDealDate;
    }

    TPriceSeries lResult( ToSize_t( lMaxDate-lMinDate+1 ) );
    for( const auto &lDeal : aPnls ) {
        const size_t lDealDate = ToSize_t( Trunc( lDeal.DateTime / gOneDay ) - lMinDate );
        const TPrice lDealPnl = lDeal.Price * ((lDeal.Volume == 0) ? 1.0 : lDeal.Volume );

        TSimpleTick lDayDeal = lResult[ lDealDate ];
        lDayDeal.DateTime = ToDouble( Trunc( lDeal.DateTime / gOneDay ) * gOneDay );
        lDayDeal.Price += lDealPnl;
        lDayDeal.Volume += 1.0;

        lResult[ lDealDate ] = lDayDeal;
    }

    for( size_t i = 1; i < lResult.size(); ++i ){
        lResult[i].Price += lResult[i-1].Price;
    }

    return lResult;
}

//------------------------------------------------------------------------------------------
bool IsGrows( const TPriceSeries & aDailyPnls, const size_t aPeriod, const size_t aTollerance ) {
    if( aPeriod == 0 ){
        throw std::logic_error( "aPeriod can be positive" );
    }

    if( aDailyPnls.size() <= aPeriod ) {
        return false;
    }

    size_t lTollerance = ( aTollerance == 0 ) ? 1 : aTollerance;

    for( size_t i=0; i < aDailyPnls.size() - aPeriod; ++i ) {
        if( IsGreat( aDailyPnls[i].Price, aDailyPnls[ i+aPeriod ].Price ) ) {
            if( --lTollerance == 0 ) {
                return false;
            }
        }
    }

    return true;
}

//------------------------------------------------------------------------------------------
TPriceSeries PnLsAmplifier( const TPriceSeries &aPnl, const std::vector<double> &aAmplifiers, const TInnerDate aBegin, const TInnerDate aEnd ) {

    if( aAmplifiers.size() < 2UL ){
        return aPnl;
    }

    //sort deals
    TPriceSeries lPnls( aPnl );
    std::sort( lPnls.begin(), lPnls.end(),
        []( const TSimpleTick& lh, const TSimpleTick& rh ){
            return IsLess( lh.DateTime, rh.DateTime );
        }
    );

    //days range detect
    const TInnerDate lMinDate =  trunc( (isPositiveValue( aBegin ) ? aBegin : lPnls.begin()->DateTime) / gOneDay );
    const TInnerDate lMaxDate =  trunc( (isPositiveValue( aEnd ) ? aEnd : lPnls.rbegin()->DateTime) / gOneDay );

    if( CeilToSize_t(lMaxDate-lMinDate) < aAmplifiers.size() ){
        return aPnl;
    }

    const double lMinDelta = trunc( (lMaxDate-lMinDate) / ToDouble( aAmplifiers.size() ) );
    if( IsLess( lMinDelta, 1.0 ) ){
        return aPnl;
    }

    std::vector<TInnerDate> ldates( aAmplifiers.size() );
    for( size_t i=0; i<aAmplifiers.size(); ++i ) {
        ldates[i] = lMinDate + lMinDelta * ToDouble(i+1) ;
    }

    TPriceSeries lPnls_result;
    lPnls_result.reserve( ToSize_t(*aAmplifiers.rbegin()) * aPnl.size() );
    for( auto it = lPnls.begin(); it != lPnls.end(); ++it ){
        const TSimpleTick lTick( *it );

        for( size_t i=0; i < ldates.size(); ++i ){
            if( not IsGreat( trunc(lTick.DateTime / gOneDay), ldates[i] ) ){
                for( size_t j=0; j < ToSize_t(aAmplifiers[i]); ++j ){
                    lPnls_result.push_back( lTick );
                }
                break;
            }
        }
    }

    lPnls_result.shrink_to_fit();
    return lPnls_result;
}

//------------------------------------------------------------------------------------------


#include <iostream>

#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/timer.hpp>

#include <vector>
#include <math.h>

// extern "C"{
//   #include <cblas.h>
// }

#define INVALID -1

#include "NRGclasses.hpp"
#include "NRGfunctions.hpp"

void UpdateMatrices_uBLAS(CNRGbasisarray* pSingleSite,
			  CNRGbasisarray* pAeigCut,
			  CNRGbasisarray* pAbasis,
			  CNRGmatrix* NRGMats, int NumNRGMats, bool display){

  // Boost matrices
  boost::numeric::ublas::matrix<double> Zibl;
  boost::numeric::ublas::matrix<double> Zjbl;
  boost::numeric::ublas::matrix<double> fnbasis;

  boost::numeric::ublas::matrix<complex<double> > cZibl;
  boost::numeric::ublas::matrix<complex<double> > cZjbl;
  boost::numeric::ublas::matrix<complex<double> > cfnbasis;

  boost::numeric::ublas::matrix<double> fnw;
  boost::numeric::ublas::matrix<complex<double> > cfnw;

  // Check that eigenvectors are properly normalized!

  boost::numeric::ublas::matrix<complex<double> > cOneTest;

  // Check time
  boost::timer t;
  double time_elapsed;

  boost::timer t2;
  double time_lap2;

  CNRGmatrix* MatOLD = new CNRGmatrix [NumNRGMats];

  //Clear all
  for (int imats=0;imats<NumNRGMats;imats++){
    if (NRGMats[imats].NeedOld) MatOLD[imats]=NRGMats[imats];
    NRGMats[imats].ClearAll();
    NRGMats[imats].SyncNRGarray(*pAeigCut);
  }
  // end for

  // Note: assumes  Abasis and Aeig have
  // EXACTLY the same block structure... should check.

  // Loop over blocks (icount counts for each matrix)
  int icount[NumNRGMats];
  for (int ii=0;ii<NumNRGMats;ii++) icount[ii]=0;

  t2.restart();
  time_lap2=0.0;
  // Note Aeig can be a "cut" array
  for (int ibl=0;ibl<pAeigCut->NumBlocks();ibl++){
    int Nstibl=pAeigCut->GetBlockSize(ibl);

    cout << "Block i : " << ibl << "/" << pAeigCut->NumBlocks()-1
	 << " (sz=" <<  Nstibl << ") Nshell=" << pAeigCut->Nshell << endl;
    // Corresponding block size in pAbasis
    int iblBC=FindMatchBlock(pAeigCut,ibl,pAbasis);
    int NstiblBC=pAbasis->GetBlockSize(iblBC);

    for (int jbl=0;jbl<pAeigCut->NumBlocks();jbl++){
      int Nstjbl=pAeigCut->GetBlockSize(jbl);

      cout << "  Block j : " << jbl << "/" << pAeigCut->NumBlocks()-1
	   << " (sz=" <<  Nstjbl
	   << ") Time to here " << t2.elapsed()
	   << "; last lap " << t2.elapsed()-time_lap2 << endl;
      time_lap2=t2.elapsed();

      int jblBC=FindMatchBlock(pAeigCut,jbl,pAbasis);
      int NstjblBC=pAbasis->GetBlockSize(jblBC);

      //Loop matrices: check if the matrix elements are non-zero
      for (int imats=0;imats<NumNRGMats;imats++){
	if ( NRGMats[imats].CheckForMatEl(pAeigCut,ibl,jbl)   ){
	  cout << "   Updating Op imats=: " << imats << " of " << NumNRGMats-1 << endl;
	  // Set Zi,Zj
	  if (display){
	    cout << " Nshell = " << pAeigCut->Nshell << endl;
	    //cout << " Updating Op imats=: " << imats << " of " << NumNRGMats-1 << endl;
	    cout << " icount = " << icount[imats] << " to "
		 << icount[imats]+Nstibl*Nstjbl-1 << endl;
	    cout << "  Setting up Block i : " << ibl
		 << " (size " <<  Nstibl
		 << "  was bl " << iblBC << " sz " << NstiblBC
		 << ") x Block j " << jbl
		 << " (size " << Nstjbl
		 << "  was bl " << jblBC << " sz " << NstjblBC
		 << ") of " << pAeigCut->NumBlocks() << endl;
	  }
	  NRGMats[imats].MatBlockMap.push_back(ibl);
	  NRGMats[imats].MatBlockMap.push_back(jbl);
	  NRGMats[imats].MatBlockBegEnd.push_back(icount[imats]);
	  icount[imats]+=Nstibl*Nstjbl;
	  NRGMats[imats].MatBlockBegEnd.push_back(icount[imats]-1);

	  // 		  cout << "    Setting up BLAS matrices..." << endl;
	  //boost::numeric::ublas::matrix<double> Zibl(Nstibl,Nstibl);
	  //boost::numeric::ublas::matrix<double> Zjbl(Nstjbl,Nstjbl);
	  if (NRGMats[imats].IsComplex){
	    cZibl.resize(Nstibl,NstiblBC);
	    cZjbl.resize(Nstjbl,NstjblBC);
	    cZibl=pAeigCut->cEigVecCut2BLAS(ibl);
	    cZjbl=pAeigCut->cEigVecCut2BLAS(jbl);
	    cfnbasis.resize(NstiblBC,NstjblBC);
	  }else{
	    Zibl.resize(Nstibl,NstiblBC);
	    Zjbl.resize(Nstjbl,NstjblBC);
	    Zibl=pAeigCut->EigVecCut2BLAS(ibl);
	    Zjbl=pAeigCut->EigVecCut2BLAS(jbl);
	    fnbasis.resize(NstiblBC,NstjblBC);
	  }

	  if (display) cout << "    ...Zs done ..." << endl;

// 	  fnbasis.resize(NstiblBC,NstjblBC);
	  // Set-up fn basis: Loop in the basis states
	  int istbl=0;
	  for (int ist=pAbasis->GetBlockLimit(iblBC,0);
	       ist<=pAbasis->GetBlockLimit(iblBC,1);ist++){
	    //int typei=pAbasis->iType[ist];
	    //int stcfi=pAbasis->StCameFrom[ist];

	    int jstbl=0;
	    for (int jst=pAbasis->GetBlockLimit(jblBC,0);
		 jst<=pAbasis->GetBlockLimit(jblBC,1);jst++){
	      //int typej=pAbasis->iType[jst];
	      //int stcfj=pAbasis->StCameFrom[jst];

	      if (NRGMats[imats].IsComplex)
		cfnbasis(istbl,jstbl)=ZeroC;
	      else fnbasis(istbl,jstbl)=0.0;

	      //Calculate fbasis
	      if (NRGMats[imats].NeedOld){
		int iold=pAbasis->StCameFrom[ist];
		int jold=pAbasis->StCameFrom[jst];
		if (NRGMats[imats].IsComplex){
		  cfnbasis(istbl,jstbl)=(NRGMats[imats].CalcMatElCplx(pAbasis,pSingleSite,ist,jst))*MatOLD[imats].cGetMatEl(iold,jold);
		}
		  else
		fnbasis(istbl,jstbl)=(NRGMats[imats].CalcMatEl(pAbasis,pSingleSite,ist,jst))*MatOLD[imats].GetMatEl(iold,jold);
	      }
	      else{
		if (NRGMats[imats].IsComplex)
		  cfnbasis(istbl,jstbl)=NRGMats[imats].CalcMatElCplx(pAbasis,pSingleSite,ist,jst);

		else
		  fnbasis(istbl,jstbl)=NRGMats[imats].CalcMatEl(pAbasis,pSingleSite,ist,jst);
	      }
	      jstbl++;
	    }
	    // end jst loop
	    istbl++;
	  }
	  // end ist loop
	  if (display) cout << "    ...fnbasis done." << endl;

	  if (display) cout << "    Multiplying BLAS matrices (final size:"
			    << Nstibl << " x " << Nstjbl << ") ... " << endl;

	  t.restart();
	  if (NRGMats[imats].IsComplex){
	    cfnw.resize(Nstibl,Nstjbl);
// 	    noalias(cfnw)=prod (cZibl,
// 		     boost::numeric::ublas::matrix<complex<double> >(prod(cfnbasis,trans(cZjbl))) );
	    // Wrong. It should be:
	    noalias(cfnw)=prod (cZibl,
		     boost::numeric::ublas::matrix<complex<double> >(prod(cfnbasis,herm(cZjbl))) );

	  }
	  else{
	    fnw.resize(Nstibl,Nstjbl);
	    noalias(fnw)=prod (Zibl,
		     boost::numeric::ublas::matrix<double>(prod(fnbasis,trans(Zjbl))) );
	  }
	  time_elapsed=t.elapsed();
	  if (display) cout << "    ...done. Elapsed time:" << time_elapsed << endl;
	  // Debug
	  // if ( (display)&&(pAeigCut->Nshell==1)&&
	  //      ( (ibl==1)||(jbl==1) )&&
	  //      ( (imats==1) )
	  //      ) {
	  //   cout << "Z(i="<<ibl<<")  : " <<  Zibl << endl;
	  //   cout << "Z(j="<<jbl<<")  : " <<  Zjbl << endl;
	  //   cout << "fbasis   :" <<  fnbasis << endl;
	  //   cout << "Zi.fbasis.ZjT : " <<  fnw << endl;
	  //   ///
	  //   // cout << "cZ(i="<<ibl<<")  : " <<  cZibl << endl;
	  //   // cout << "cZ(j="<<jbl<<")  : " <<  cZjbl << endl;

	  //   // cOneTest.resize(NstiblBC,NstiblBC);
	  //   // noalias(cOneTest)=prod (herm(cZibl), cZibl);

	  //   // cout << "cZi+.cZi   : " <<  cOneTest << endl;

	  //   // cOneTest.resize(NstjblBC,NstjblBC);
	  //   // noalias(cOneTest)=prod (herm(cZjbl), cZjbl);

	  //   // cout << "cZj+.cZj   : " <<  cOneTest << endl;

	  //   // cout << "cfbasis   :" <<  cfnbasis << endl;
	  //   // cout << "cZi.cfbasis.cZjT : " <<  cfnw << endl;
	  //   //cout << "faux   :" <<  faux << endl;
	  //  }
	  // end debug

	  // Add to NRGMats[imats-1]
	  if (NRGMats[imats].IsComplex){
	    for (int ii=0;ii<cfnw.size1();ii++)
	      for (int jj=0;jj<cfnw.size2();jj++)
		NRGMats[imats].MatElCplx.push_back(cfnw(ii,jj));
	  }
	  else{
	    for (int ii=0;ii<fnw.size1();ii++)
	      for (int jj=0;jj<fnw.size2();jj++)
		NRGMats[imats].MatEl.push_back(fnw(ii,jj));
	  }
	  // end if is complex
	}
	//end if Q=Q'+1 etc

      }
      // end imats loop

    }
    // end jbl loop

  }
  // end ibl loop

  // Release MatOLD
  delete[] MatOLD;

  cout << " DONE updating operators in Nshell = " << pAeigCut->Nshell << endl;

}
// END subroutine

//////////////////////////////////
//////////////////////////////////
//////////////////////////////////
//////////////////////////////////

void UpdateMatrices(CNRGbasisarray* pSingleSite,CNRGbasisarray* pAeigCut,
		    CNRGbasisarray* pAbasis,
		    CNRGmatrix* NRGMats, int NumNRGMats, bool display){

  // STL vectors
  vector<double> fnbasis;
  vector<complex<double> > cfnbasis;

  vector<double> fnw;
  vector<complex<double> > cfnw;

  vector<double> auxMat1;
  vector<complex<double> > cauxMat1;

  double auxEl;
  complex<double> cauxEl;

  int ibegZibl,ibegZjbl; // Point where the blocks begin

  int ldaBC,lda,ldaaux; //=max(SizeBl,SizeBl2);

  // cblas_dgemm variables
  double ALPHA=1.0, BETA=0.0;
  //   C <- ALPHA*A.B + BETA*C

  //typedef CBLAS_ORDER CBLAS_LAYOUT; // this for backward compatibility

  // Check that eigenvectors are properly normalized!

  //boost::numeric::ublas::matrix<complex<double> > cOneTest;

  // Check time
  boost::timer t;
  double time_elapsed;

  boost::timer t2;
  double time_lap2;

  CNRGmatrix* MatOLD = new CNRGmatrix [NumNRGMats];

  //Clear all
  for (int imats=0;imats<NumNRGMats;imats++){
    if (NRGMats[imats].NeedOld) MatOLD[imats]=NRGMats[imats];
    NRGMats[imats].ClearAll();
    NRGMats[imats].SyncNRGarray(*pAeigCut);
  }
  // end for

  // Note: assumes  Abasis and Aeig have
  // EXACTLY the same block structure... should check.

  // Loop over blocks (icount counts for each matrix)
  int icount[NumNRGMats];
  for (int ii=0;ii<NumNRGMats;ii++) icount[ii]=0;

  t2.restart();
  time_lap2=0.0;
  // Note Aeig can be a "cut" array
  for (int ibl=0;ibl<pAeigCut->NumBlocks();ibl++){
    int Nstibl=pAeigCut->GetBlockSize(ibl);

    cout << "Block i : " << ibl << "/" << pAeigCut->NumBlocks()-1
	 << " (sz=" <<  Nstibl << ") Nshell=" << pAeigCut->Nshell << endl;
    // Corresponding block size in pAbasis
    int iblBC=FindMatchBlock(pAeigCut,ibl,pAbasis);
    int NstiblBC=pAbasis->GetBlockSize(iblBC);

    for (int jbl=0;jbl<pAeigCut->NumBlocks();jbl++){
      int Nstjbl=pAeigCut->GetBlockSize(jbl);

      cout << "  Block j : " << jbl << "/" << pAeigCut->NumBlocks()-1
	   << " (sz=" <<  Nstjbl
	   << ") Time to here " << t2.elapsed()
	   << "; last lap " << t2.elapsed()-time_lap2 << endl;
      time_lap2=t2.elapsed();

      int jblBC=FindMatchBlock(pAeigCut,jbl,pAbasis);
      int NstjblBC=pAbasis->GetBlockSize(jblBC);

      //Loop matrices: check if the matrix elements are non-zero
      for (int imats=0;imats<NumNRGMats;imats++){
	if ( NRGMats[imats].CheckForMatEl(pAeigCut,ibl,jbl)   ){
	  cout << "   Updating Op imats=: " << imats << " of " << NumNRGMats-1 << endl;
	  // Set Zi,Zj
	  if (display){
	    cout << " Nshell = " << pAeigCut->Nshell << endl;
	    //cout << " Updating Op imats=: " << imats << " of " << NumNRGMats-1 << endl;
	    cout << " icount = " << icount[imats] << " to "
		 << icount[imats]+Nstibl*Nstjbl-1 << endl;
	    cout << "  Setting up Block i : " << ibl
		 << " (size " <<  Nstibl
		 << "  was bl " << iblBC << " sz " << NstiblBC
		 << ") x Block j " << jbl
		 << " (size " << Nstjbl
		 << "  was bl " << jblBC << " sz " << NstjblBC
		 << ") of " << pAeigCut->NumBlocks() << endl;
	  }
	  NRGMats[imats].MatBlockMap.push_back(ibl);
	  NRGMats[imats].MatBlockMap.push_back(jbl);
	  NRGMats[imats].MatBlockBegEnd.push_back(icount[imats]);
	  icount[imats]+=Nstibl*Nstjbl;
	  NRGMats[imats].MatBlockBegEnd.push_back(icount[imats]-1);

	  ibegZibl=pAeigCut->GetBlockLimitEigv(ibl,0);
	  ibegZjbl=pAeigCut->GetBlockLimitEigv(jbl,0);

	  cfnbasis.clear();
	  fnbasis.clear();

	  if (display) cout << "    ...Zs done ..." << endl;

	  // Set-up fn basis: Loop in the basis states
	  int istbl=0;
	  for (int ist=pAbasis->GetBlockLimit(iblBC,0);
	       ist<=pAbasis->GetBlockLimit(iblBC,1);ist++){
	    //int typei=pAbasis->iType[ist];
	    //int stcfi=pAbasis->StCameFrom[ist];

	    int jstbl=0;
	    for (int jst=pAbasis->GetBlockLimit(jblBC,0);
		 jst<=pAbasis->GetBlockLimit(jblBC,1);jst++){
	      //int typej=pAbasis->iType[jst];
	      //int stcfj=pAbasis->StCameFrom[jst];

	      //Calculate fbasis
	      if (NRGMats[imats].NeedOld){
		int iold=pAbasis->StCameFrom[ist];
		int jold=pAbasis->StCameFrom[jst];
		if (NRGMats[imats].IsComplex){
		  cauxEl=(NRGMats[imats].CalcMatElCplx(pAbasis,pSingleSite,ist,jst))*MatOLD[imats].cGetMatEl(iold,jold);
		  cfnbasis.push_back(cauxEl);
		}
		else{
		  auxEl=(NRGMats[imats].CalcMatEl(pAbasis,pSingleSite,ist,jst))*MatOLD[imats].GetMatEl(iold,jold);
		  fnbasis.push_back(auxEl);
		}
	      }
	      else{
		if (NRGMats[imats].IsComplex){
		  cauxEl=NRGMats[imats].CalcMatElCplx(pAbasis,pSingleSite,ist,jst);
		  cfnbasis.push_back(cauxEl);
		}
		else{
		  auxEl=NRGMats[imats].CalcMatEl(pAbasis,pSingleSite,ist,jst);
		  fnbasis.push_back(auxEl);
		}
	      }// end if NeedOld else
	      jstbl++;
	    }
	    // end jst loop
	    istbl++;
	  }
	  // end ist loop
	  if (display) cout << "    ...fnbasis done." << endl;

	  if (display) cout << "    Multiplying BLAS matrices (final size:"
			    << Nstibl << " x " << Nstjbl << ") ... " << endl;

	  t.restart();

	  // Let's see:
	  // LDA-> No. of cols of the ORIGINAL matrix
	  //
	  //   Zibl -> (Nstibl x NstiblBC) matrix -> LDA=NstiblBC
	  //   Zjbl -> (Nstjbl x NstjblBC) matrix -> LDA=NstjblBC
	  //   fnbasis -> (NstiblBC x NstjblBC) matrix -> LDA=NstjblBC
	  //   fnw -> (Nstibl x Nstjbl) matrix    -> LDA=Nstjbl
	  // auxMat1 -> (NstiblBC x Nstjbl) matrix -> LDA=Nstjbl
	  // auxMat1 = cfnbasis_(NstiblBC x NstjblBC) . herm(cZjbl)_(NstjblBC x Nstjbl)
	  //
	  //  -> (NstiblBC x Nstjbl) matrix
	  // cfnw = cZibl_(Nstibl x NstiblBC) . auxMat1_(NstiblBC x Nstjbl)

	  if (NRGMats[imats].IsComplex){

	    cauxMat1.clear();
	    cauxMat1.resize(NstiblBC*Nstjbl,ZeroC);

	    // fnbasis_NstiblBC x NstjblBC) x [Zjbl_(Nstjbl x NstjblBC)]^+
	    // -> cMat1_(NstiblBC x Nstjbl)
	    cblas_zgemm(CblasRowMajor,  CblasNoTrans, CblasConjTrans,
			NstiblBC, Nstjbl, NstjblBC,
			&ALPHA, &cfnbasis[0], NstjblBC, &(pAeigCut->cEigVec[ibegZjbl]), NstjblBC,
			&BETA, &cauxMat1[0], Nstjbl);

	    cfnw.clear();
	    cfnw.resize(Nstibl*Nstjbl,ZeroC);

	    //  Zibl_(Nstibl x NstiblBC) x cMat1_(NstiblBC x Nstjbl)
	    //  -> fnw_(Nstibl x Nstjbl)
	    cblas_zgemm(CblasRowMajor,  CblasNoTrans, CblasNoTrans,
			Nstibl, Nstjbl, NstiblBC,
			&ALPHA, &(pAeigCut->cEigVec[ibegZibl]), NstiblBC,
			&cauxMat1[0], Nstjbl, &BETA, &cfnw[0], Nstjbl );

	  }else{

	    auxMat1.clear();
	    auxMat1.resize(NstiblBC*Nstjbl,0.0);

	    // fnbasis_NstiblBC x NstjblBC) x [Zjbl_(Nstjbl x NstjblBC)]^+
	    // -> cMat1_(NstiblBC x Nstjbl)
	    cblas_dgemm(CblasRowMajor,  CblasNoTrans, CblasConjTrans,
			NstiblBC, Nstjbl, NstjblBC,
			ALPHA, &fnbasis[0], NstjblBC, &(pAeigCut->dEigVec[ibegZjbl]), NstjblBC,
			BETA, &auxMat1[0], Nstjbl);

	    fnw.clear();
	    fnw.resize(Nstibl*Nstjbl,0.0);

	    //  Zibl_(Nstibl x NstiblBC) x auxMat1_(NstiblBC x Nstjbl)
	    //  -> fnw_(Nstibl x Nstjbl)
	    cblas_dgemm(CblasRowMajor,  CblasNoTrans, CblasNoTrans,
			Nstibl, Nstjbl, NstiblBC,
			ALPHA, &(pAeigCut->dEigVec[ibegZibl]), NstiblBC,
			&auxMat1[0], Nstjbl, BETA, &fnw[0], Nstjbl );

	  }
	  time_elapsed=t.elapsed();
	  if (display) cout << "    ...done. Elapsed time:" << time_elapsed << endl;
	  // Debug
	  // if ( (display)&&(pAeigCut->Nshell==1)&&
	  //      ( (ibl==1)||(jbl==1) )&&
	  //      ( (imats==1) )
	  //      ) {
	  //   cout << " ibl= " << ibl
	  // 	 << " jbl= " << jbl
	  // 	 << endl;
	  //   cout << " Nstibl=" << Nstibl
	  // 	 << " NstiblBC=" << NstiblBC
	  // 	 << " Nstjbl=" << Nstjbl
	  // 	 << " NstjblBC=" << NstjblBC
	  // 	 << endl;
	  //   cout << " fnbasis = ";
	  //   for (int ii=0; ii<fnbasis.size(); ii++){
	  //     if ( ii % NstjblBC ==0) cout << endl;
	  //     cout << fnbasis[ii] << " ";
	  //   }
	  //   cout<< endl;
	  //   cout << " fnw = ";
	  //   for (int ii=0; ii<fnw.size(); ii++){
	  //     if ( ii % Nstjbl ==0) cout << endl;
	  //     cout << fnw[ii] << " ";
	  //   }
	  //   cout<< endl;

	    // cout << " cfnbasis = ";
	    // for (int ii=0; ii<cfnbasis.size(); ii++){
	    //   if ( ii % NstjblBC ==0) cout << endl;
	    //   cout << cfnbasis[ii] << " ";
	    // }
	    // cout<< endl;
	    // cout << " cfnw = ";
	    // for (int ii=0; ii<cfnw.size(); ii++){
	    //   if ( ii % Nstjbl ==0) cout << endl;
	    //   cout << cfnw[ii] << " ";
	    // }
	    // cout<< endl;

	  // }
	// end debug

	  // Add to NRGMats[imats-1]
	  if (NRGMats[imats].IsComplex){
	    for (int ii=0;ii<cfnw.size();ii++)
	      NRGMats[imats].MatElCplx.push_back(cfnw[ii]);
	  }
	  else{
	    for (int ii=0;ii<fnw.size();ii++)
	      NRGMats[imats].MatEl.push_back(fnw[ii]);
	  }
	  // end if is complex
	}
	//end if Q=Q'+1 etc

      }
      // end imats loop

    }
    // end jbl loop
  }
  // end ibl loop

  // Release MatOLD
  delete[] MatOLD;

  cout << " DONE updating operators in Nshell = " << pAeigCut->Nshell << endl;

}
// END subroutine

//////////////////////////////////
//////////////////////////////////
//////////////////////////////////