/******************************************************************************
 * Copyright © 2014-2017 The SuperNET Developers.                             *
 *                                                                            *
 * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at                  *
 * the top-level directory of this distribution for the individual copyright  *
 * holder information and the developer policies on copyright and licensing.  *
 *                                                                            *
 * Unless otherwise agreed in a custom licensing agreement, no part of the    *
 * SuperNET software, including this file may be copied, modified, propagated *
 * or distributed except according to the terms contained in the LICENSE file *
 *                                                                            *
 * Removal or modification of this copyright notice is prohibited.            *
 *                                                                            *
 ******************************************************************************/

// included from basilisk.c
/*
 In order to provide liquidity from central exchanges, we need to issue balancing trades, however to do this properly, we need to know what the desired balance is. If unspecified, then a neutral balance is assumed.
 The liquidity_info interface is quite flexible, there is a single function liquidity_active() which returns non-zero if the LP node should respond. The model is that the liquidity_command() is used to configure the liquidity_active()'s response.
 In order for dynamic adaptiveness to work, the liquidity_command/liquidity_active needs to interact with the balancing.
 A simplistic default trio of functions are provided, but any level of complexity is possible with the liquidity interface.
 */

#define TRADEBOTS_NUMANSWERS 8
#define TRADEBOTS_NUMDECAYS 8
#define TRADEBOTS_RAWFEATURESINCR 7
#define TRADEBOTS_MAXPAIRS 1024

#define _OCAS_PLUS_INF (-log(0.0))
double OCAS_PLUS_INF,OCAS_NEG_INF;

double Tradebots_decays[TRADEBOTS_NUMDECAYS] = { 0.5, 0.666, 0.8, 0.9, 0.95, 0.99, 0.995, 0.999 };
int32_t Tradebots_answergaps[TRADEBOTS_NUMANSWERS] = { 60, 60, 120, 120, 240, 240, 720, 720 };

struct tradebot_arbentry
{
    char exchange[16];
    double price,volume,profitmargin;
    uint32_t timestamp;
};

struct tradebot_arbexchange
{
    char name[16];
    struct tradebot_arbentry trades[2];
};

struct tradebot_arbpair
{
    char base[32],rel[32];
    uint32_t lasttime,lastanswertime; FILE *fp;
    int32_t numexchanges,counter,btccounter,usdcounter,cnycounter,refc;
    double highbid,lowask,hblavolume,btcbid,btcask,btcvol,usdbid,usdask,usdvol,cnybid,cnyask,cnyvol;
    double bidaves[TRADEBOTS_NUMDECAYS],askaves[TRADEBOTS_NUMDECAYS];
    double bidslopes[TRADEBOTS_NUMDECAYS],askslopes[TRADEBOTS_NUMDECAYS];
    struct tradebot_arbexchange exchanges[16];
    uint8_t dirmasks[2],slopedirs[2];
    char *svmpairs[TRADEBOTS_MAXPAIRS][2];
    int32_t RTgood[TRADEBOTS_NUMANSWERS],RTbad[TRADEBOTS_NUMANSWERS],numrawfeatures,numsvmfeatures,numpairs;
    float rawfeatures[TRADEBOTS_NUMANSWERS+64],prevrawfeatures[60 * TRADEBOTS_NUMANSWERS+64],*svms; // svms is coeffs vector[TRADEBOTS_NUMANSWERS]
    float RTpreds[TRADEBOTS_NUMANSWERS],svmpreds[TRADEBOTS_NUMANSWERS],answers[TRADEBOTS_NUMANSWERS];
};
struct tradebot_arbpair Arbpairs[TRADEBOTS_MAXPAIRS],*Pair_NXTBTC,*Pair_BTCUSD,*Pair_BTCCNY;
int32_t Tradebot_numarbpairs;

struct tradebot_arbpair *tradebots_arbpair_find(char *base,char *rel)
{
    int32_t i;
    for (i=0; i<Tradebot_numarbpairs; i++)
        if ( strcmp(Arbpairs[i].base,base) == 0 && strcmp(Arbpairs[i].rel,rel) == 0 )
            return(&Arbpairs[i]);
    return(0);
}

int32_t tradebots_calcrawfeatures(struct tradebot_arbpair *pair)
{
    int32_t starti,i,n = 0; double ave; uint32_t timestamp;
    n = TRADEBOTS_NUMANSWERS;
    for (i=59; i>0; i--)
        memcpy(&pair->prevrawfeatures[i*72],&pair->prevrawfeatures[(i-1)*72],sizeof(pair->rawfeatures));
    memcpy(pair->prevrawfeatures,pair->rawfeatures,sizeof(pair->rawfeatures));
    memset(pair->rawfeatures,0,sizeof(pair->rawfeatures));
    if ( fabs(pair->highbid) < SMALLVAL || fabs(pair->lowask) < SMALLVAL )
        return(-1);
    ave = _pairaved(pair->highbid,pair->lowask);
    timestamp = (uint32_t)time(NULL);
    n = TRADEBOTS_NUMANSWERS;
    memcpy(&pair->rawfeatures[TRADEBOTS_NUMANSWERS],&timestamp,sizeof(*pair->rawfeatures)), n++;
    pair->rawfeatures[n++] = pair->highbid;
    pair->rawfeatures[n++] = pair->lowask;
    pair->rawfeatures[n++] = pair->hblavolume / ave;
    n = TRADEBOTS_RAWFEATURESINCR + TRADEBOTS_NUMANSWERS;
    for (i=0; i<TRADEBOTS_NUMDECAYS; i++)
    {
        if ( fabs(pair->bidaves[i]) < SMALLVAL || fabs(pair->askaves[i]) < SMALLVAL )
            return(-1);
        starti = n;
        pair->rawfeatures[n++] = (pair->bidaves[i] / ave) - 1.;
        pair->rawfeatures[n++] = (pair->askaves[i] / ave) - 1.;
        pair->rawfeatures[n++] = 10000. * (pair->bidslopes[i] / ave);
        pair->rawfeatures[n++] = 10000. * (pair->askslopes[i] / ave);
        if ( n < starti+TRADEBOTS_RAWFEATURESINCR )
            n = starti+TRADEBOTS_RAWFEATURESINCR;
    }
    if ( pair->fp != 0 )
    {
        if ( fwrite(pair->rawfeatures,1,sizeof(pair->rawfeatures),pair->fp) != sizeof(pair->rawfeatures) )
            printf("fwrite error for %s/%s rawfeatures[%d]\n",pair->base,pair->rel,n);
        else fflush(pair->fp);
    }
    if ( n > sizeof(pair->rawfeatures)/sizeof(*pair->rawfeatures) )
    {
        printf("n.%d too many for rawfeatures %d\n",n,(int32_t)(sizeof(pair->rawfeatures)/sizeof(*pair->rawfeatures)));
        exit(-1);
    }
    return(n);
}

uint32_t tradebots_featureset(double *highbidp,double *lowaskp,double *avep,double *volp,double *bidaves,double *askaves,double *bidslopes,double *askslopes,float *rawfeatures)
{
    uint32_t timestamp; int32_t i,n,starti;
    memcpy(&timestamp,&rawfeatures[TRADEBOTS_NUMANSWERS],sizeof(timestamp));
    n = TRADEBOTS_NUMANSWERS + 1;
    *highbidp = rawfeatures[n++];
    *lowaskp = rawfeatures[n++];
    *avep = _pairaved(*highbidp,*lowaskp);
    *volp = rawfeatures[n++];
    //printf("[%9.6f %9.6f] vol %f t.%u\n",*highbidp,*lowaskp,*volp,timestamp);
    n = TRADEBOTS_RAWFEATURESINCR + TRADEBOTS_NUMANSWERS;
    for (i=0; i<TRADEBOTS_NUMDECAYS; i++)
    {
        starti = n;
        bidaves[i] = rawfeatures[n++];
        askaves[i] = rawfeatures[n++];
        bidslopes[i] = rawfeatures[n++];
        askslopes[i] = rawfeatures[n++];
        if ( n < starti+TRADEBOTS_RAWFEATURESINCR )
            n = starti+TRADEBOTS_RAWFEATURESINCR;
    }
    return(timestamp);
}

