func (t *TicketPurchaser) Purchase(height int64, keyHeight int64) (*PurchaseStats, error) {
	ps := &PurchaseStats{Height: height,KeyHeight: keyHeight}
	if _, exists := t.heightCheck[keyHeight]; exists {
		log.Debugf("We've already seen this keyHeight, "+
			"reorg/fork detected at keyHeight %v", keyHeight)
		return ps, nil
	}
	t.heightCheck[keyHeight] = struct{}{}
	winSize := t.activeNet.StakeDiffWindowSize
	thisIdxDiffPeriod := int(keyHeight % winSize)
	nextIdxDiffPeriod := int((keyHeight + 1) % winSize)
	thisWindowPeriod := int(keyHeight / winSize)
	refreshStakeInfo := false
	if t.firstStart {
		t.firstStart = false
		log.Debugf("First run for ticket buyer")
		log.Debugf("Transaction relay fee: %v", hcashutil.Amount(t.cfg.TxFee))
		refreshStakeInfo = true
	} else {
		if nextIdxDiffPeriod == 0 {
			log.Debugf("Resetting stake window variables")
			refreshStakeInfo = true
		}
		if thisIdxDiffPeriod != 0 && thisWindowPeriod > t.windowPeriod {
			log.Debugf("Reconnected in a different window, now at height %v", height)
			refreshStakeInfo = true
		}
	}
	t.idxDiffPeriod = int(keyHeight % winSize)
	t.windowPeriod = int(keyHeight / winSize)
	if refreshStakeInfo {
		log.Debugf("Getting StakeInfo")
		var curStakeInfo *wallet.StakeInfoData
		var err error
		for i := 1; i <= stakeInfoReqTries; i++ {
			curStakeInfo, err = t.wallet.StakeInfo(false, t.hcashdChainSvr)
			if err != nil {
				log.Debugf("Waiting for StakeInfo, attempt %v: (%v)", i, err.Error())
				time.Sleep(stakeInfoReqTryDelay)
				continue
			}
			if err == nil {
				log.Debugf("Got StakeInfo")
				break
			}
		}
		if err != nil {
			return ps, err
		}
		t.stakePoolSize = curStakeInfo.PoolSize
		t.stakeLive = curStakeInfo.Live
		t.stakeImmature = curStakeInfo.Immature
		subsidyCache := blockchain.NewSubsidyCache(keyHeight, t.wallet.ChainParams())
		subsidy := blockchain.CalcStakeVoteSubsidy(subsidyCache, keyHeight, t.wallet.ChainParams())
		t.stakeVoteSubsidy = hcashutil.Amount(subsidy)
		log.Tracef("Stake vote subsidy: %v", t.stakeVoteSubsidy)
	}
	maxPerBlock := t.MaxPerBlock()
	if maxPerBlock == 0 {
		return ps, nil
	} else if maxPerBlock < 0 {
		if int(keyHeight)%maxPerBlock != 0 {
			return ps, nil
		}
		maxPerBlock = 1
	}
	avgPriceAmt, err := t.calcAverageTicketPrice(height, keyHeight)
	if err != nil {
		return ps, fmt.Errorf("Failed to calculate average ticket "+
			" price amount at height %v: %v", height, err)
	}
	if avgPriceAmt < hcashutil.Amount(t.activeNet.MinimumStakeDiff) {
		avgPriceAmt = hcashutil.Amount(t.activeNet.MinimumStakeDiff)
	}
	log.Debugf("Calculated average ticket price: %v", avgPriceAmt)
	ps.PriceAverage = avgPriceAmt
	nextStakeDiff, err := t.wallet.StakeDifficulty()
	log.Tracef("Next stake difficulty: %v", nextStakeDiff)
	if err != nil {
		return ps, err
	}
	ps.PriceCurrent = nextStakeDiff
	t.ticketPrice = nextStakeDiff
	ps.TicketPrice = nextStakeDiff
	sDiffEsts, err := t.hcashdChainSvr.EstimateStakeDiff( nil)
	if err == nil {
		ps.PriceNext, err = hcashutil.NewAmount(sDiffEsts.Expected)
		if err != nil {
			return ps, err
		}
		log.Tracef("Estimated stake diff: (min: %v, expected: %v, max: %v)",
			sDiffEsts.Min, sDiffEsts.Expected, sDiffEsts.Max)
	}
	var maxPriceAmt hcashutil.Amount
	maxPriceAbs := t.MaxPriceAbsolute()
	if maxPriceAbs > 0 &&
		maxPriceAbs < avgPriceAmt.MulF64(t.MaxPriceRelative()) {
		maxPriceAmt = maxPriceAbs
		log.Debugf("Using absolute max price: %v", maxPriceAmt)
	} else {
		maxPriceAmt = avgPriceAmt.MulF64(t.MaxPriceRelative())
		log.Debugf("Using relative max price: %v", maxPriceAmt)
	}
	accountName, err := t.AccountName()
	if err != nil {
		return ps, err
	}
	account := t.Account()
	bal, err := t.wallet.CalculateAccountBalance(account, 0)
	if err != nil {
		return ps, err
	}
	log.Debugf("Spendable balance for account '%s': %v", accountName, bal.Spendable)
	ps.Balance = bal.Spendable
	if nextStakeDiff > maxPriceAmt {
		log.Infof("Not buying because max price exceeded: "+
			"(max price: %v, ticket price: %v)", maxPriceAmt, nextStakeDiff)
		return ps, nil
	}
	inMP, err := t.ownTicketsInMempool()
	if err != nil {
		return ps, err
	}
	log.Tracef("Own tickets in mempool: %v", inMP)
	if !t.cfg.DontWaitForTickets {
		if inMP >= t.MaxInMempool() {
			log.Infof("Currently waiting for %v tickets to enter the "+
				"blockchain before buying more tickets (in mempool: %v,"+
				" max allowed in mempool %v)", inMP-t.MaxInMempool(),
				inMP, t.cfg.MaxInMempool)
			return ps, nil
		}
	}
	oneBlock := uint32(1)
	info, err := t.hcashdChainSvr.TicketFeeInfo(&oneBlock, &zeroUint32)
	if err != nil {
		return ps, err
	}
	if len(info.FeeInfoBlocks) < 1 {
		return ps, fmt.Errorf("error FeeInfoBlocks bad length")
	}
	ticketPurchasesInLastBlock := int(info.FeeInfoBlocks[0].Number)
	log.Tracef("Ticket purchase slots filled in last block: %v", ticketPurchasesInLastBlock)
	mempoolall, err := t.allTicketsInMempool()
	if err != nil {
		return ps, err
	}
	log.Tracef("All tickets in mempool: %v", mempoolall)
	var feeToUse hcashutil.Amount
	maxStake := int(t.activeNet.MaxFreshStakePerBlock)
	if ticketPurchasesInLastBlock < maxStake && mempoolall < maxStake {
		feeToUse = t.MinFee()
		log.Debugf("Using min ticket fee: %v", feeToUse)
		if ticketPurchasesInLastBlock < maxStake {
			log.Tracef("(ticket purchase slots available in last block)")
		}
		if mempoolall < maxStake {
			log.Tracef("(total tickets in mempool is less than max per block)")
		}
	} else {
		var chainFee hcashutil.Amount
		if t.idxDiffPeriod < t.cfg.BlocksToAvg {
			chainFee, err = t.findClosestFeeWindows(nextStakeDiff,
				t.useMedian)
			if err != nil {
				return ps, err
			}
		} else {
			chainFee, err = t.findTicketFeeBlocks(t.useMedian)
			if err != nil {
				return ps, err
			}
		}
		log.Tracef("Average ticket fee: %v", chainFee)
		feeToUse = chainFee.MulF64(t.cfg.FeeTargetScaling)
		if feeToUse > t.MaxFee() {
			log.Infof("Not buying because max fee exceed: (max fee: %v, scaled fee: %v)",
				t.MaxFee(), feeToUse)
			return ps, nil
		}
		if feeToUse < t.MinFee() {
			feeToUse = t.MinFee()
		}
	}
	t.wallet.SetTicketFeeIncrement(feeToUse)
	balanceToMaintainAmt := t.BalanceToMaintain().ToCoin()
	if balanceToMaintainAmt > bal.Total.ToCoin()*t.cfg.BalanceToMaintainRelative {
		log.Debugf("Using absolute balancetomaintain: %v", balanceToMaintainAmt)
	} else {
		balanceToMaintainAmt = bal.Total.ToCoin() * t.cfg.BalanceToMaintainRelative
		log.Debugf("Using relative balancetomaintain: %v", balanceToMaintainAmt)
	}
	ticketsLeftInWindow := (int(winSize) - t.idxDiffPeriod) * int(t.activeNet.MaxFreshStakePerBlock)
	log.Tracef("Ticket allotment left in window is %v, blocks left is %v",
		ticketsLeftInWindow, (int(winSize) - t.idxDiffPeriod))
	toBuyForBlock := int(math.Floor((bal.Spendable.ToCoin() - balanceToMaintainAmt) / nextStakeDiff.ToCoin()))
	if toBuyForBlock < 0 {
		toBuyForBlock = 0
	}
	if toBuyForBlock == 0 {
		log.Infof("Not enough funds to buy tickets: (spendable: %v, balancetomaintain: %v) ",
			bal.Spendable.ToCoin(), balanceToMaintainAmt)
	}
	if !t.cfg.NoSpreadTicketPurchases && toBuyForBlock > 0 {
		log.Debugf("Spreading purchases throughout window")
		proportionLive := float64(0)
		if t.stakePoolSize != 0 {
			proportionLive = float64(t.stakeLive) / float64(t.stakePoolSize)
		}
		blocksRemaining := int(winSize) - t.idxDiffPeriod
		tixWillRedeem := float64(blocksRemaining) * float64(t.activeNet.TicketsPerBlock) * proportionLive
		yourAvgTixPrice := 0.0
		if t.stakeLive+t.stakeImmature != 0 {
			yourAvgTixPrice = bal.LockedByTickets.ToCoin() / float64(t.stakeLive+t.stakeImmature)
		}
		redeemedFunds := tixWillRedeem * yourAvgTixPrice
		stakeRewardFunds := tixWillRedeem * t.stakeVoteSubsidy.ToCoin()
		tixToBuyWithRedeemedFunds := redeemedFunds / nextStakeDiff.ToCoin()
		tixToBuyWithStakeRewardFunds := stakeRewardFunds / nextStakeDiff.ToCoin()
		tixCanBuy := (bal.Spendable.ToCoin() - balanceToMaintainAmt) / nextStakeDiff.ToCoin()
		if tixCanBuy < 0 {
			tixCanBuy = 0
		}
		tixCanBuyAll := tixCanBuy + tixToBuyWithRedeemedFunds + tixToBuyWithStakeRewardFunds
		buyPerBlock := tixCanBuy / float64(blocksRemaining)
		buyPerBlockAll := tixCanBuyAll / float64(blocksRemaining)
		log.Debugf("Your average purchase price for tickets in the pool is %.2f HCASH", yourAvgTixPrice)
		log.Debugf("Available funds of %.2f HCASH can buy %.2f tickets, %.2f tickets per block",
			bal.Spendable.ToCoin()-balanceToMaintainAmt, tixCanBuy, buyPerBlock)
		log.Debugf("With %.2f%% proportion live, you will redeem ~%.2f tickets in the remaining %d blocks",
			proportionLive*100, tixWillRedeem, blocksRemaining)
		log.Debugf("Redeemed ticket value expected is %.2f HCASH, buys %.2f tickets, %.2f%% more",
			redeemedFunds, tixToBuyWithRedeemedFunds, tixToBuyWithRedeemedFunds/tixCanBuy*100)
		log.Debugf("Stake reward expected is %.2f HCASH, buys %.2f tickets, %.2f%% more",
			stakeRewardFunds, tixToBuyWithStakeRewardFunds, tixToBuyWithStakeRewardFunds/tixCanBuy*100)
		log.Infof("Will buy ~%.2f tickets per block, %.2f ticket purchases remain this window", buyPerBlockAll, tixCanBuyAll)
		if blocksRemaining > 0 && tixCanBuy > 0 {
			rand.Seed(time.Now().UTC().UnixNano())
			if tixCanBuyAll >= float64(blocksRemaining) {
				toBuyForBlock = int(math.Floor(buyPerBlockAll))
				ticketRemainder := int(math.Floor(tixCanBuyAll)) % blocksRemaining
				if rand.Float64() <= float64(ticketRemainder) {
					toBuyForBlock++
				}
			} else {
				if rand.Float64() <= buyPerBlockAll {
					log.Debugf("Buying one this round")
					toBuyForBlock = 1
				} else {
					toBuyForBlock = 0
					log.Debugf("Skipping this round")
				}
			}
			if toBuyForBlock > int(math.Floor(tixCanBuy)) {
				log.Debugf("Waiting for current windows tickets to redeem")
				log.Debugf("Cant yet buy %d, will buy %d",
					toBuyForBlock, int(math.Floor(tixCanBuy)))
				toBuyForBlock = int(math.Floor(tixCanBuy))
			}
		} else {
			toBuyForBlock = 0
			if tixCanBuy < 0 {
				log.Debugf("tixCanBuy < 0, this should not occur")
			} else if tixCanBuy < 1 {
				log.Debugf("Not enough funds to purchase a ticket")
			}
			if blocksRemaining < 0 {
				log.Debugf("blocksRemaining < 0, this should not occur")
			} else if blocksRemaining == 0 {
				log.Debugf("There are no more opportunities to purchase tickets in this window")
			}
		}
	}
	if toBuyForBlock > maxPerBlock {
		toBuyForBlock = maxPerBlock
		if maxPerBlock == 1 {
			log.Infof("Limiting to 1 purchase so that maxperblock is not exceeded")
		} else {
			log.Infof("Limiting to %d purchases so that maxperblock is not exceeded", maxPerBlock)
		}
	}
	if toBuyForBlock <= 0 {
		log.Infof("Not buying any tickets this round")
		return ps, nil
	}
	notEnough := func(bal hcashutil.Amount, toBuy int, sd hcashutil.Amount) bool {
		return (bal.ToCoin() - float64(toBuy)*sd.ToCoin()) <
			balanceToMaintainAmt
	}
	if notEnough(bal.Spendable, toBuyForBlock, nextStakeDiff) {
		for notEnough(bal.Spendable, toBuyForBlock, nextStakeDiff) {
			if toBuyForBlock == 0 {
				break
			}
			toBuyForBlock--
			log.Debugf("Not enough, decremented amount of tickets to buy")
		}
		if toBuyForBlock == 0 {
			log.Infof("Not buying because spendable balance would be %v "+
				"but balance to maintain is %v",
				(bal.Spendable.ToCoin() - float64(toBuyForBlock)*
					nextStakeDiff.ToCoin()),
				balanceToMaintainAmt)
			return ps, nil
		}
	}
	if !t.cfg.DontWaitForTickets {
		if toBuyForBlock+inMP > t.cfg.MaxInMempool {
			toBuyForBlock = t.cfg.MaxInMempool - inMP
			log.Debugf("Limiting to %d purchases so that maxinmempool is not exceeded", toBuyForBlock)
		}
	}
	ticketAddress := t.TicketAddress()
	if ticketAddress == nil {
		ticketAddress, err = t.wallet.NewInternalAddress(account, wallet.WithGapPolicyWrap())
		if err != nil {
			return ps, err
		}
	}
	poolFeesAmt, err := hcashutil.NewAmount(t.cfg.PoolFees)
	if err != nil {
		return ps, err
	}
	expiry := int32(int(keyHeight) + (t.ExpiryDelta() + 2))
	hashes, purchaseErr := t.wallet.PurchaseTickets(0,
		maxPriceAmt,
		0,  
		ticketAddress,
		account,
		toBuyForBlock,
		t.PoolAddress(),
		poolFeesAmt.ToCoin(),
		expiry,
		t.wallet.RelayFee(),
		t.wallet.TicketFeeIncrement(),
	)
	for i := range hashes {
		log.Infof("Purchased ticket %v at stake difficulty %v (%v "+
			"fees per KB used)", hashes[i], nextStakeDiff.ToCoin(),
			feeToUse.ToCoin())
	}
	ps.Purchased = len(hashes)
	if purchaseErr != nil {
		log.Errorf("One or more tickets could not be purchased: %v", purchaseErr)
	}
	bal, err = t.wallet.CalculateAccountBalance(account, 0)
	if err != nil {
		return ps, err
	}
	log.Debugf("Usable balance for account '%s' after purchases: %v", accountName, bal.Spendable)
	ps.Balance = bal.Spendable
	if len(hashes) == 0 && purchaseErr != nil {
		return ps, purchaseErr
	}
	return ps, nil
}
Purchase is the main handler for purchasing tickets for the user.
TODO Not make this an inlined pile of crap.
package ticketbuyer
import (
	"fmt"
	"math"
	"math/rand"
	"sync"
	"time"
	"github.com/HcashOrg/hcashd/blockchain"
	"github.com/HcashOrg/hcashd/chaincfg"
	"github.com/HcashOrg/hcashrpcclient"
	"github.com/HcashOrg/hcashutil"
	"github.com/HcashOrg/hcashwallet/wallet"
)
var (
	zeroUint32 = uint32(0)
	stakeInfoReqTries = 20
	stakeInfoReqTryDelay = time.Second * 1
)
const (
	TicketFeeMean = "mean"
	TicketFeeMedian = "median"
	PriceTargetVWAP = "vwap"
	PriceTargetPool = "pool"
	PriceTargetDual = "dual"
)
type Config struct {
	AccountName               string
	AvgPriceMode              string
	AvgPriceVWAPDelta         int
	BalanceToMaintainAbsolute int64
	BalanceToMaintainRelative float64
	BlocksToAvg               int
	DontWaitForTickets        bool
	ExpiryDelta               int
	FeeSource                 string
	FeeTargetScaling          float64
	MinFee                    int64
	MaxFee                    int64
	MaxPerBlock               int
	MaxPriceAbsolute          int64
	MaxPriceRelative          float64
	MaxInMempool              int
	PoolAddress               hcashutil.Address
	PoolFees                  float64
	NoSpreadTicketPurchases   bool
	TicketAddress             hcashutil.Address
	TxFee                     int64
}
type TicketPurchaser struct {
	cfg              *Config
	activeNet        *chaincfg.Params
	hcashdChainSvr     *hcashrpcclient.Client
	wallet           *wallet.Wallet
	ticketAddress    hcashutil.Address
	poolAddress      hcashutil.Address
	firstStart       bool
	windowPeriod     int           
	idxDiffPeriod    int           
	useMedian        bool          
	priceMode        avgPriceMode  
	heightCheck      map[int64]struct{}
	ticketPrice      hcashutil.Amount
	stakePoolSize    uint32
	stakeLive        uint32
	stakeImmature    uint32
	stakeVoteSubsidy hcashutil.Amount
	purchaserMtx      sync.Mutex
	account           uint32
	balanceToMaintain hcashutil.Amount
	maxPriceAbsolute  hcashutil.Amount
	maxPriceRelative  float64
	maxFee            hcashutil.Amount
	minFee            hcashutil.Amount
	poolFees          float64
	maxPerBlock       int
	maxInMempool      int
	expiryDelta       int
}
func (t *TicketPurchaser) Config() (*Config, error) {
	t.purchaserMtx.Lock()
	accountName, err := t.wallet.AccountName(t.account)
	if err != nil {
		return nil, err
	}
	config := &Config{
		AccountName:               accountName,
		AvgPriceMode:              t.cfg.AvgPriceMode,
		AvgPriceVWAPDelta:         t.cfg.AvgPriceVWAPDelta,
		BalanceToMaintainAbsolute: int64(t.balanceToMaintain),
		BlocksToAvg:               t.cfg.BlocksToAvg,
		DontWaitForTickets:        t.cfg.DontWaitForTickets,
		ExpiryDelta:               t.cfg.ExpiryDelta,
		FeeSource:                 t.cfg.FeeSource,
		FeeTargetScaling:          t.cfg.FeeTargetScaling,
		MinFee:                    int64(t.minFee),
		MaxFee:                    int64(t.maxFee),
		MaxPerBlock:               t.maxPerBlock,
		MaxPriceAbsolute:          int64(t.maxPriceAbsolute),
		MaxPriceRelative:          t.maxPriceRelative,
		MaxInMempool:              t.cfg.MaxInMempool,
		PoolAddress:               t.cfg.PoolAddress,
		PoolFees:                  t.poolFees,
		NoSpreadTicketPurchases:   t.cfg.NoSpreadTicketPurchases,
		TicketAddress:             t.cfg.TicketAddress,
		TxFee:                     t.cfg.TxFee,
	}
	t.purchaserMtx.Unlock()
	return config, nil
}
func (t *TicketPurchaser) AccountName() (string, error) {
	t.purchaserMtx.Lock()
	accountName, err := t.wallet.AccountName(t.account)
	t.purchaserMtx.Unlock()
	return accountName, err
}
func (t *TicketPurchaser) Account() uint32 {
	t.purchaserMtx.Lock()
	account := t.account
	t.purchaserMtx.Unlock()
	return account
}
func (t *TicketPurchaser) SetAccount(account uint32) {
	t.purchaserMtx.Lock()
	t.account = account
	t.purchaserMtx.Unlock()
}
func (t *TicketPurchaser) BalanceToMaintain() hcashutil.Amount {
	t.purchaserMtx.Lock()
	balanceToMaintain := t.balanceToMaintain
	t.purchaserMtx.Unlock()
	return balanceToMaintain
}
func (t *TicketPurchaser) SetBalanceToMaintain(balanceToMaintain int64) {
	t.purchaserMtx.Lock()
	t.balanceToMaintain = hcashutil.Amount(balanceToMaintain)
	t.purchaserMtx.Unlock()
}
func (t *TicketPurchaser) MaxPriceAbsolute() hcashutil.Amount {
	t.purchaserMtx.Lock()
	maxPriceAbsolute := t.maxPriceAbsolute
	t.purchaserMtx.Unlock()
	return maxPriceAbsolute
}
func (t *TicketPurchaser) SetMaxPriceAbsolute(maxPriceAbsolute int64) {
	t.purchaserMtx.Lock()
	t.maxPriceAbsolute = hcashutil.Amount(maxPriceAbsolute)
	t.purchaserMtx.Unlock()
}
func (t *TicketPurchaser) MaxPriceRelative() float64 {
	t.purchaserMtx.Lock()
	maxPriceRelative := t.maxPriceRelative
	t.purchaserMtx.Unlock()
	return maxPriceRelative
}
func (t *TicketPurchaser) SetMaxPriceRelative(maxPriceRelative float64) {
	t.purchaserMtx.Lock()
	t.maxPriceRelative = maxPriceRelative
	t.purchaserMtx.Unlock()
}
func (t *TicketPurchaser) MaxFee() hcashutil.Amount {
	t.purchaserMtx.Lock()
	maxFee := t.maxFee
	t.purchaserMtx.Unlock()
	return maxFee
}
func (t *TicketPurchaser) SetMaxFee(maxFee int64) {
	t.purchaserMtx.Lock()
	t.maxFee = hcashutil.Amount(maxFee)
	t.purchaserMtx.Unlock()
}
func (t *TicketPurchaser) MinFee() hcashutil.Amount {
	t.purchaserMtx.Lock()
	minFee := t.minFee
	t.purchaserMtx.Unlock()
	return minFee
}
func (t *TicketPurchaser) SetMinFee(minFee int64) {
	t.purchaserMtx.Lock()
	t.minFee = hcashutil.Amount(minFee)
	t.purchaserMtx.Unlock()
}
func (t *TicketPurchaser) TicketAddress() hcashutil.Address {
	t.purchaserMtx.Lock()
	ticketAddress := t.ticketAddress
	t.purchaserMtx.Unlock()
	return ticketAddress
}
func (t *TicketPurchaser) SetTicketAddress(ticketAddress hcashutil.Address) {
	t.purchaserMtx.Lock()
	t.ticketAddress = ticketAddress
	t.purchaserMtx.Unlock()
}
func (t *TicketPurchaser) PoolAddress() hcashutil.Address {
	t.purchaserMtx.Lock()
	poolAddress := t.poolAddress
	t.purchaserMtx.Unlock()
	return poolAddress
}
func (t *TicketPurchaser) SetPoolAddress(poolAddress hcashutil.Address) {
	t.purchaserMtx.Lock()
	t.poolAddress = poolAddress
	t.purchaserMtx.Unlock()
}
func (t *TicketPurchaser) PoolFees() float64 {
	t.purchaserMtx.Lock()
	poolFees := t.poolFees
	t.purchaserMtx.Unlock()
	return poolFees
}
func (t *TicketPurchaser) SetPoolFees(poolFees float64) {
	t.purchaserMtx.Lock()
	t.poolFees = poolFees
	t.purchaserMtx.Unlock()
}
func (t *TicketPurchaser) MaxPerBlock() int {
	t.purchaserMtx.Lock()
	maxPerBlock := t.maxPerBlock
	t.purchaserMtx.Unlock()
	return maxPerBlock
}
func (t *TicketPurchaser) SetMaxPerBlock(maxPerBlock int) {
	t.purchaserMtx.Lock()
	t.maxPerBlock = maxPerBlock
	t.purchaserMtx.Unlock()
}
func (t *TicketPurchaser) MaxInMempool() int {
	t.purchaserMtx.Lock()
	maxInMempool := t.maxInMempool
	t.purchaserMtx.Unlock()
	return maxInMempool
}
func (t *TicketPurchaser) SetMaxInMempool(maxInMempool int) {
	t.purchaserMtx.Lock()
	t.maxInMempool = maxInMempool
	t.purchaserMtx.Unlock()
}
func (t *TicketPurchaser) ExpiryDelta() int {
	t.purchaserMtx.Lock()
	expiryDelta := t.expiryDelta
	t.purchaserMtx.Unlock()
	return expiryDelta
}
func (t *TicketPurchaser) SetExpiryDelta(expiryDelta int) {
	t.purchaserMtx.Lock()
	t.expiryDelta = expiryDelta
	t.purchaserMtx.Unlock()
}
func NewTicketPurchaser(cfg *Config,
	hcashdChainSvr *hcashrpcclient.Client,
	w *wallet.Wallet,
	activeNet *chaincfg.Params) (*TicketPurchaser, error) {
	priceMode := avgPriceMode(AvgPriceVWAPMode)
	switch cfg.AvgPriceMode {
	case PriceTargetPool:
		priceMode = AvgPricePoolMode
	case PriceTargetDual:
		priceMode = AvgPriceDualMode
	}
	account, err := w.AccountNumber(cfg.AccountName)
	if err != nil {
		return nil, err
	}
	return &TicketPurchaser{
		cfg:           cfg,
		activeNet:     activeNet,
		hcashdChainSvr:  hcashdChainSvr,
		wallet:        w,
		firstStart:    true,
		ticketAddress: cfg.TicketAddress,
		poolAddress:   cfg.PoolAddress,
		useMedian:     cfg.FeeSource == TicketFeeMedian,
		priceMode:     priceMode,
		heightCheck:   make(map[int64]struct{}),
		account:           account,
		balanceToMaintain: hcashutil.Amount(cfg.BalanceToMaintainAbsolute),
		maxFee:            hcashutil.Amount(cfg.MaxFee),
		minFee:            hcashutil.Amount(cfg.MinFee),
		maxPerBlock:       cfg.MaxPerBlock,
		maxPriceAbsolute:  hcashutil.Amount(cfg.MaxPriceAbsolute),
		maxPriceRelative:  cfg.MaxPriceRelative,
		poolFees:          cfg.PoolFees,
		maxInMempool:      cfg.MaxInMempool,
		expiryDelta:       cfg.ExpiryDelta,
	}, nil
}
type PurchaseStats struct {
	Height        int64
	KeyHeight	  int64
	PriceMaxScale float64
	PriceAverage  hcashutil.Amount
	PriceNext     hcashutil.Amount
	PriceCurrent  hcashutil.Amount
	Purchased     int
	LeftWindow    int
	Balance       hcashutil.Amount
	TicketPrice   hcashutil.Amount
}