mirror of
https://gitee.com/fasiondog/hikyuu.git
synced 2024-12-04 12:57:45 +08:00
买空/卖空支持(continue)
This commit is contained in:
parent
87ffded746
commit
17ae2a3261
@ -10,46 +10,16 @@
|
||||
|
||||
namespace hku {
|
||||
|
||||
PositionRecord::PositionRecord(PositionRecord&& rv) {
|
||||
stock = rv.stock;
|
||||
takeDatetime = rv.takeDatetime;
|
||||
cleanDatetime = rv.cleanDatetime;
|
||||
number = rv.number;
|
||||
stoploss = rv.stoploss;
|
||||
goalPrice = rv.goalPrice;
|
||||
totalNumber = rv.totalNumber;
|
||||
buyMoney = rv.buyMoney;
|
||||
totalCost = rv.totalCost;
|
||||
totalRisk = rv.totalRisk;
|
||||
sellMoney = rv.sellMoney;
|
||||
contracts = std::move(rv.contracts);
|
||||
}
|
||||
|
||||
PositionRecord& PositionRecord::operator=(PositionRecord&& rv) {
|
||||
HKU_IF_RETURN(this == &rv, *this);
|
||||
stock = rv.stock;
|
||||
takeDatetime = rv.takeDatetime;
|
||||
cleanDatetime = rv.cleanDatetime;
|
||||
number = rv.number;
|
||||
stoploss = rv.stoploss;
|
||||
goalPrice = rv.goalPrice;
|
||||
totalNumber = rv.totalNumber;
|
||||
buyMoney = rv.buyMoney;
|
||||
totalCost = rv.totalCost;
|
||||
totalRisk = rv.totalRisk;
|
||||
sellMoney = rv.sellMoney;
|
||||
contracts = std::move(rv.contracts);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PositionRecord::PositionRecord(const Stock& stock, const Datetime& takeDatetime,
|
||||
const Datetime& cleanDatetime, double number, price_t stoploss,
|
||||
price_t goalPrice, double totalNumber, price_t totalMoney,
|
||||
price_t totalCost, price_t totalRisk, price_t sellMoney)
|
||||
const Datetime& cleanDatetime, double number, price_t avgPrice,
|
||||
price_t stoploss, price_t goalPrice, double totalNumber,
|
||||
price_t totalMoney, price_t totalCost, price_t totalRisk,
|
||||
price_t sellMoney)
|
||||
: stock(stock),
|
||||
takeDatetime(takeDatetime),
|
||||
cleanDatetime(cleanDatetime),
|
||||
number(number),
|
||||
avgPrice(avgPrice),
|
||||
stoploss(stoploss),
|
||||
goalPrice(goalPrice),
|
||||
totalNumber(totalNumber),
|
||||
@ -64,57 +34,48 @@ void PositionRecord::addTradeRecord(const TradeRecord& tr, const MarginRecord& m
|
||||
takeDatetime = tr.datetime;
|
||||
}
|
||||
switch (tr.business) {
|
||||
case BUSINESS_BUY:
|
||||
number += tr.number;
|
||||
case BUSINESS_BUY: {
|
||||
double new_number = number + tr.number;
|
||||
if (new_number == 0.0) {
|
||||
cleanDatetime = tr.datetime;
|
||||
} else {
|
||||
avgPrice = std::fabs((tr.realPrice * tr.number + avgPrice * number) / new_number);
|
||||
number = new_number;
|
||||
stoploss = tr.stoploss;
|
||||
goalPrice = tr.goalPrice;
|
||||
totalNumber += tr.number;
|
||||
buyMoney =
|
||||
roundEx(tr.realPrice * tr.number * stock.unit() * tr.margin_ratio + buyMoney,
|
||||
stock.precision());
|
||||
totalCost = roundEx(tr.cost.total + totalCost, stock.precision());
|
||||
totalRisk =
|
||||
roundEx(totalRisk + (tr.realPrice - tr.stoploss) * number * stock.unit(),
|
||||
stock.precision());
|
||||
}
|
||||
} break;
|
||||
|
||||
case BUSINESS_SELL: {
|
||||
double new_number = number - tr.number;
|
||||
if (new_number == 0.0) {
|
||||
cleanDatetime = tr.datetime;
|
||||
} else {
|
||||
avgPrice = std::fabs((tr.realPrice * tr.number - avgPrice * number) / new_number);
|
||||
}
|
||||
number = new_number;
|
||||
stoploss = tr.stoploss;
|
||||
goalPrice = tr.goalPrice;
|
||||
totalNumber += tr.number;
|
||||
buyMoney =
|
||||
roundEx(tr.realPrice * tr.number * stock.unit() + buyMoney, stock.precision());
|
||||
totalCost = roundEx(tr.cost.total + totalCost, stock.precision());
|
||||
totalRisk = roundEx(totalRisk + (tr.realPrice - tr.stoploss) * number * stock.unit(),
|
||||
stock.precision());
|
||||
break;
|
||||
|
||||
case BUSINESS_SELL:
|
||||
number -= tr.number;
|
||||
if (number == 0.0) {
|
||||
cleanDatetime = tr.datetime;
|
||||
}
|
||||
stoploss = tr.stoploss;
|
||||
goalPrice = tr.goalPrice;
|
||||
totalCost = roundEx(tr.cost.total + totalCost, stock.precision());
|
||||
sellMoney =
|
||||
roundEx(sellMoney + tr.realPrice * tr.number * stock.unit(), stock.precision());
|
||||
break;
|
||||
|
||||
} break;
|
||||
|
||||
default:
|
||||
HKU_ERROR("The business({}) should not appear here!", getBusinessName(tr.business));
|
||||
break;
|
||||
}
|
||||
|
||||
contracts.emplace_back(tr.datetime, tr.business, tr.realPrice, tr.number, 0.0, margin);
|
||||
}
|
||||
|
||||
std::tuple<price_t, price_t> PositionRecord::getProfit(Datetime datetime) {
|
||||
price_t profit = 0.0; // 浮动盈亏
|
||||
price_t margin = 0.0; // 维持保证金
|
||||
size_t pos = stock.getPos(datetime);
|
||||
if (pos != 0 && pos != Null<size_t>()) {
|
||||
for (auto& contract : contracts) {
|
||||
auto k = stock.getKRecord(pos - 1);
|
||||
contract.profit = (k.closePrice - contract.price) * number * stock.unit();
|
||||
profit += contract.profit;
|
||||
margin += k.closePrice * number * stock.unit() * contract.margin.initRatio *
|
||||
contract.margin.maintainRatio;
|
||||
}
|
||||
} else {
|
||||
for (auto& contract : contracts) {
|
||||
profit += contract.profit;
|
||||
margin += contract.price * contract.number * stock.unit() * contract.margin.initRatio *
|
||||
contract.margin.maintainRatio;
|
||||
}
|
||||
}
|
||||
return std::make_tuple(profit, margin);
|
||||
}
|
||||
|
||||
string PositionRecord::toString() const {
|
||||
@ -128,17 +89,12 @@ string PositionRecord::toString() const {
|
||||
precision = stock.precision();
|
||||
}
|
||||
|
||||
price_t costPrice = 0.0;
|
||||
if (number != 0.0) {
|
||||
costPrice = roundEx((buyMoney - sellMoney) / number, precision);
|
||||
}
|
||||
|
||||
string strip(", ");
|
||||
std::stringstream os;
|
||||
os << std::fixed;
|
||||
os.precision(precision);
|
||||
os << "Position(" << market << strip << code << strip << name << strip << takeDatetime << strip
|
||||
<< cleanDatetime << strip << number << strip << costPrice << strip << stoploss << strip
|
||||
<< cleanDatetime << strip << number << strip << avgPrice << strip << stoploss << strip
|
||||
<< goalPrice << strip << totalNumber << strip << buyMoney << strip << totalCost << strip
|
||||
<< totalRisk << strip << sellMoney << ")";
|
||||
os.unsetf(std::ostream::floatfield);
|
||||
@ -149,7 +105,8 @@ string PositionRecord::toString() const {
|
||||
bool HKU_API operator==(const PositionRecord& d1, const PositionRecord& d2) {
|
||||
return d1.stock == d2.stock && d1.takeDatetime == d2.takeDatetime &&
|
||||
d1.cleanDatetime == d2.cleanDatetime && fabs(d1.number - d2.number) < 0.00001 &&
|
||||
fabs(d1.stoploss - d2.stoploss) < 0.0001 && fabs(d1.goalPrice - d2.goalPrice) < 0.0001 &&
|
||||
fabs(d1.avgPrice - d2.avgPrice) < 0.0001 && fabs(d1.stoploss - d2.stoploss) < 0.0001 &&
|
||||
fabs(d1.goalPrice - d2.goalPrice) < 0.0001 &&
|
||||
fabs(d1.totalNumber - d2.totalNumber) < 0.00001 &&
|
||||
fabs(d1.buyMoney - d2.buyMoney) < 0.0001 && fabs(d1.totalCost - d2.totalCost) < 0.0001 &&
|
||||
fabs(d1.sellMoney - d2.sellMoney) < 0.0001;
|
||||
|
@ -19,39 +19,6 @@
|
||||
|
||||
namespace hku {
|
||||
|
||||
struct ContractRecord {
|
||||
ContractRecord() = default;
|
||||
ContractRecord(const Datetime& datetime, BUSINESS business, price_t price, double number,
|
||||
price_t profit, const MarginRecord& margin)
|
||||
: datetime(datetime),
|
||||
business(business),
|
||||
price(price),
|
||||
number(number),
|
||||
profit(profit),
|
||||
margin(margin) {}
|
||||
|
||||
Datetime datetime; // 交易日期
|
||||
BUSINESS business; // 业务类型
|
||||
price_t price; // 成交价格
|
||||
double number; // 成交数量
|
||||
price_t profit; // 浮动盈亏
|
||||
MarginRecord margin; // 保证金比例
|
||||
|
||||
#if HKU_SUPPORT_SERIALIZATION
|
||||
private:
|
||||
friend class boost::serialization::access;
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int version) {
|
||||
ar& BOOST_SERIALIZATION_NVP(datetime);
|
||||
ar& BOOST_SERIALIZATION_NVP(business);
|
||||
ar& BOOST_SERIALIZATION_NVP(price);
|
||||
ar& BOOST_SERIALIZATION_NVP(number);
|
||||
ar& BOOST_SERIALIZATION_NVP(profit);
|
||||
ar& BOOST_SERIALIZATION_NVP(margin);
|
||||
}
|
||||
#endif /* HKU_SUPPORT_SERIALIZATION */
|
||||
};
|
||||
|
||||
/**
|
||||
* 持仓记录
|
||||
* @ingroup TradeManagerClass
|
||||
@ -59,14 +26,10 @@ private:
|
||||
class HKU_API PositionRecord {
|
||||
public:
|
||||
PositionRecord() = default;
|
||||
PositionRecord(const PositionRecord&) = default;
|
||||
PositionRecord(PositionRecord&& rv);
|
||||
PositionRecord& operator=(const PositionRecord&) = default;
|
||||
PositionRecord& operator=(PositionRecord&& rv);
|
||||
|
||||
PositionRecord(const Stock& stock, const Datetime& takeDatetime, const Datetime& cleanDatetime,
|
||||
double number, price_t stoploss, price_t goalPrice, double totalNumber,
|
||||
price_t buyMoney, price_t totalCost, price_t totalRisk, price_t sellMoney);
|
||||
double number, price_t avgPrice, price_t stoploss, price_t goalPrice,
|
||||
double totalNumber, price_t buyMoney, price_t totalCost, price_t totalRisk,
|
||||
price_t sellMoney);
|
||||
|
||||
/** 仅用于python的__str__ */
|
||||
string toString() const;
|
||||
@ -81,6 +44,7 @@ public:
|
||||
Datetime takeDatetime; ///< 初次建仓日期
|
||||
Datetime cleanDatetime; ///< 平仓日期,当前持仓记录中为Null<Datetime>()
|
||||
double number = 0.0; ///< 当前持仓数量
|
||||
price_t avgPrice = 0.0; ///< 平均买入/卖出价格
|
||||
price_t stoploss = 0.0; ///< 当前止损价
|
||||
price_t goalPrice = 0.0; ///< 当前的目标价格
|
||||
double totalNumber = 0.0; ///< 累计持仓数量
|
||||
@ -89,8 +53,6 @@ public:
|
||||
price_t totalRisk = 0.0; ///< 累计交易风险 = 各次 (买入价格-止损)*买入数量, 不包含交易成本
|
||||
price_t sellMoney = 0.0; ///< 累计卖出资金
|
||||
|
||||
std::list<ContractRecord> contracts;
|
||||
|
||||
//===================
|
||||
//序列化支持
|
||||
//===================
|
||||
@ -106,6 +68,7 @@ private:
|
||||
ar& bs::make_nvp("takeDatetime", take);
|
||||
ar& bs::make_nvp("cleanDatetime", clean);
|
||||
ar& BOOST_SERIALIZATION_NVP(number);
|
||||
ar& BOOST_SERIALIZATION_NVP(avgPrice);
|
||||
ar& BOOST_SERIALIZATION_NVP(stoploss);
|
||||
ar& BOOST_SERIALIZATION_NVP(goalPrice);
|
||||
ar& BOOST_SERIALIZATION_NVP(totalNumber);
|
||||
@ -113,7 +76,6 @@ private:
|
||||
ar& BOOST_SERIALIZATION_NVP(totalCost);
|
||||
ar& BOOST_SERIALIZATION_NVP(totalRisk);
|
||||
ar& BOOST_SERIALIZATION_NVP(sellMoney);
|
||||
ar& BOOST_SERIALIZATION_NVP(contracts);
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
@ -126,6 +88,7 @@ private:
|
||||
takeDatetime = Datetime(take);
|
||||
cleanDatetime = Datetime(clean);
|
||||
ar& BOOST_SERIALIZATION_NVP(number);
|
||||
ar& BOOST_SERIALIZATION_NVP(avgPrice);
|
||||
ar& BOOST_SERIALIZATION_NVP(stoploss);
|
||||
ar& BOOST_SERIALIZATION_NVP(goalPrice);
|
||||
ar& BOOST_SERIALIZATION_NVP(totalNumber);
|
||||
@ -133,7 +96,6 @@ private:
|
||||
ar& BOOST_SERIALIZATION_NVP(totalCost);
|
||||
ar& BOOST_SERIALIZATION_NVP(totalRisk);
|
||||
ar& BOOST_SERIALIZATION_NVP(sellMoney);
|
||||
ar& BOOST_SERIALIZATION_NVP(contracts);
|
||||
}
|
||||
|
||||
BOOST_SERIALIZATION_SPLIT_MEMBER()
|
||||
|
@ -318,13 +318,12 @@ TradeRecord TradeManager::buy(const Datetime& datetime, const Stock& stock, pric
|
||||
HKU_ERROR_IF_RETURN(datetime < lastDatetime(), result,
|
||||
"{} {} datetime must be >= lastDatetime({})!", datetime,
|
||||
stock.market_code(), lastDatetime());
|
||||
HKU_ERROR_IF_RETURN(number == 0.0 || (!getParam<bool>("support_margin") && number <= 0.0),
|
||||
result, "{} {} Invalid buy numer: {}!", datetime, stock.market_code(),
|
||||
number);
|
||||
HKU_ERROR_IF_RETURN(std::fabs(number) < stock.minTradeNumber(), result,
|
||||
HKU_ERROR_IF_RETURN(number <= 0.0, result, "{} {} Invalid buy numer: {}!", datetime,
|
||||
stock.market_code(), number);
|
||||
HKU_ERROR_IF_RETURN(number < stock.minTradeNumber(), result,
|
||||
"{} {} Buy number({}) must be >= minTradeNumber({})!", datetime,
|
||||
stock.market_code(), number, stock.minTradeNumber());
|
||||
HKU_ERROR_IF_RETURN(std::fabs(number) > stock.maxTradeNumber(), result,
|
||||
HKU_ERROR_IF_RETURN(number != MAX_DOUBLE && number > stock.maxTradeNumber(), result,
|
||||
"{} {} Buy number({}) must be <= maxTradeNumber({})!", datetime,
|
||||
stock.market_code(), number, stock.maxTradeNumber());
|
||||
|
||||
@ -387,6 +386,11 @@ TradeRecord TradeManager::buy(const Datetime& datetime, const Stock& stock, pric
|
||||
} else {
|
||||
PositionRecord& position = pos_iter->second;
|
||||
position.addTradeRecord(result, margin);
|
||||
if (position.number == 0.0) {
|
||||
m_position_history.push_back(position);
|
||||
//删除当前持仓
|
||||
m_position.erase(stock.id());
|
||||
}
|
||||
}
|
||||
|
||||
if (result.datetime > m_broker_last_datetime) {
|
||||
@ -413,7 +417,7 @@ TradeRecord TradeManager::sell(const Datetime& datetime, const Stock& stock, pri
|
||||
HKU_ERROR_IF_RETURN(datetime < lastDatetime(), result,
|
||||
"{} {} datetime must be >= lastDatetime({})!", datetime,
|
||||
stock.market_code(), lastDatetime());
|
||||
HKU_ERROR_IF_RETURN(number == 0.0, result, "{} {} number is zero!", datetime,
|
||||
HKU_ERROR_IF_RETURN(number <= 0.0, result, "{} {} number is zero!", datetime,
|
||||
stock.market_code());
|
||||
|
||||
// 对于分红扩股造成不满足最小交易量整数倍的情况,只能通过number=MAX_DOUBLE的方式全仓卖出
|
||||
@ -737,29 +741,6 @@ void TradeManager::updateWithWeight(const Datetime& datetime) {
|
||||
m_trade_list.push_back(new_trade_buffer[i]);
|
||||
}
|
||||
|
||||
if (getParam<bool>("support_margin")) {
|
||||
price_t total_profit = 0.0;
|
||||
price_t total_maintain_margin = 0.0;
|
||||
for (; position_iter != m_position.end(); ++position_iter) {
|
||||
auto [profit, margin] = position_iter->second.getProfit(datetime);
|
||||
total_profit += profit;
|
||||
total_maintain_margin += margin;
|
||||
}
|
||||
|
||||
bool will_margin_closeout = false;
|
||||
if (total_profit < 0.0 && m_cash < total_maintain_margin) {
|
||||
if (getParam<bool>("auto_checkin")) {
|
||||
checkin(datetime, roundEx(total_maintain_margin - m_cash, precision));
|
||||
} else {
|
||||
will_margin_closeout = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (will_margin_closeout) {
|
||||
// 执行强平操作
|
||||
}
|
||||
}
|
||||
|
||||
m_last_update_datetime = datetime;
|
||||
}
|
||||
|
||||
@ -996,8 +977,8 @@ bool TradeManager::_add_buy_tr(const TradeRecord& tr) {
|
||||
position_map_type::iterator pos_iter = m_position.find(tr.stock.id());
|
||||
if (pos_iter == m_position.end()) {
|
||||
m_position[tr.stock.id()] = PositionRecord(
|
||||
tr.stock, tr.datetime, Null<Datetime>(), tr.number, tr.stoploss, tr.goalPrice, tr.number,
|
||||
money, tr.cost.total,
|
||||
tr.stock, tr.datetime, Null<Datetime>(), tr.number, tr.realPrice, tr.stoploss,
|
||||
tr.goalPrice, tr.number, money, tr.cost.total,
|
||||
roundEx((tr.realPrice - tr.stoploss) * tr.number * tr.stock.unit(), precision), 0.0);
|
||||
} else {
|
||||
PositionRecord& position = pos_iter->second;
|
||||
|
@ -19,8 +19,8 @@ using namespace hku;
|
||||
|
||||
void export_PositionRecord() {
|
||||
class_<PositionRecord>("PositionRecord", "持仓记录", init<>())
|
||||
.def(init<const Stock&, const Datetime&, const Datetime&, double, price_t, price_t, double,
|
||||
price_t, price_t, price_t, price_t>())
|
||||
.def(init<const Stock&, const Datetime&, const Datetime&, double, price_t, price_t, price_t,
|
||||
double, price_t, price_t, price_t, price_t>())
|
||||
|
||||
.def("__str__", &PositionRecord::toString)
|
||||
.def("__repr__", &PositionRecord::toString)
|
||||
@ -30,6 +30,7 @@ void export_PositionRecord() {
|
||||
.def_readwrite("clean_datetime", &PositionRecord::cleanDatetime,
|
||||
"平仓日期,当前持仓记录中为 constant.null_datetime")
|
||||
.def_readwrite("number", &PositionRecord::number, "当前持仓数量(float)")
|
||||
.def_readwrite("avg_price", &PositionRecord::avgPrice, "平均买入/卖出价格(float)")
|
||||
.def_readwrite("stoploss", &PositionRecord::stoploss, "当前止损价(float)")
|
||||
.def_readwrite("goal_price", &PositionRecord::goalPrice, "当前的目标价格(float)")
|
||||
.def_readwrite("total_number", &PositionRecord::totalNumber, "累计持仓数量(float)")
|
||||
|
Loading…
Reference in New Issue
Block a user