struct tradebot_arbpair *tradebots_arbpair_create(char *base,char *rel)
{
    struct tradebot_arbpair *pair; char fname[1024]; double ave;
    if ( Tradebot_numarbpairs < sizeof(Arbpairs)/sizeof(*Arbpairs) )
    {
        printf("new pair.%d (%s/%s)\n",Tradebot_numarbpairs,base,rel);
        pair = &Arbpairs[Tradebot_numarbpairs];
        pair->refc = Tradebot_numarbpairs++;
        strcpy(pair->rel,rel);
        strcpy(pair->base,base);
        if ( strcmp(base,"NXT") == 0 && strcmp(rel,"BTC") == 0 )
            Pair_NXTBTC = pair, printf("Pair_NXTBTC <- %p\n",pair);
        else if ( strcmp(base,"BTC") == 0 && strcmp(rel,"USD") == 0 )
            Pair_BTCUSD = pair;
        else if ( strcmp(base,"BTC") == 0 && strcmp(rel,"CNY") == 0 )
            Pair_BTCCNY = pair;
        sprintf(fname,"SVM/rawfeatures/%s_%s",base,rel);
        pair->fp = OS_appendfile(fname);
        if ( (ftell(pair->fp) % sizeof(pair->rawfeatures)) != 0 )
        {
            printf("misalinged rawfeatures %d %d\n",(uint32_t)ftell(pair->fp),(uint32_t)(ftell(pair->fp) % sizeof(pair->rawfeatures)));
        }
        fseek(pair->fp,(ftell(pair->fp) / sizeof(pair->rawfeatures)) * sizeof(pair->rawfeatures) - sizeof(pair->rawfeatures),SEEK_SET);
        if ( fread(pair->rawfeatures,1,sizeof(pair->rawfeatures),pair->fp) == sizeof(pair->rawfeatures) )
        {
            pair->lasttime = tradebots_featureset(&pair->highbid,&pair->lowask,&ave,&pair->hblavolume,pair->bidaves,pair->askaves,pair->bidslopes,pair->askslopes,pair->rawfeatures);
            printf("%s/%s [%.8f %.8f] %u\n",pair->base,pair->rel,pair->highbid,pair->lowask,pair->lasttime);
        }
        return(pair);
    } else return(0);
}

int32_t tradebots_expandrawfeatures(double *svmfeatures,float *rawfeatures,uint32_t reftimestamp,float *refrawfeatures)
{
    double factor,highbid,lowask,ave,vol,bidaves[TRADEBOTS_NUMDECAYS],askaves[TRADEBOTS_NUMDECAYS],bidslopes[TRADEBOTS_NUMDECAYS],askslopes[TRADEBOTS_NUMDECAYS];
    double refhighbid,reflowask,refave,refvol,refbidaves[TRADEBOTS_NUMDECAYS],refaskaves[TRADEBOTS_NUMDECAYS],refbidslopes[TRADEBOTS_NUMDECAYS],refaskslopes[TRADEBOTS_NUMDECAYS];
    uint32_t timestamp; int32_t i,j,starti,n = 0;
    tradebots_featureset(&refhighbid,&reflowask,&refave,&refvol,refbidaves,refaskaves,refbidslopes,refaskslopes,refrawfeatures);
    timestamp = tradebots_featureset(&highbid,&lowask,&ave,&vol,bidaves,askaves,bidslopes,askslopes,rawfeatures);
    if ( timestamp == 0 || reftimestamp == 0 || timestamp >= reftimestamp+60 )
    {
        //printf("tradebots_expandrawfeatures: timestamp.%u vs reftimestamp.%u\n",timestamp,reftimestamp);
        return(-1);
    }
    factor = sqrt(reftimestamp - timestamp);
    if ( factor > 60. )
        factor = 60.;
    else if ( factor < 1. )
        factor = 1.;
    factor = 1. / factor;
    if ( refhighbid == 0. || highbid == 0. || lowask == 0. || reflowask == 0. )
    {
        //printf("tradebots_expandrawfeatures: (%f %f) ref (%f %f)\n",highbid,lowask,refhighbid,reflowask);
        return(-1);
    }
    svmfeatures[n++] = highbid;
    svmfeatures[n++] = (highbid / ave) - 1.;
    svmfeatures[n++] = lowask;
    svmfeatures[n++] = (lowask / ave) - 1.;
    svmfeatures[n++] = (lowask - highbid);
    svmfeatures[n++] = (lowask - highbid) / ave;
    svmfeatures[n++] = vol;
    starti = n;
    svmfeatures[n++] = refhighbid;
    svmfeatures[n++] = (refhighbid / refave) - 1.;
    svmfeatures[n++] = reflowask;
    svmfeatures[n++] = (reflowask / refave) - 1.;
    svmfeatures[n++] = (reflowask - refhighbid);
    svmfeatures[n++] = (reflowask - refhighbid) / refave;
    svmfeatures[n++] = refvol;
    for (i=0; i<starti; i++)
        svmfeatures[n++] = (svmfeatures[i] - svmfeatures[i+starti]);
    for (i=0; i<TRADEBOTS_NUMDECAYS; i++)
    {
        svmfeatures[n++] = bidaves[i];
        svmfeatures[n++] = askaves[i];
        svmfeatures[n++] = bidslopes[i];
        svmfeatures[n++] = askslopes[i];
        
        svmfeatures[n++] = bidaves[i] - refbidaves[i];
        svmfeatures[n++] = bidaves[i] - refaskaves[i];
        
        svmfeatures[n++] = (askaves[i] - bidaves[i]);
        svmfeatures[n++] = askaves[i] - refaskaves[i];
        svmfeatures[n++] = askaves[i] - refbidaves[i];
        
        svmfeatures[n++] = bidslopes[i] - refbidslopes[i];
        svmfeatures[n++] = bidslopes[i] - refaskslopes[i];
      
        svmfeatures[n++] = (askslopes[i] - bidslopes[i]);
        svmfeatures[n++] = askslopes[i] - refaskslopes[i];
        svmfeatures[n++] = askslopes[i] - refbidslopes[i];
        
        svmfeatures[n++] = (askaves[i] - bidaves[i]) - (refaskaves[i] - refbidaves[i]);
        svmfeatures[n++] = (askslopes[i] - bidslopes[i]) - (refaskslopes[i] - refbidslopes[i]);
        for (j=i+1; j<TRADEBOTS_NUMDECAYS; j++)
        {
            svmfeatures[n++] = (bidaves[i] - bidaves[j]);
            svmfeatures[n++] = (askaves[i] - askaves[j]);
            svmfeatures[n++] = (askaves[i] - bidaves[j]);
            svmfeatures[n++] = (bidslopes[i] - bidslopes[j]);
            svmfeatures[n++] = (askslopes[i] - askslopes[j]);
            svmfeatures[n++] = (askslopes[i] - bidslopes[j]);
        }
    }
    //if ( fabs(factor - 1.) > SMALLVAL )
    {
        for (i=starti; i<n; i++)
            if ( svmfeatures[i] != 0.f )
            {
                //svmfeatures[i] *= factor;
                if ( svmfeatures[i] > 0. )
                    svmfeatures[i] = cbrt(svmfeatures[i]);
                else svmfeatures[i] = -cbrt(-svmfeatures[i]);
            }
    }
    return(n);
}

