#include <iostream>
#include <array>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <stdlib.h>
#include <stdio.h>
#include <cstring>
#include <ctime>
using namespace std;
enum class instr_type { CALL, PUT, SPOT };
typedef std::int8_t BS;
const BS Buy = 'B';
const BS Sell = 'S';
const BS SellShort = 'H';
const BS BuyCover = 'C';
template <typename T = BS>
class BuySellT {
public:
BuySellT() : _bs(0) {}
BuySellT(const T bs) noexcept : _bs(bs){}
BuySellT& operator =(const T& p) {
_bs = p;
return *this;
}
BuySellT& operator =(const BuySellT& bs) {
if (this != &bs) {
_bs = bs.native();
}
return *this;
}
bool operator ==(const BuySellT& bs) const {
return (regular() == bs.regular());
}
bool operator ==(const T& bs) const {
return (regular() == bs);
}
operator T() const {
return regular();
}
T regular() const {
if (SellShort == _bs) {
return Sell;
}
if (BuyCover == _bs) {
return Buy;
}
return _bs;
}
T native() const {
return _bs;
}
bool empty() const {
return (0 == _bs);
}
private:
T _bs;
};
typedef BuySellT<BS> TBuySell;
class DateCode {
public:
static const char* YYYYMMDD(std::time_t datetime, char* to, size_t maxchar){
if (nullptr == to) {
return to;
}
if(datetime < 0) {
sprintf(to, "not datetime");
return to;
}
struct tm *TM = gmtime(&datetime);
if(nullptr == TM) {
sprintf(to, "null");
return to;
}
sprintf(to, "%04d%02d%02d",
TM->tm_year + 1900, TM->tm_mon + 1, TM->tm_mday);
return to;
}
static std::string YYYYMMDD(std::time_t datetime) {
if (datetime < 0) {
return "not datetime";
}
struct tm *TM = gmtime(&datetime);
if (nullptr == TM) {
return "null";
}
char buf[356];
sprintf(buf, "'%04d%02d%02d'",
TM->tm_year + 1900, TM->tm_mon + 1, TM->tm_mday);
return std::string(buf);
}
static const char* YYYYMMDD(std::time_t datetime, char* to, size_t maxchar, unsigned char delimiter){
if (nullptr == to) {
return to;
}
if (datetime < 0) {
sprintf(to, "not datetime");
return to;
}
struct tm *TM = gmtime(&datetime);
if (nullptr == TM) {
sprintf(to, "null");
return to;
}
sprintf(to, "%04d%c%02d%c%02d", TM->tm_year + 1900, delimiter, TM->tm_mon + 1, delimiter, TM->tm_mday);
return to;
}
const char* date(std::time_t time) noexcept {
DateCode::YYYYMMDD(time, date_, sizeof(date_), '/');
return date_;
}
const char* time(std::time_t time, const char delimeter = ':') noexcept {
auto seconds = time % 86400;
date_[0] = 0;
if (delimeter != 0) {
sprintf(date_, "%02zd%c%02zd%c%02zd", seconds / 3600, delimeter, (seconds / 60) % 60, delimeter, seconds % 60);
} else {
sprintf(date_, "%02zd%02zd%02zd", seconds / 3600, (seconds / 60) % 60, seconds % 60);
}
return date_;
}
private:
char date_[256]{0};
};
static const char* sInstrType(instr_type type) noexcept{
constexpr std::array<const char*, 3> ar{ "CALL", "PUT", "SPOT"};
auto idx = static_cast<std::size_t>(type);
if (idx >= ar.size() || type < instr_type::CALL){
return "Invalid instrument type";
}
return ar[idx];
}
template<typename T>
static const char* sLevel(T level) noexcept {
constexpr std::array<const char*, 6> ar{ "", "Covered Calls", "Long options", "Option strategies and Short-stock Covered Puts", "Naked Puts", "Naked Calls" };
if (level >= ar.size() || level < 0) {
return "Invalid Option Level";
}
return ar[level];
}
template <int len = 25, typename T = char>
class FixedString {
public:
std::array<T, len> str_ = { 0 };
public:
FixedString() {}
FixedString(const char* str) {
if (nullptr == str) {
return;
}
copy(str);
}
FixedString(const FixedString& str) {
str_ = str.str_;
}
FixedString& operator = (const FixedString& str) {
if (this != &str && str.str_.size() == str_.size()) {
str_ = str.str_;
}
return *this;
}
FixedString& operator = (const char* str) {
if (nullptr == str) {
return *this;
}
copy(str);
return *this;
}
FixedString& operator = (const std::string& str) {
copy(str.c_str());
return *this;
}
const char* c_str() const {
return str_.data();
}
bool empty() const {
return (str_[0] == 0);
}
bool operator == (const FixedString& str) const {
return (0 == strcmp(str.str_.data(), str_.data()));
}
bool operator == (const char* str) const {
return (0 == strcmp(str, str_.data()));
}
bool operator != (const char* str) const {
return (0 != strcmp(str, str_.data()));
}
bool operator < (const FixedString& str) const {
return strcmp(str_.data(), str.str_.data()) < 0;
}
protected:
void copy(const char* from) {
for (auto& x : str_) {
if (0 == *from) {
x = 0;
break;
}
x = *from;
from++;
}
str_.back() = 0;
}
};
class option_level_calc
{
public:
template<typename T = int64_t, size_t len = 25 + 1>
struct position
{
position() : B(0), S(0), N(0), contract_size(1), type(instr_type::SPOT), expiration(0)
{
}
position(const char* Seccode, const char* Underlying, T OrderBuy, T OrderSell, T Saldo, T ContractSize, instr_type InstrType, std::time_t Expiration) :
seccode(Seccode)
, underlying_asset(Underlying)
, B(OrderBuy)
, S(OrderSell)
, N(Saldo)
, contract_size(ContractSize)
, type(InstrType)
, expiration(Expiration)
{
}
std::string print() const noexcept {
std::string s{ "POS: seccode=" };
s += seccode.c_str();
s += " root="; s += underlying_asset.c_str();
s += " N="; s += std::to_string(N);
s += " B="; s += std::to_string(B);
s += " S="; s += std::to_string(S);
s += " cs="; s += std::to_string(contract_size);
s += " type="; s += sInstrType(type);
return s;
}
FixedString<len> seccode;
FixedString<len> underlying_asset;
T B;
T S;
T N;
T contract_size;
instr_type type;
std::time_t expiration;
};
template<typename T = int64_t, size_t len = 25 + 1>
struct order
{
order() = default;
order(const char* Seccode, const char* Underlying, std::int64_t Vol, TBuySell Bs, T ContractSize, instr_type InstrType, std::time_t Expiration) :
seccode(Seccode)
, underlying_asset(Underlying)
, Qty(Vol)
, bs(Bs)
, contract_size(ContractSize)
, type(InstrType)
, expiration(Expiration)
{
}
static void update(order<>& ord, instr_type type, TBuySell bs, T contract_size, T Qty) noexcept {
if (instr_type::SPOT == type) {
if (bs == Buy) {
ord.Bspot += Qty;
} else {
ord.Sspot += Qty;
}
} else if (instr_type::CALL == type) {
if (bs == Buy) {
ord.Bcall += (Qty * contract_size);
} else {
ord.Scall += (Qty * contract_size);
}
} else if (instr_type::PUT == type) {
if (bs == Buy) {
ord.Bput += (Qty * contract_size);
} else {
ord.Sput += (Qty * contract_size);
}
}
}
void calc_buy_sell_params() noexcept {
if (legs.empty()) {
order<>::update(*this, type, bs, contract_size, Qty);
} else {
for (const auto& leg : legs) {
order<>::update(*this, leg.type, leg.bs, leg.contract_size, leg.Qty);
}
}
}
void clear() noexcept {
Bspot = 0;
Sspot = 0;
Bcall = 0;
Scall = 0;
Bput = 0;
Sput = 0;
// остальные поля чистить не надо.
}
std::string print() const noexcept {
std::string s{"ORD"};
if (!legs.empty()) {
s += " MULTILEG";
}
s += " seccode="; s += seccode.c_str();
s += " root="; s += underlying_asset.c_str();
s += " Qty="; s += std::to_string(Qty);
s += " bs="; s += bs.native();
s += " cs="; s += std::to_string(contract_size);
s += " type="; s += sInstrType(type);
s += " Bspot="; s += std::to_string(Bspot);
s += " Sspot="; s += std::to_string(Sspot);
s += " Bcall="; s += std::to_string(Bcall);
s += " Scall="; s += std::to_string(Scall);
s += " Bput="; s += std::to_string(Bput);
s += " Sput="; s += std::to_string(Sput);
for (const auto& x : legs) {
s += "\n\t LEG";
auto sLeg = x.print();
s += sLeg;
}
return s;
}
FixedString<len> seccode;
FixedString<len> underlying_asset;
T Bspot{ 0 }; // заявлено купить спота
T Sspot{ 0 }; // заявлено продать спота
T Bcall{ 0 }; // заявлено купить коллов
T Scall{ 0 }; // заявлено продать коллов
T Bput{ 0 }; // заявлено купить путов
T Sput{ 0 }; // заявлено продать путов
T Qty{ 0 }; // исходные. заявлено количество
TBuySell bs{ 0 }; // исходные. направление
T contract_size{ 1 };
instr_type type{ instr_type::SPOT };
std::time_t expiration{ 0 };
std::vector<order> legs;
};
template<typename Container_positions, typename Container_orders, typename Container_trace>
static std::int64_t calc(Container_positions& positions, Container_orders& orders, Container_trace& output, std::int32_t log_severity)
{
return option_level_calc::the_calc(positions, orders, output, log_severity);
}
protected:
using DATES = std::set<std::time_t>;
struct BA {
void clear() noexcept {
NspotLong = 0;
NspotShort = 0;
NcallLong = 0;
NcallShort = 0;
NputLong = 0;
NputShort = 0;
orders.clear();
positions.clear();
// exp_dates чистить не надо
}
void fill(instr_type type, std::int64_t N, std::int64_t ContractSize) {
if (instr_type::SPOT == type) {
if (N > 0) {
NspotLong += N;
} else if (N < 0) {
NspotShort += abs(N);
}
} else if (instr_type::CALL == type) {
if (N > 0) {
NcallLong += N * ContractSize;
} else if (N < 0) {
NcallShort += abs(N * ContractSize);
}
} else {
if (N > 0) {
NputLong += N * ContractSize;
} else if (N < 0) {
NputShort += abs(N * ContractSize);
}
}
}
std::string print() const noexcept {
std::string s{};
s += "NspotLong="; s += std::to_string(NspotLong);
s += " NspotShort="; s += std::to_string(NspotShort);
s += " NcallLong="; s += std::to_string(NcallLong);
s += " NcallShort="; s += std::to_string(NcallShort);
s += " NputLong="; s += std::to_string(NputLong);
s += " NputShort="; s += std::to_string(NputShort);
for (const auto& ord : orders) {
s += "\n";
auto sOrd = ord.print();
s += sOrd;
}
for (const auto& x : positions) {
s += "\n";
auto sPos = x.print();
s += sPos;
}
return s;
}
std::int64_t NspotLong{ 0 }; // текущий лонг в споте
std::int64_t NspotShort{ 0 }; // текущий шорт в споте(фактически не используется)
std::int64_t NcallLong{ 0 }; // совокупный лонг в коллах
std::int64_t NcallShort{ 0 }; // совокупный шорт в коллах
std::int64_t NputLong{ 0 }; // совокупный лонг в путах
std::int64_t NputShort{ 0 }; // совокупный шорт в путах
std::vector<order<>> orders;
std::vector<position<>> positions;
DATES exp_dates; // date + рассчетный опционный уровень
std::int64_t MaxBaLevel{ 0 }; // максимальный уровень в этом базовом активе
};
static std::int64_t omega(std::int64_t V, std::int64_t N) noexcept {
auto x{ V };
auto y{ V };
if ((x < -N ? V : -N) < 0) { x = 0; }
if ((y > -N ? V : -N) > 0) { y = 0; }
return x - y;
}
template<typename Container_positions, typename Container_orders, typename Container_trace>
static std::int64_t ba_calc(const BA& ba, Container_positions& positions, Container_orders& orders, Container_trace& output) {
auto three_level = [&](const auto& ba, const auto& orders) -> std::int64_t {
// На уровнях 3 и 4 не должно быть внеспредовых шортов в коллах, не перекрытых спотом
std::int64_t W{ ba.NspotLong + ba.NcallLong - ba.NcallShort };
if (W < 0) {
return 5;
}
for (const auto& o : orders) {
std::int64_t dW{ o.Sspot - o.Bspot };
if (!ba.NspotLong && dW > 0) {
dW = 0;
}
dW += o.Scall - o.Bcall;
if (dW > 0) {
W -= dW;
if (W < 0) {
return 5;
}
}
}
// На уровне 3 не должно быть внеспредовых шортов в путах
W = ba.NputLong - ba.NputShort;
if (W < 0) {
return 4;
}
for (const auto& o : orders) {
std::int64_t dW{ o.Sput - o.Bput };
if (dW > 0) {
W -= dW;
if (W < 0) {
return 4;
}
}
}
return 3;
};
auto two_level = [&](const auto& ba, const auto& positions, const auto& orders) -> std::int64_t {
// На уровне 2 не должно быть шортов в путах
for (const auto& i : positions) {
if (instr_type::PUT == i.type && i.N - i.S < 0) {
return three_level(ba, orders);
}
}
return 2;
};
auto one_level = [&](const auto& ba, const auto& positions, const auto& orders) -> std::int64_t {
// На уровнях 1 и 2 не должно быть непокрытых шортов в коллах
// Определяем isPositive ("положительность" базового актива)
bool isPositive{ false };
if (ba.NspotLong) {
isPositive = true;
}
else if (0 == ba.NspotShort) {
for (const auto& i : positions) {
if (instr_type::SPOT == i.type && i.B) {
isPositive = true;
break;
}
}
}
if (isPositive) {
std::int64_t W{ ba.NspotLong - ba.NcallShort };
if (W < 0) {
return three_level(ba, orders);
}
for (const auto& o : orders) {
std::int64_t dW{ o.Sspot - o.Bspot + o.Scall };
for (const auto& m : o.legs) {
if (instr_type::CALL == m.type) {
std::int64_t N{ 0 };
for (const auto& i : positions) {
if (instr_type::CALL == i.type &&
i.seccode == m.seccode.c_str()) {
N = i.N;
break;
}
}
// legVol = полный заявленный объём инструмента этой ноги, со знаком
// legVol = leg.QtyRatio * Quantity * Contractsize * sign(leg.buysell)
std::int64_t legVol{ m.Qty * o.Qty * m.contract_size * (m.bs == Buy ? 1 : -1) };
dW -= omega(legVol, N);
}
}
if (dW > 0) {
W -= dW;
if (W < 0) {
return three_level(ba, orders);
}
}
}
} else {
for (const auto& i : positions) {
if (instr_type::CALL == i.type && i.N - i.S < 0) {
return three_level(ba, orders);
}
}
}
// На уровне 1 не должно быть путов
if (ba.NputLong || ba.NputShort) {
return two_level(ba, positions, orders);
}
for (const auto& o : orders) {
if (o.Bput || o.Sput) {
return two_level(ba, positions, orders);
}
}
// На уровне 1 не должно быть лонгов в коллах
for (const auto& i : positions) {
if (instr_type::CALL == i.type && i.N + i.B > 0) {
return two_level(ba, positions, orders);
}
}
return 1;
};
auto calc_level = [&](const auto& ba, const auto& positions, const auto& orders) -> std::int64_t {
// На уровне 0 не должно быть никаких опционов
if (ba.NcallLong || ba.NcallShort || ba.NputLong || ba.NputShort) {
return one_level(ba, positions, orders);
}
for (const auto& ord : orders) {
if (ord.Bcall || ord.Scall || ord.Bput || ord.Sput) {
return one_level(ba, positions, orders);;
}
}
return 0ll;
};
return calc_level(ba, positions, orders);
}
template<typename Container_positions, typename Container_orders, typename Container_trace>
static std::int64_t the_calc(Container_positions& positions, Container_orders& orders, Container_trace& output, std::int32_t log_severity)
{
auto now = ::time(nullptr);
now /= 86400;
std::map<FixedString<25 + 1>, BA> base_actives;
for (const auto& i : positions) {
if (!i.underlying_asset.empty()) {
if (log_severity > 1) {
output.push_back(i.print().c_str());
}
auto& ba = base_actives[i.underlying_asset];
// формируем пустой список, рассчитываем список дат экспираций
// где первый самый маленький элемент - текущая дата
ba.exp_dates.insert(DATES::value_type(now));
if (i.expiration && i.expiration > now) {
ba.exp_dates.insert(DATES::value_type(i.expiration));
}
}
}
// Все опционы базового актива группируются по дате экспирации.
// Если количество уникальных дат экспирации больше одной, то дополнительно производится
// определение прогнозного опционного уровня в базовом активе на последовательные моменты после каждой очередной экспирации,
// кроме самой дальней.
for (auto& x : base_actives) {
if (x.second.exp_dates.size() > 1) {
x.second.exp_dates.erase(std::prev(x.second.exp_dates.end())); // удаляем самую дальнюю дату экспирации
}
}
for (auto& ord : orders) {
ord.clear();
ord.calc_buy_sell_params();
}
if (log_severity > 1) {
for (const auto& ord : orders) {
output.push_back(ord.print().c_str());
}
output.push_back("===============================");
}
std::int64_t max_level{ 0 };
for (auto& j : base_actives) {
std::string log("OptLevels: ba=");
const auto& underlying = j.first;
log += underlying.c_str();
auto& ba = j.second;
for (auto date : ba.exp_dates) {
if (log_severity > 0) {
char buf[256];
log += " {date="; log += DateCode::YYYYMMDD(date * 86400, buf, sizeof(buf), '-');
}
for (const position<>& i : positions) {
if (!i.underlying_asset.empty() && underlying == i.underlying_asset) {
if (0 == i.expiration || i.expiration > date) {
ba.fill(i.type, i.N, i.contract_size);
ba.positions.push_back(i);
}
}
}
for (const order<>& o : orders) {
// ноги мультилега могут вырождаться
if (!o.underlying_asset.empty() && underlying == o.underlying_asset) {
if (0 == o.expiration || o.expiration > date) {
if (o.legs.empty()) {
ba.orders.push_back(o);
} else {
// multileg
order<> ord(o.seccode.c_str(), o.underlying_asset.c_str(), o.Qty, o.bs, o.contract_size, o.type, o.expiration);
for (const auto& leg : o.legs) {
if (0 == leg.expiration || leg.expiration > date) {
ord.legs.push_back(leg);
}
}
auto size = ord.legs.size();
//вырождение мультилега, или неверные параметры
if (size < 5) {
ord.calc_buy_sell_params();
ba.orders.push_back(ord);
}
}
}
}
}
auto LevelByDate = ba_calc<>(ba, ba.positions, ba.orders, output);
if (log_severity > 1) {
char buf[256]{ 0 };
DateCode::YYYYMMDD(date * 86400, buf, sizeof(buf), '-');
std::string s{ "BA=" + std::string(underlying.c_str()) + " "};
s += buf;
s += (" ----------------------------------------");
output.push_back(s.c_str());
s = ba.print();
output.push_back(s.c_str());
}
if (log_severity > 0) {
log += " level="; log += std::to_string(LevelByDate); log += "}";
}
if (LevelByDate > ba.MaxBaLevel) {
ba.MaxBaLevel = LevelByDate;
}
if (ba.MaxBaLevel > max_level) {
max_level = ba.MaxBaLevel;
}
ba.clear(); // очищаем результаты выборок
}
if (log_severity > 0) {
output.push_back(log.c_str());
}
}
return max_level;
}
};
int main() {
std::vector<option_level_calc::position<>> positions;
std::vector<option_level_calc::order<>> orders;
orders.emplace_back("AAPL 200619C00250000", "AAPL", 1, 'B', 100, instr_type::CALL, 29587);
positions.emplace_back("AAPL 200619C00250000", "AAPL", 60, 6, 4, 100, instr_type::CALL, 29587);
positions.emplace_back("AAPL 200619230250000", "AAPL", 1, 70, 0, 100, instr_type::PUT, 29587);
positions.emplace_back("MSFT 200619230250020", "MSFT", 1, 77, 0, 100, instr_type::CALL, 29587);
positions.emplace_back("MSFT 20061923wq50020", "MSFT", 1, 22, 0, 100, instr_type::CALL, 29587);
positions.emplace_back("MSFT 20061923ws50020", "MSFT", 1, 22, 900, 100, instr_type::CALL, 29587);
positions.emplace_back("CISCO 2006192350020", "CSCO", 1, 22, 900, 100, instr_type::CALL, 29587);
positions.emplace_back("CISCO 2006192350020", "CSCO", 700, 22, 90, 100, instr_type::PUT, 29587);
positions.emplace_back("IBS 2006192350020", "IBS", 700, 22, 90, 100, instr_type::PUT, 29587);
positions.emplace_back("MSFT", "MSFT", 1, 0, 4, 1, instr_type::SPOT, 0);
positions.emplace_back("MSFTA", "", 12, 0, 4, 1, instr_type::SPOT, 0);
std::vector<FixedString<128>> output;
auto level = option_level_calc::calc(positions, orders, output, 3);
std::cout << "Option level calc : port_level=" << level << " " << std::endl;
for (auto& item : output)
{
std::cout << "\t" << item.c_str() << std::endl;
}
return 0;
}