int32_t tradebots_calcsvmfeatures(double *svmfeatures,struct tradebot_arbpair *pair,float *rawfeatures,float *prevrawfeatures)
{
    int32_t i,j,n,numpairfeatures,flag; struct tradebot_arbpair *ptr; uint32_t reftimestamp;
    memcpy(&reftimestamp,&rawfeatures[TRADEBOTS_NUMANSWERS],sizeof(reftimestamp));
    if ( reftimestamp == 0 )
    {
        printf("reftimestamp.%u is illegal\n",reftimestamp);
        return(-1);
    }
    numpairfeatures = n = tradebots_expandrawfeatures(svmfeatures,prevrawfeatures,reftimestamp,rawfeatures);
    if ( n <= 0 )
        return(-1);
    for (i=0; i<60; i++,n+=numpairfeatures)
        tradebots_expandrawfeatures(&svmfeatures[n],&prevrawfeatures[i*72],reftimestamp,rawfeatures);
    if ( 0 && pair->numsvmfeatures != (1+pair->numpairs)*n )
    {
        for (i=0; i<pair->numpairs; i++) // need to do lookups
        {
            flag = -1;
            if ( (ptr= tradebots_arbpair_find(pair->svmpairs[i][0],pair->svmpairs[i][1])) != 0 )
                flag = tradebots_expandrawfeatures(&svmfeatures[n],ptr->rawfeatures,reftimestamp,rawfeatures);
            if ( flag < 0 )
            {
                for (j=0; j<numpairfeatures; j++)
                    svmfeatures[n++] = 0.;
            } else n += flag;
        }
    }
    return(n);
}

int32_t tradebots_calcpreds(float *RTpreds,struct tradebot_arbpair *pair,double *svmfeatures)
{
    int32_t i,j,n=0; double feature,preds[TRADEBOTS_NUMANSWERS];
    memset(preds,0,sizeof(preds));
    if ( pair->svms != 0 )
    {
        for (i=n=0; i<pair->numsvmfeatures; i++)
        {
            feature = svmfeatures[i];
            for (j=0; j<TRADEBOTS_NUMANSWERS; j++)
                preds[j] += feature * pair->svms[n++];
        }
    }
    return(n);
}

void tradebots_calcanswers(struct tradebot_arbpair *pair)
{
    double highbid,lowask,futurebid,futureask,ave,vol,bidaves[TRADEBOTS_NUMDECAYS],askaves[TRADEBOTS_NUMDECAYS],bidslopes[TRADEBOTS_NUMDECAYS],askslopes[TRADEBOTS_NUMDECAYS];
    float rawfeatures[sizeof(pair->rawfeatures)/sizeof(*pair->rawfeatures)],futuremin=0,futuremax=0,minval=0,maxval=0,*hblas = 0;
    uint32_t timestamp,firsttime = 0; long fpos,savepos; int32_t flag,i,iter,j,ind,maxi=0;
    OCAS_PLUS_INF = _OCAS_PLUS_INF; OCAS_NEG_INF = -_OCAS_PLUS_INF;
    if ( pair->fp != 0 )
    {
        for (iter=0; iter<2; iter++)
        {
            rewind(pair->fp);
            fpos = 0;
            while ( fread(rawfeatures,1,sizeof(pair->rawfeatures),pair->fp) == sizeof(pair->rawfeatures) )
            {
                savepos = ftell(pair->fp);
                timestamp = tradebots_featureset(&highbid,&lowask,&ave,&vol,bidaves,askaves,bidslopes,askslopes,rawfeatures);
                //printf("timestamp.%u firsttime.%u\n",timestamp,firsttime);
                if ( timestamp == 0 )
                    continue;
                if ( firsttime == 0 )
                {
                    firsttime = timestamp;
                    maxi = (int32_t)((time(NULL) - firsttime) / 60 + 1);
                    hblas = calloc(maxi,sizeof(*hblas)*2);
                    printf("HBLAS[%d] allocated\n",maxi);
                }
                if ( (i= (timestamp - firsttime)/60) >= 0 && i < maxi )
                {
                    if ( iter == 0 )
                    {
                        if ( hblas[i << 1] == 0 )
                        {
                            hblas[i << 1] = highbid;
                            hblas[(i << 1) + 1] = lowask;
                        }
                        else
                        {
                            _xblend(&hblas[i << 1],highbid,0.5);
                            _xblend(&hblas[(i << 1) + 1],lowask,0.5);
                        }
                    }
                    else
                    {
                        highbid = hblas[i << 1];
                        lowask = hblas[(i << 1) + 1];
                        if ( fabs(highbid) > SMALLVAL && fabs(lowask) > SMALLVAL )
                        {
                            memset(pair->answers,0,sizeof(pair->answers));
                            flag = 0;
                            for (j=0; j<TRADEBOTS_NUMANSWERS; j++)
                            {
                                ind = i + Tradebots_answergaps[j];
                                if ( ind < maxi )
                                {
                                    if ( (j & 1) != 0 )
                                        pair->answers[j] = _pairaved(futuremax,futuremin) - _pairaved(minval,maxval);
                                    else
                                    {
                                        futurebid = hblas[ind << 1];
                                        futureask = hblas[(ind << 1) + 1];
                                        minval = MIN(highbid,lowask);
                                        maxval = MAX(highbid,lowask);
                                        futuremin = MIN(futurebid,futureask);
                                        futuremax = MAX(futurebid,futureask);
                                        if ( futuremin > maxval )
                                        {
                                            if ( futuremax < minval )
                                                printf("%s/%s A%d: highly volatile minmax.(%f %f) -> (%f %f) %d of %d\n",pair->base,pair->rel,j,minval,maxval,futuremin,futuremax,i,maxi);
                                            else
                                            {
                                                pair->answers[j] = (futuremin - maxval);
                                                flag++;
                                            }
                                        }
                                        else if ( futuremax < minval )
                                            pair->answers[j] = (futuremax - minval), flag++;
                                        //printf("i.%d j.%d gap.%d ind.%d answer %9.6f (%f %f) -> (%f %f)\n",i,j,Tradebots_answergaps[j],ind,pair->answers[j],minval,maxval,futuremin,futuremax);
                                    }
                                }
                            }
                            if ( flag != 0 )
                            {
                                fseek(pair->fp,fpos,SEEK_SET);
                                if ( fwrite(pair->answers,1,sizeof(pair->answers),pair->fp) != sizeof(pair->answers) )
                                    printf("error writing answers for %s/%s t%u i.%d of %d\n",pair->base,pair->rel,timestamp,i,maxi);
                                else if ( 0 )
                                {
                                    for (j=0; j<TRADEBOTS_NUMANSWERS; j++)
                                        printf("%9.6f ",pair->answers[j]);
                                    printf("%s/%s answers %d of %d\n",pair->base,pair->rel,i,maxi);
                                }
                                fseek(pair->fp,savepos,SEEK_SET);
                            }
                        }
                    }
                }
                fpos = ftell(pair->fp);
            }
            if ( iter == 0 )
            {
                if ( hblas == 0 )
                    break;
                highbid = hblas[0];
                lowask = hblas[1];
                for (i=1; i<maxi; i++)
                {
                    if ( fabs(hblas[i << 1]) > SMALLVAL && fabs(hblas[(i << 1) + 1]) > SMALLVAL )
                    {
                        highbid = hblas[i << 1];
                        lowask = hblas[(i << 1) + 1];
                    }
                    else
                    {
                        hblas[i << 1] = highbid;
                        hblas[(i << 1) + 1] = lowask;
                    }
                    //printf("%9.6f ",_pairaved(highbid,lowask));
                }
                //printf("maxi.%d\n",maxi);
            }
        }
        if ( hblas != 0 )
            free(hblas);
    }
    if ( pair->fp != 0 && (ftell(pair->fp) % sizeof(pair->rawfeatures)) != 0 )
        printf("ERROR: %s/%s not on feature boundary\n",pair->base,pair->rel);
}

double get_yval(double *answerp,int32_t selector,int32_t ind,int32_t refc,int32_t answerind)
{
    float answer; struct tradebot_arbpair *pair; long savepos;
    pair = &Arbpairs[refc];
    if ( pair->fp != 0 )
    {
        savepos = ftell(pair->fp);
        fseek(pair->fp,ind*sizeof(pair->rawfeatures)+answerind*sizeof(*pair->rawfeatures),SEEK_SET);
        if ( fread(&answer,1,sizeof(answer),pair->fp) != sizeof(answer) )
            answer = 0;
        fseek(pair->fp,savepos,SEEK_SET);
        if ( isnan(answer) != 0 )
            return(0);
        answer /= 10.;
        /*if ( answer > .01 )
            answer = .01;
        else if ( answer < -.01 )
            answer = -.01;
        if ( answerp != 0 )
            *answerp = answer;
       */
        if ( answer > 0. )
            answer = sqrt(answer);
        else answer = -sqrt(-answer);
        *answerp = answer;
        /*if ( answer > 0. )
            return(1.);
        else if ( answer < 0. )
            return(-1.);*/
        return(answer);
    }
	return(0.);
}

float *get_features(int32_t numfeatures,int32_t refc,int32_t ind)
{
    struct tradebot_arbpair *pair; long savepos; int32_t i,n; double svmfeatures[32768];
    float rawfeatures[sizeof(pair->rawfeatures)],prevrawfeatures[60 * sizeof(pair->rawfeatures)],*svmf=0;
    pair = &Arbpairs[refc];
    pair->numsvmfeatures = numfeatures;
    if ( pair->fp != 0 && ind > 61 )
    {
        savepos = ftell(pair->fp);
        fseek(pair->fp,(ind-60)*sizeof(pair->rawfeatures),SEEK_SET);
        if ( fread(prevrawfeatures,60,sizeof(pair->rawfeatures),pair->fp) == sizeof(pair->rawfeatures) && fread(&rawfeatures,1,sizeof(pair->rawfeatures),pair->fp) == sizeof(pair->rawfeatures) )
        {
            n = tradebots_calcsvmfeatures(svmfeatures,pair,rawfeatures,prevrawfeatures);
            //int32_t nonz; for (i=nonz=0; i<n; i++)
            //    if ( svmfeatures[i] != 0 )
            //        nonz++;
            //printf("%d ",nonz);
            //    printf("%9.6f ",rawfeatures[i]);
            //printf("rawfeatures[%d] -> %d\n",ind,n);
            if ( n != pair->numsvmfeatures )
            {
                //printf("unexpected numsvmfeatures refc.%d ind.%d %d vs %d\n",refc,ind,n,pair->numsvmfeatures);
                return(0);
            }
            svmf = calloc(n,sizeof(*svmf));
            for (i=0; i<n; i++)
                svmf[i] = svmfeatures[i];
        }
        fseek(pair->fp,savepos,SEEK_SET);
    }
    return(svmf);
}

/*double set_ocas_model(int refc,int answerind,double *W,double W0,int numfeatures,int firstweekind,int len,int bad,double dist,double predabs,int posA,int negA,double answerabs,double aveanswer)
{
    int32_t i,nonz=0;
    for (i=0; i<numfeatures; i++)
        if ( W[i] != 0. )
            nonz++;//, printf("%.6f ",W[i]);
    printf("model.%d W0 %.7f numfeatures.%d nonz.%d\n",answerind,W0,numfeatures,nonz);
    return(W0);
}*/

void tradebots_modelfname(char *modelname,char *base,char *rel,int32_t answerind,int32_t numfeatures)
{
	sprintf(modelname,"SVM/models/%s_%s_%d.A%d",base,rel,numfeatures,answerind);
    OS_portable_path(modelname);
}

double load_model(register int *posAp,register int *negAp,register double *W,register int refc,register int answerind,register int numfeatures)
{
	int32_t j; FILE *fp; double perc; char modelname[512];
    tradebots_modelfname(modelname,Arbpairs[refc].base,Arbpairs[refc].rel,answerind,numfeatures);
	if ( (fp= fopen(modelname,"rb")) != 0 )
	{
		//printf("load file\n");
		j = (int)fread(W,sizeof(*W),numfeatures+1,fp);
		//fread(&W0,1,sizeof(W0),fp);
		if ( fread(&perc,1,sizeof(perc),fp) == sizeof(perc) )
			j++;
		if ( fread(posAp,1,sizeof(*posAp),fp) == sizeof(*posAp) )
			j++;
		if ( fread(negAp,1,sizeof(*negAp),fp) == sizeof(*negAp) )
			j++;
		fclose(fp);
		//printf("loaded %s bias %9.6f | %5.2f%%\n",modelname,W[numfeatures],perc);
		return(perc);
	}
#ifndef DISABLE_EXISTINGMODEL
	else if ( 0 )
	{
        tradebots_modelfname(modelname,Arbpairs[refc].base,Arbpairs[refc].rel,answerind,numfeatures);
		if ( (fp= fopen(modelname,"rb")) != 0 )
		{
			j = (int)fread(W,sizeof(*W),numfeatures+1,fp);
			if ( fread(&perc,1,sizeof(perc),fp) == sizeof(perc) )
				j++;
			if ( fread(posAp,1,sizeof(*posAp),fp) == sizeof(*posAp) )
				j++;
			if ( fread(negAp,1,sizeof(*negAp),fp) == sizeof(*negAp) )
				j++;
			fclose(fp);
			printf("Using backup model for %s/%s loaded %s bias %9.6f | %5.2f%%\n",Arbpairs[refc].base,Arbpairs[refc].rel,modelname,W[numfeatures],perc);
			return(perc * .9);
		}
	}
#endif
	else printf("couldn't load (%s)\n",modelname);
	return(-1);
}

int save_model(int refc,int answerind,double *W,int numfeatures,double W0,double perc,int posA,int negA)
{
	FILE *fp; char modelname[512];
    tradebots_modelfname(modelname,Arbpairs[refc].base,Arbpairs[refc].rel,answerind,numfeatures);
    printf("save model.(%s)\n",modelname);
	if ( (fp= fopen(modelname,"wb")) != 0 )
	{
		//printf("save %s %.f%% posA.%d negA.%d\n",modelname,perc,posA,negA);
		fwrite(W,sizeof(*W),numfeatures,fp);
		fwrite(&W0,1,sizeof(W0),fp);
		fwrite(&perc,1,sizeof(perc),fp);
		fwrite(&posA,1,sizeof(posA),fp);
		fwrite(&negA,1,sizeof(negA),fp);
		fclose(fp);
		return(0);
	}
	return(-1);
}

double init_model(double *percp,double *W,double *oldW,int c,int answerind,int numfeatures)
{
	int32_t j,posA,negA,nonz=0; double *bestmodel=0;
	memset(oldW,0,sizeof(*oldW)*numfeatures);
	memset(W,0,sizeof(*W)*numfeatures);
    if ( load_model(&posA,&negA,W,c,answerind,numfeatures) > 0 )
    {
        bestmodel = W;
        if ( bestmodel != 0 )
        {
            for (j=0; j<=numfeatures; j++)
            {
                if ( bestmodel[j] != 0 )
                    nonz++;
                oldW[j] = W[j];
            }
            if ( nonz != 0 )
                return(bestmodel[numfeatures]);
        }
	}
	return(0.);
}

double set_ocas_model(int refc,int answerind,double *W,double W0,int numfeatures,int firstweekind,int len,int bad,double dist,double predabs,int posA,int negA,double answerabs,double aveanswer)
{
	double perc = (100. * (double)(len - bad)) / len;
#ifndef DISABLE_EXISTINGMODEL
	int32_t _posA,_negA; double *tmpW,fileperc;
    tmpW = calloc(numfeatures+2,sizeof(*tmpW));
	if ( (fileperc= load_model(&_posA,&_negA,tmpW,refc,answerind,numfeatures)) > perc )
	{
		if ( (_posA+_negA) != 0 && _posA >= posA && _negA >= negA )
		{
			memcpy(W,tmpW,sizeof(*W)*numfeatures);
			printf("%s/%s.A%02d numfeatures.%d posA.%d negA.%d saved model %f is better than %f +A%d -A%d\n",Arbpairs[refc].base,Arbpairs[refc].rel,answerind,numfeatures,_posA,_negA,fileperc,perc,posA,negA);
            W0 = tmpW[numfeatures];
            free(tmpW);
			return(tmpW[numfeatures]);
		}
	}
    free(tmpW);
#endif
	save_model(refc,answerind,W,numfeatures,W0,perc,posA,negA);
	return(W0);
}

#ifndef _WIN
#include "tradebots_SVM.h"
#endif

static char *assetids[][2] =
{
    { "12071612744977229797", "UNITY" },
    { "15344649963748848799", "DEX" },
    { "6883271355794806507", "PANGEA" },
    { "17911762572811467637", "JUMBLR" },
    { "17083334802666450484", "BET" },
    { "13476425053110940554", "CRYPTO" },
    { "6932037131189568014", "HODL" },
    { "3006420581923704757", "SHARK" },
    { "17571711292785902558", "BOTS" },
    { "10524562908394749924", "MGW" },
};

uint64_t NXT_assetidfind(char *base)
{
    int32_t i;
    for (i=0; i<sizeof(assetids)/sizeof(*assetids); i++)
        if ( strcmp(assetids[i][1],base) == 0 )
            return(calc_nxt64bits(assetids[i][0]));
    return(0);
}

char *NXT_assetnamefind(char *base)
{
    int32_t i;
    for (i=0; i<sizeof(assetids)/sizeof(*assetids); i++)
        if ( strcmp(assetids[i][0],base) == 0 )
            return(assetids[i][1]);
    return(0);
}

void tradebot_arbentry(struct tradebot_arbentry *arb,char *exchange,double price,double volume,uint32_t timestamp,double profitmargin)
{
    if ( arb->exchange[0] == 0 )
        strcpy(arb->exchange,exchange);
    if ( strcmp(arb->exchange,exchange) == 0 )
    {
        arb->price = price;
        arb->volume = volume;
        arb->timestamp = timestamp;
        arb->profitmargin = profitmargin;
    } else printf("mismatched arbexchange? (%s vs %s)\n",arb->exchange,exchange);
}

struct tradebot_arbexchange *tradebots_arbexchange_find(struct tradebot_arbpair *pair,char *exchange)
{
    int32_t i;
    if ( pair->numexchanges > sizeof(pair->exchanges)/sizeof(*pair->exchanges) )
    {
        printf("data corruption pair.%p %s %s/%s numexchanges.%d\n",pair,exchange,pair->base,pair->rel,pair->numexchanges);
        return(0);
    }
    for (i=0; i<pair->numexchanges; i++)
        if ( strcmp(pair->exchanges[i].name,exchange) == 0 )
            return(&pair->exchanges[i]);
    return(0);
}

struct tradebot_arbexchange *tradebots_arbexchange_create(struct tradebot_arbpair *pair,char *exchange)
{
    if ( pair->numexchanges < sizeof(pair->exchanges)/sizeof(*pair->exchanges) )
    {
        strcpy(pair->exchanges[pair->numexchanges].name,exchange);
        return(&pair->exchanges[pair->numexchanges++]);
    } else return(0);
}

void tradebot_arbcandidate(struct supernet_info *myinfo,char *exchange,int32_t tradedir,char *base,char *rel,double price,double volume,uint32_t timestamp,double profitmargin)
{
    int32_t i,offset,flag; double highbid,lowask,lastbid,lastask,arbval; uint32_t now;
    struct tradebot_arbentry *bid,*ask; struct tradebot_arbexchange *arbex; struct tradebot_arbpair *pair = 0;
    if ( strcmp(rel,"BTC") != 0 && strcmp(rel,"NXT") != 0 && strcmp(rel,"USD") != 0 && strcmp(rel,"CNY") != 0 )
    {
        printf("reject non-BTC arbcandidate (%s/%s)\n",base,rel);
        return;
    }
    offset = (tradedir > 0) ? 0 : 1;
    if ( (pair= tradebots_arbpair_find(base,rel)) == 0 )
        pair = tradebots_arbpair_create(base,rel);
    if ( pair == 0 )
    {
        printf("cant get pair for %s %s/%s\n",exchange,base,rel);
        return;
    }
    if ( (arbex= tradebots_arbexchange_find(pair,exchange)) == 0 )
        arbex = tradebots_arbexchange_create(pair,exchange);
    if ( arbex != 0 )
    {
        //printf("cand.%d %16s %s %12.6f (%5s/%-5s) at %12.8f profit %.03f\n",pair->numexchanges,exchange,tradedir<0?"ask":"bid",volume,base,rel,price,profitmargin);
        tradebot_arbentry(&arbex->trades[offset],exchange,price,volume,timestamp,profitmargin);
        bid = ask = 0;
        pair->highbid = pair->lowask = highbid = lowask = 0.;
        now = (uint32_t)time(NULL);
        //if ( pair->numexchanges >= 2 )
        {
            for (i=0; i<pair->numexchanges; i++)
            {
                arbex = &pair->exchanges[i];
                if ( arbex->trades[0].price != 0. && (highbid == 0. || arbex->trades[0].price >= highbid) )
                {
                    bid = &arbex->trades[0];
                    if ( now > bid->timestamp+30 )
                        bid->price = 0.;
                    else highbid = bid->price;
                }
                if ( arbex->trades[1].price != 0. && (lowask == 0. || arbex->trades[1].price <= lowask) )
                {
                    ask = &arbex->trades[1];
                    if ( now > ask->timestamp+30 )
                        ask->price = 0.;
                    else lowask = ask->price;
                }
                //printf("%p %s %s %f %f -> %p %p %f %f (%f %f)\n",pair,pair->base,arbex->name,arbex->trades[0].price,arbex->trades[1].price,bid,ask,highbid,lowask,pair->highbid,pair->lowask);
            }
            flag = 0;
            if ( Pair_NXTBTC != 0 && pair->btccounter != Pair_NXTBTC->counter )
                flag |= 1;
            if ( Pair_BTCUSD != 0 && pair->usdcounter != Pair_BTCUSD->counter )
                flag |= 2;
            if ( Pair_BTCCNY != 0 && pair->cnycounter != Pair_BTCCNY->counter )
                flag |= 4;
            //printf("%s %s/%s flag.%d (%d %d) %p %p\n",exchange,base,rel,flag,pair->btccounter,Pair_NXTBTC!=0?Pair_NXTBTC->counter:-1,bid,ask);
            if ( bid != 0 && ask != 0 && bid->price != 0. && ask->price != 0 && (now - bid->timestamp) < 30 && (now - ask->timestamp) < 30 && (fabs(bid->price - pair->highbid) > SMALLVAL || fabs(ask->price - pair->lowask) > SMALLVAL || (strcmp(pair->rel,"NXT") == 0 && flag != 0)) )
            {
                pair->counter++;
                pair->hblavolume = volume = MIN(bid->volume,ask->volume);
                arbval = lastbid = lastask = 0.;
                memset(pair->dirmasks,0,sizeof(pair->dirmasks));
                memset(pair->slopedirs,0,sizeof(pair->slopedirs));
                if ( strcmp(pair->rel,"NXT") == 0 )
                {
                    if ( Pair_NXTBTC != 0 && Pair_NXTBTC->highbid != 0. && Pair_NXTBTC->lowask != 0. )
                    {
                        pair->btccounter = Pair_NXTBTC->counter;
                        pair->btcbid = highbid * Pair_NXTBTC->highbid;
                        pair->btcask = lowask * Pair_NXTBTC->lowask;
                        pair->btcvol = volume * _pairaved(pair->btcbid,pair->btcask);
                    }
                }
                else if ( strcmp(pair->rel,"BTC") == 0 )
                {
                    pair->btcbid = highbid;
                    pair->btcask = lowask;
                    pair->btcvol = volume;
                }
                if ( strcmp(pair->rel,"USD") == 0 )
                {
                    pair->usdbid = highbid;
                    pair->usdask = lowask;
                    pair->usdvol = volume;
                }
                if ( strcmp(pair->rel,"CNY") == 0 )
                {
                    pair->cnybid = highbid;
                    pair->cnyask = lowask;
                    pair->cnyvol = volume;
                }
                if ( pair->btcbid != 0. && pair->btcask != 0. )
                {
                    if ( strcmp(pair->rel,"USD") != 0 && Pair_BTCUSD != 0 && Pair_BTCUSD->highbid != 0. && Pair_BTCUSD->lowask != 0. )
                    {
                        pair->usdcounter = Pair_BTCUSD->counter;
                        pair->usdbid = pair->btcbid * Pair_BTCUSD->highbid;
                        pair->usdask = pair->btcask * Pair_BTCUSD->lowask;
                        pair->usdvol = pair->btcvol * _pairaved(pair->usdbid,pair->usdask);
                    }
                    if ( strcmp(pair->rel,"CNY") != 0 && Pair_BTCCNY != 0 && Pair_BTCCNY->highbid != 0. && Pair_BTCCNY->lowask != 0. )
                    {
                        pair->cnycounter = Pair_BTCCNY->counter;
                        pair->cnybid = pair->btcbid * Pair_BTCCNY->highbid;
                        pair->cnyask = pair->btcask * Pair_BTCCNY->lowask;
                        pair->cnyvol = pair->btcvol * _pairaved(pair->cnybid,pair->cnyask);
                    }
                }
                for (i=0; i<TRADEBOTS_NUMDECAYS; i++)
                {
                    if ( (pair->bidslopes[i]= dxblend(&pair->bidaves[i],highbid,Tradebots_decays[i])) > 0. )
                        pair->slopedirs[0] |= (1 << i);
                    if ( (pair->askslopes[i]= dxblend(&pair->askaves[i],lowask,Tradebots_decays[i])) > 0. )
                        pair->slopedirs[1] |= (1 << i);
                    lastbid = pair->bidaves[i];
                    lastask = pair->askaves[i];
                    //printf("(%.8f %.8f) ",lastbid,lastask);
                }
                for (i=0; i<TRADEBOTS_NUMDECAYS; i++)
                {
                    if ( i == 0 )
                    {
                        if ( highbid > lastbid )
                            pair->dirmasks[0] |= (1 << i);
                        if ( lowask > lastask )
                            pair->dirmasks[1] |= (1 << i);
                    }
                    else
                    {
                        if ( pair->bidaves[i-1] > lastbid )
                            pair->dirmasks[0] |= (1 << i);
                        if ( pair->askaves[i-1] > lastask )
                            pair->dirmasks[1] |= (1 << i);
                    }
                }
                //printf("%12.6f %7s/%-3s %8s %14.8f %8s %14.8f spread %6.2f%% %02x:%02x %02x:%02x %d\n",volume,base,rel,bid->exchange,highbid,ask->exchange,lowask,100.*(lowask-highbid)/_pairaved(highbid,lowask),pair->dirmasks[0],pair->slopedirs[0],pair->dirmasks[1],pair->slopedirs[1],pair->counter);
                //printf("BTC.(%.8f %.8f) %.8f %.8f USD.(%.4f %.4f) CNY.(%.3f %.3f)\n",pair->btcbid,pair->btcask,Pair_BTCUSD!=0?Pair_BTCUSD->highbid:0,Pair_BTCUSD!=0?Pair_BTCUSD->lowask:0,pair->usdbid,pair->usdask,pair->cnybid,pair->cnyask);
            }
            if ( highbid != 0 )
                pair->highbid = highbid;
            if ( lowask != 0 )
                pair->lowask = lowask;
            //printf(">>>>>>> %s (%s/%s) BTC %.8f %.8f v%f counter.%d btc.%d (%d)\n",exchange,pair->base,pair->rel,pair->btcbid,pair->btcask,pair->btcvol,pair->counter,pair->btccounter,Pair_NXTBTC!=0?Pair_NXTBTC->counter:-1);
            if ( bid != 0 && ask != 0 && highbid != 0. && lowask != 0. && highbid > lowask && strcmp(bid->exchange,ask->exchange) != 0 && strcmp(rel,"BTC") == 0 )
            {
                volume = MIN(bid->volume,ask->volume);
                if ( volume*_pairaved(highbid,lowask) > 0.1 )
                    volume = 0.1 / _pairaved(highbid,lowask);
                if ( highbid * (1. - bid->profitmargin) > lowask * (1. + ask->profitmargin) )
                {
                    arbval = highbid * (1. - bid->profitmargin) - lowask * (1. + ask->profitmargin);
                    printf(">>>>>>>> FOUND ARB %s/%s highbid.%s %.8f lowask.%s %.8f volume %f (%.8f %.8f) %.4f%%\n",pair->base,pair->rel,bid->exchange,bid->price,ask->exchange,ask->price,volume,highbid,lowask,100.*(highbid-lowask)/_pairaved(highbid,lowask));
                    InstantDEX_buy(myinfo,0,0,0,ask->exchange,pair->base,"BTC",ask->price,volume,1);
                    InstantDEX_sell(myinfo,0,0,0,bid->exchange,pair->base,"BTC",bid->price,volume,1);
                    printf("finished trades %s/%s volume %f\n",pair->base,pair->rel,volume);
                }
            }
            if ( pair->counter > TRADEBOTS_NUMDECAYS )
            {
                if ( pair->lasttime != time(NULL) )
                {
                    if ( (pair->numrawfeatures= tradebots_calcrawfeatures(pair)) > 0 )
                    {
                        if ( pair->numsvmfeatures != 0 )
                        {
                            if ( myinfo->svmfeatures == 0 )
                                myinfo->svmfeatures = calloc(sizeof(*myinfo->svmfeatures),pair->numsvmfeatures);
                            if ( tradebots_calcsvmfeatures(myinfo->svmfeatures,pair,pair->rawfeatures,pair->prevrawfeatures) > 0 )
                                tradebots_calcpreds(pair->RTpreds,pair,myinfo->svmfeatures);
                        }
                    }
                    pair->lasttime = (uint32_t)time(NULL);
                }
                if ( 0 && time(NULL) > pair->lastanswertime+3600 )
                {
                    tradebots_calcanswers(pair);
                    pair->lastanswertime = (uint32_t)time(NULL);
                }
            }
        }
    }
}

cJSON *linfo_json(struct liquidity_info *li)
{
    cJSON *item = cJSON_CreateObject();
    jaddstr(item,"base",li->base);
    jaddstr(item,"rel",li->rel);
    if ( li->exchange[0] != 0 )
        jaddstr(item,"exchange",li->exchange);
    if ( li->assetid != 0 )
        jadd64bits(item,"assetid",li->assetid);
    if ( li->profit != 0. )
        jaddnum(item,"profitmargin",li->profit);
    if ( li->refprice != 0. )
        jaddnum(item,"refprice",li->refprice);
    if ( li->bid != 0. )
        jaddnum(item,"bid",li->bid);
    if ( li->ask != 0. )
        jaddnum(item,"ask",li->ask);
    if ( li->minvol != 0. )
        jaddnum(item,"minvol",li->minvol);
    if ( li->maxvol != 0. )
        jaddnum(item,"maxvol",li->maxvol);
    if ( li->totalvol != 0. )
        jaddnum(item,"totalvol",li->totalvol);
    return(item);
}

void _default_liquidity_command(struct supernet_info *myinfo,char *base,bits256 hash,cJSON *vals)
{
    struct liquidity_info li,refli; int32_t i; char *exchange,*relstr,numstr[32];
    if ( (exchange= jstr(vals,"exchange")) == 0 )
        exchange = "DEX";
    else if ( strcmp(exchange,"*") == 0 )
        exchange = "";
    else if ( exchanges777_find(exchange) == 0 )
    {
        printf("cant find exchange.(%s)\n",exchange);
        return;
    }
    if ( (relstr= jstr(vals,"rel")) == 0 )
        relstr = "BTC";
    if ( base == 0 || base[0] == 0 )
        base = jstr(vals,"base");
    if ( base == 0 || base[0] == 0 )
        return;
    memset(&li,0,sizeof(li));
    safecopy(li.base,base,sizeof(li.base));
    safecopy(li.rel,relstr,sizeof(li.rel));
    strncpy(li.exchange,exchange,sizeof(li.exchange));
    li.profit = jdouble(vals,"profit");
    li.refprice = jdouble(vals,"refprice");
    li.bid = jdouble(vals,"bid");
    li.ask = jdouble(vals,"ask");
    if ( (li.minvol= jdouble(vals,"minvol")) <= 0. )
        li.minvol = (strcmp("BTC",base) == 0) ? 0.0001 : 0.001;
    if ( strcmp(li.base,"KMD") == 0 && strcmp(li.rel,"BTC") == 0 && li.minvol >= 100. )
        li.minvol = 100.;
    if ( (li.maxvol= jdouble(vals,"maxvol")) < li.minvol )
        li.maxvol = li.minvol;
    if ( (li.totalvol= jdouble(vals,"total")) < li.maxvol )
        li.totalvol = li.maxvol;
    if ( strcmp("NXT",li.rel) == 0 )
        li.assetid = NXT_assetidfind(base);
    else if ( strcmp("UNITY",base) == 0 )
        li.assetid = NXT_assetidfind(base);
    if ( strcmp(li.base,"BTC") == 0 && strcmp("USD",li.rel) != 0 && strcmp("CNY",li.rel) != 0 )
    {
        printf("unsupported base BTC (%s/%s)\n",li.base,li.rel);
        return;
    }
    if ( strcmp(li.base,"BTC") != 0 && strcmp("BTC",li.rel) != 0 &&
        strcmp(li.base,"NXT") != 0 && strcmp("NXT",li.rel) != 0 &&
        strcmp(li.base,"USD") != 0 && strcmp("USD",li.rel) != 0 &&
        strcmp(li.base,"CNY") != 0 && strcmp("CNY",li.rel) != 0 &&
        strcmp(li.exchange,"DEX") != 0 ) // filter out most invalids
    {
        printf("unsupported base/rel %s/%s\n",li.base,li.rel);
        return;
    }
    for (i=0; i<sizeof(myinfo->linfos)/sizeof(*myinfo->linfos); i++)
    {
        refli = myinfo->linfos[i];
        if ( strcmp(li.rel,refli.base) == 0 && strcmp(li.base,refli.rel) == 0 )
        {
            /*li = refli;
            strcpy(li.base,refli.base);
            strcpy(li.rel,refli.rel);
            if ( fabs(li.refprice) > SMALLVAL )
                li.refprice = (1. / li.refprice);
            else li.refprice = 0.;
            li.dir = -li.dir;
            myinfo->linfos[i] = li;*/
            printf("cant Set rev linfo[%d] (%s/%s) %.6f %.8f already have (%s/%s)\n",i,li.rel,li.base,li.profit,li.refprice,refli.base,refli.rel);
            return;
        }
        else if ( refli.base[0] == 0 || (strcmp(li.base,refli.base) == 0 && strcmp(li.rel,refli.rel) == 0 && strcmp(li.exchange,refli.exchange) == 0) )
        {
            if ( refli.base[0] == 0 && li.exchange[0] != 0 && strcmp(li.exchange,"DEX") != 0 )
            {
                if ( strcmp("NXT",li.rel) == 0 && li.assetid != 0 )
                {
                    sprintf(numstr,"%llu",(long long)li.assetid);
                    printf("monitor %s %s\n",li.rel,numstr);
                    tradebot_monitor(myinfo,0,0,0,li.exchange,numstr,li.rel,0.);
                } else tradebot_monitor(myinfo,0,0,0,li.exchange,li.base,li.rel,0.);
            }
            myinfo->linfos[i] = li;
            //printf("Set linfo[%d] %s (%s/%s) profitmargin %.6f bid %.8f ask %.8f minvol %.6f maxvol %.6f ref %.8f <- (%s)\n",i,li.exchange,li.base,li.rel,li.profit,li.bid,li.ask,li.minvol,li.maxvol,li.refprice,jprint(vals,0));
            return;
        }
    }
    printf("ERROR: too many linfos %d\n",i);
}

int32_t _default_volume_ok(struct supernet_info *myinfo,struct liquidity_info *li,int32_t dir,double volume,double price)
{
    double minvol,maxvol;
    if ( dir < 0 )
    {
        minvol = li->minvol;
        maxvol = li->maxvol;
    }
    else
    {
        minvol = price * li->minvol;
        maxvol = price * li->maxvol;
    }
    printf("dir.%d minvol %f maxvol %f vs (%f %f) volume %f price %.8f\n",dir,li->minvol,li->maxvol,minvol,maxvol,volume,price);
    if ( (minvol == 0. || volume >= minvol) && (maxvol == 0. || volume <= maxvol) )
        return(0);
    else return(-1);
}

double _default_liquidity_active(struct supernet_info *myinfo,double *refpricep,char *exchange,char *base,char *rel,double destvolume)
{
    int32_t i,dir; struct liquidity_info refli;
    *refpricep = 0.;
    for (i=0; i<sizeof(myinfo->linfos)/sizeof(*myinfo->linfos); i++)
    {
        refli = myinfo->linfos[i];
        if ( refli.base[0] == 0 )
            continue;
        if ( strcmp(base,refli.base) == 0 && strcmp(rel,refli.rel) == 0 )
            dir = 1;
        else if ( strcmp(rel,refli.base) == 0 && strcmp(base,refli.rel) == 0 )
            dir = -1;
        else continue;
        if ( exchange[0] != 0 && refli.exchange[0] != 0 && strcmp(exchange,refli.exchange) != 0 )
        {
            printf("continue %s %s/%s [%d] dir.%d vs %s %s/%s\n",exchange,base,rel,i,dir,refli.exchange,refli.base,refli.rel);
            continue;
        }
        if ( _default_volume_ok(myinfo,&refli,dir,destvolume,dir > 0 ? refli.bid : refli.ask) == 0 )
        {
            if ( refli.profit != 0. )
                *refpricep = refli.refprice;
            else if ( dir > 0 )
                *refpricep = refli.bid;
            else if ( dir < 0 )
                *refpricep = refli.ask;
            printf(">>>>>>>> %s %s/%s [%d] dir.%d vs %s/%s -> %.8f margin %f\n",exchange,base,rel,i,dir,refli.base,refli.rel,*refpricep,refli.profit);
            return(refli.profit);
        }
        break;
    }
    return(0.);
}

struct liquidity_info *_default_lifind(struct supernet_info *myinfo,int32_t *dirp,char *base,char *rel)
{
    struct liquidity_info *li; int32_t i;
    *dirp = 0;
    for (i=0; i<sizeof(myinfo->linfos)/sizeof(*myinfo->linfos); i++)
    {
        li = &myinfo->linfos[i];
        if ( strcmp(li->base,base) == 0 && strcmp(li->rel,rel) == 0 )
        {
            *dirp = 1;
            return(li);
        }
        else if ( strcmp(li->base,rel) == 0 && strcmp(li->rel,base) == 0 )
        {
            *dirp = -1;
            return(li);
        }
    }
    return(0);
}

void _default_swap_balancingtrade(struct supernet_info *myinfo,struct basilisk_swap *swap,int32_t iambob)
{
    // update balance, compare to target balance, issue balancing trade via central exchanges, if needed
    struct liquidity_info *li; double vol,price,volume,srcamount,destamount,profitmargin,dir=0.,dotrade=1.; char base[64],rel[64]; int32_t idir; char *tradestr=0; cJSON *tradejson;
    srcamount = swap->I.req.srcamount;
    destamount = swap->I.req.destamount;
    profitmargin = (double)swap->I.req.profitmargin / 1000000.;
    if ( srcamount <= SMALLVAL || destamount <= SMALLVAL )
    {
        printf("illegal amount for balancing %f %f\n",srcamount,destamount);
        return;
    }
    if ( (li= _default_lifind(myinfo,&idir,swap->I.req.src,swap->I.req.dest)) != 0 )
    {
        if ( idir < 0 )
            vol = 1. / ((double)destamount / SATOSHIDEN);
        else vol = ((double)srcamount / SATOSHIDEN);
        li->totalvol -= vol;
        if ( li->totalvol <= 0. || (li->minvol != 0. && li->totalvol < li->minvol) )
            li->minvol = li->maxvol = li->totalvol = 0.;
        printf("li.(%s/%s) totalvol %f after -= %f minmax.(%f %f)\n",li->base,li->rel,li->totalvol,vol,li->minvol,li->maxvol);
    }
    strcpy(rel,"BTC");
    if ( strcmp(swap->I.req.src,"BTC") == 0 )
    {
        strcpy(base,swap->I.req.dest);
        price = (srcamount / destamount);
        volume = destamount / SATOSHIDEN;
        dir = -1.;
    }
    else if ( strcmp(swap->I.req.dest,"BTC") == 0 )
    {
        strcpy(base,swap->I.req.src);
        price = (destamount / srcamount);
        volume = srcamount / SATOSHIDEN;
        dir = 1.;
    }
    else
    {
        printf("only BTC trades can be balanced, not (%s/%s)\n",swap->I.req.src,swap->I.req.dest);
        return;
    }
    if ( iambob != 0 )
    {
        if ( myinfo->IAMLP != 0 )
        {
            printf("BOB: price %f * vol %f -> %s newprice %f margin %.2f%%\n",price,volume,dir < 0. ? "buy" : "sell",price + dir * price * profitmargin,100*profitmargin);
            if ( dir < 0. )
                tradestr = InstantDEX_buy(myinfo,0,0,0,"bittrex",base,rel,price,volume,dotrade);
            else tradestr = InstantDEX_sell(myinfo,0,0,0,"bittrex",base,rel,price,volume,dotrade);
        }
    }
    else
    {
        if ( myinfo->IAMLP != 0 )
        {
            printf("ALICE: price %f * vol %f -> %s newprice %f margin %.2f%%\n",price,volume,dir > 0. ? "buy" : "sell",price - dir * price * profitmargin,100*profitmargin);
            if ( dir > 0. )
                tradestr = InstantDEX_buy(myinfo,0,0,0,"bittrex",base,rel,price,volume,dotrade);
            else tradestr = InstantDEX_sell(myinfo,0,0,0,"bittrex",base,rel,price,volume,dotrade);
        }
    }
    if ( tradestr != 0 )
    {
        if ( (tradejson= cJSON_Parse(tradestr)) != 0 )
        {
            if ( jobj(tradejson,"error") == 0 ) // balancing is opposite trade
                tradebot_pendingadd(myinfo,tradejson,swap->I.req.dest,destamount,swap->I.req.src,srcamount);
            else free_json(tradejson);
        }
        free(tradestr);
    }
}

void tradebot_swap_balancingtrade(struct supernet_info *myinfo,struct basilisk_swap *swap,int32_t iambob)
{
    if ( swap->bobcoin != 0 && swap->alicecoin != 0 )
    {
        if ( iambob != 0 )
        {
            if ( strcmp(swap->I.req.src,swap->bobcoin->symbol) == 0 )
                swap->bobcoin->DEXinfo.DEXpending -= swap->I.req.srcamount;
            else if ( strcmp(swap->I.req.dest,swap->bobcoin->symbol) == 0 )
                swap->bobcoin->DEXinfo.DEXpending -= swap->I.req.destamount;
        }
        else
        {
            if ( strcmp(swap->I.req.src,swap->alicecoin->symbol) == 0 )
                swap->alicecoin->DEXinfo.DEXpending -= swap->I.req.srcamount;
            else if ( strcmp(swap->I.req.dest,swap->alicecoin->symbol) == 0 )
                swap->alicecoin->DEXinfo.DEXpending -= swap->I.req.destamount;
        }
    }
    printf(">>>>>>>>>>>>>>>>>> balancing trade done by marketmaker\n");
    return;
    if ( swap->balancingtrade == 0 )
        _default_swap_balancingtrade(myinfo,swap,iambob);
    else (*swap->balancingtrade)(myinfo,swap,iambob);
}

void tradebot_liquidity_command(struct supernet_info *myinfo,char *base,bits256 hash,cJSON *vals)
{
    // processed in LIFO manner which allows to override existing command
    if ( myinfo->liquidity_command == 0 )
        _default_liquidity_command(myinfo,base,hash,vals);
    else (*myinfo->liquidity_command)(myinfo,base,hash,vals);
}

double tradebot_liquidity_active(struct supernet_info *myinfo,double *refpricep,char *exchange,char *base,char *rel,double destvolume)
{
    if ( myinfo->liquidity_active == 0 )
        return(_default_liquidity_active(myinfo,refpricep,exchange,base,rel,destvolume));
    else return((*myinfo->liquidity_active)(myinfo,refpricep,exchange,base,rel,destvolume));
}

// struct exchange_quote { uint64_t satoshis,orderid,offerNXT,exchangebits; double price,volume; uint32_t timestamp,val; };

void tradebots_processprices(struct supernet_info *myinfo,struct exchange_info *exchange,char *base,char *rel,struct exchange_quote *bidasks,int32_t numbids,int32_t numasks)
{
    double price,profitmargin=0.,volume; struct tradebot_arbpair *pair;
    if ( strcmp(rel,"NXT") == 0 && strcmp(base,"BTC") != 0 && (base= NXT_assetnamefind(base)) == 0 )
    {
        //printf("reject %s %s/%s\n",exchange,base,rel);
        return;
    }
    else if ( strcmp(base,"NXT") == 0 && strcmp(rel,"BTC") != 0 && (rel= NXT_assetnamefind(rel)) == 0 )
    {
        //printf("reject %s %s/%s\n",exchange,base,rel);
        return;
    }
    printf("%s %s/%s bids.%d asks.%d\n",exchange->name,base,rel,numbids,numasks);
    if ( numbids > 0 && (volume= bidasks[0].volume) > 0. && (profitmargin=
                         tradebot_liquidity_active(myinfo,&price,exchange->name,base,rel,volume)) > 0. )
    {
        if ( price == 0. )
            price = bidasks[0].price;
        tradebot_arbcandidate(myinfo,exchange->name,1,base,rel,price,volume,(uint32_t)time(NULL),profitmargin);
    }
    if ( numasks > 0 && (volume= bidasks[1].volume) > 0. && (profitmargin=
                         tradebot_liquidity_active(myinfo,&price,exchange->name,rel,base,volume)) > 0. )
    {
        if ( price == 0. )
            price = bidasks[1].price;
        tradebot_arbcandidate(myinfo,exchange->name,-1,base,rel,price,volume,(uint32_t)time(NULL),profitmargin);
    }
    if ( (pair= tradebots_arbpair_find(base,rel)) == 0 )
        pair = tradebots_arbpair_create(base,rel);
    if ( pair != 0 )
    {
        if ( strcmp(rel,"NXT") == 0 )
        {
            if ( pair->btcbid != 0. && pair->btcask != 0. )
            {
                tradebot_arbcandidate(myinfo,"arb",1,base,"BTC",pair->btcbid,pair->btcvol,(uint32_t)time(NULL),profitmargin);
                tradebot_arbcandidate(myinfo,"arb",-1,base,"BTC",pair->btcask,pair->btcvol,(uint32_t)time(NULL),profitmargin);
            }
        }
        if ( strcmp(rel,"USD") != 0 && pair->usdbid != 0. && pair->usdask != 0. )
        {
            tradebot_arbcandidate(myinfo,"arb",1,base,"USD",pair->usdbid,pair->usdvol,(uint32_t)time(NULL),profitmargin);
            tradebot_arbcandidate(myinfo,"arb",-1,base,"USD",pair->usdask,pair->usdvol,(uint32_t)time(NULL),profitmargin);
        }
        if ( strcmp(rel,"CNY") != 0 && pair->cnybid != 0. && pair->cnyask != 0. )
        {
            tradebot_arbcandidate(myinfo,"arb",1,base,"CNY",pair->cnybid,pair->cnyvol,(uint32_t)time(NULL),profitmargin);
            tradebot_arbcandidate(myinfo,"arb",-1,base,"CNY",pair->cnyask,pair->cnyvol,(uint32_t)time(NULL),profitmargin);
        }
    }
}